[automerger skipped] Fix bug of disabling grouped CBRS during profile switch on primary SIM am: f587f04d30 -s ours am: 46155cadc9 -s ours

am skip reason: Change-Id Ie0b6d84d12c7fa3ede6e8b24275145849a89f0a3 with SHA-1 4b770696fa is in history

Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/opt/telephony/+/13319432

MUST ONLY BE SUBMITTED BY AUTOMERGER

Change-Id: I0c5295e6c909a2b6a375b04df06ffb88175d9cde
diff --git a/Android.bp b/Android.bp
index d1a8937..3f87578 100644
--- a/Android.bp
+++ b/Android.bp
@@ -18,6 +18,7 @@
     srcs: [
         "src/java/android/telephony/**/*.java",
     ],
+    path: "src/java",
 }
 
 filegroup {
@@ -32,6 +33,20 @@
     srcs: [
         "src/java/**/*.java",
     ],
+    path: "src/java",
+}
+
+filegroup {
+    name: "jarjar-rules-shared",
+    srcs: ["jarjar-rules-shared.txt"],
+}
+
+genrule {
+    name: "statslog-telephony-java-gen",
+    tools: ["stats-log-api-gen"],
+    cmd: "$(location stats-log-api-gen) --java $(out) --module telephony"
+        + " --javaPackage com.android.internal.telephony --javaClass TelephonyStatsLog",
+    out: ["com/android/internal/telephony/TelephonyStatsLog.java"],
 }
 
 java_library {
@@ -43,11 +58,15 @@
     },
     srcs: [
         ":opt-telephony-common-srcs",
+        ":framework-telephony-common-shared-srcs",
+        ":net-utils-telephony-common-srcs",
+        ":statslog-telephony-java-gen",
+        ":statslog-cellbroadcast-java-gen",
         "src/java/**/I*.aidl",
         "src/java/**/*.logtags",
     ],
 
-    jarjar_rules: ":framework-jarjar-rules",
+    jarjar_rules: ":jarjar-rules-shared",
 
     libs: [
         "android.hardware.radio-V1.0-java",
@@ -55,17 +74,21 @@
         "android.hardware.radio-V1.2-java",
         "android.hardware.radio-V1.3-java",
         "android.hardware.radio-V1.4-java",
+        "android.hardware.radio-V1.5-java",
         "voip-common",
         "ims-common",
-        "services",
+        "unsupportedappusage",
     ],
     static_libs: [
         "android.hardware.radio.config-V1.0-java-shallow",
         "android.hardware.radio.config-V1.1-java-shallow",
         "android.hardware.radio.config-V1.2-java-shallow",
         "android.hardware.radio.deprecated-V1.0-java-shallow",
-        "telephony-protos",
         "ecc-protos-lite",
+        "libphonenumber-nogeocoder",
+        "PlatformProperties",
+        "net-utils-framework-common",
+        "telephony-protos",
     ],
 
     product_variables: {
@@ -75,4 +98,3 @@
         },
     },
 }
-
diff --git a/AndroidManifest_Resources.xml b/AndroidManifest_Resources.xml
new file mode 100644
index 0000000..7afba27
--- /dev/null
+++ b/AndroidManifest_Resources.xml
@@ -0,0 +1,8 @@
+<!-- Manifest for telephony resources APK -->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+          package="com.android.telephony.resources">
+    <application
+        android:directBootAware="true"
+        android:hasCode="false">
+    </application>
+</manifest>
\ No newline at end of file
diff --git a/CleanSpec.mk b/CleanSpec.mk
index 9caa0ad..1ca7248 100644
--- a/CleanSpec.mk
+++ b/CleanSpec.mk
@@ -49,6 +49,8 @@
 $(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/JAVA_LIBRARIES/telephony-common_intermediates)
 $(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/JAVA_LIBRARIES/telephony-common_intermediates)
 $(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/JAVA_LIBRARIES/telephony-common_intermediates)
+$(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/JAVA_LIBRARIES/telephony-common_intermediates)
+$(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/JAVA_LIBRARIES/telephony-common_intermediates)
 
 # ************************************************
 # NEWER CLEAN STEPS MUST BE AT THE END OF THE LIST
diff --git a/OWNERS b/OWNERS
index 284f0ec..3059d4d 100644
--- a/OWNERS
+++ b/OWNERS
@@ -6,8 +6,10 @@
 rgreenwalt@google.com
 tgunn@google.com
 jminjie@google.com
-mpq@google.com
 shuoq@google.com
 refuhoo@google.com
-paulye@google.com
 nazaninb@google.com
+sarahchin@google.com
+dbright@google.com
+xiaotonj@google.com
+
diff --git a/README.txt b/README.txt
index e613a00..9e40b77 100644
--- a/README.txt
+++ b/README.txt
@@ -1,71 +1,37 @@
-This package contains classes used to manage a DataConnection.
+-- README for frameworks/opt/telephony --
 
-A criticial aspect of this class is that most objects in this
-package run on the same thread except DataConnectionTracker
-This makes processing efficient as it minimizes context
-switching and it eliminates issues with multi-threading.
+This directory contains telephony libraries which function as the
+implementation code for APIs in TelephonyManager, SubscriptionManager,
+SmsManager and others.
 
-This can be done because all actions are either asynchronous
-or are known to be non-blocking and fast. At this time only
-DcTesterDeactivateAll takes specific advantage of this
-single threading knowledge by using Dcc#mDcListAll so be
-very careful when making changes that break this assumption.
+These libraries run in the com.android.phone process and exist to support
+telephony services created by the user’s apps (generally carrier apps), or by
+the system. This includes making phone calls, sending SMS/MMS, and connecting
+to data. Many APIs are plumbed down to the radio through HIDL calls defined in
+hardware/interfaces/radio/ hardware/interfaces/radio/config and return values
+that are sent back up as responses.
 
-A related change was in DataConnectionAc I added code that
-checks to see if the caller is on a different thread. If
-it is then the AsyncChannel#sendMessageSynchronously is
-used. If the caller is on the same thread then a getter
-is used. This allows the DCAC to be used from any thread
-and was required to fix a bug when Dcc called
-PhoneBase#notifyDataConnection which calls DCT#getLinkProperties
-and DCT#getLinkCapabilities which call Dcc all on the same
-thread. Without this change there was a dead lock when
-sendMessageSynchronously blocks.
+We define several AIDL interfaces in frameworks/base/telephony/ which we
+implement in this directory and packages/services/Telephony. This IPC scheme
+allows us to run public API code in the calling process, while the
+telephony-related code runs in the privileged com.android.phone process. Such
+implementations include PhoneInterfaceManager, SubscriptionController and
+others.
 
+The declaration of the com.android.phone process is in
+packages/services/telephony and the top-level application class is PhoneApp,
+which initializes everything else.
 
-== Testing ==
+-- Testing --
 
-The following are Intents that can be sent for testing pruproses on
-DEBUGGABLE builds (userdebug, eng)
+Unit tests are found in frameworks/opt/telephony/tests and can be
+run on a device connected through ADB with the command:
 
-*) Causes bringUp and retry requests to fail for all DC's
+	atest FrameworksTelephonyTests
 
-  adb shell am broadcast -a com.android.internal.telephony.dataconnection.action_fail_bringup --ei counter 2 --ei fail_cause -3
+Tests can also be run individually or by class:
 
-*) Causes all DC's to get torn down, simulating a temporary network outage:
+	atest <testClassName
+	atest <testClassName>#<testMethodName>
 
-  adb shell am broadcast -a com.android.internal.telephony.dataconnection.action_deactivate_all
-
-*) To simplify testing we also have detach and attach simulations below where {x} is gsm, cdma or sip
-
-  adb shell am broadcast -a com.android.internal.telephony.{x}.action_detached
-  adb shell am broadcast -a com.android.internal.telephony.{x}.action_attached
-
-
-== System properties for Testing ==
-
-On debuggable builds (userdebug, eng) you can change additional
-settings through system properties.  These properties can be set with
-"setprop" for the current boot, or added to local.prop to persist
-across boots.
-
-device# setprop key value
-
-device# echo "key=value" >> /data/local.prop
-device# chmod 644 /data/local.prop
-
-
--- Retry configuration --
-
-You can replace the connection retry configuration.  For example, you
-could change it to perform 4 retries at 5 second intervals:
-
-device# setprop test.data_retry_config "5000,5000,5000"
-
-
--- Roaming --
-
-You can force the telephony stack to always assume that it's roaming
-to verify higher-level framework functionality:
-
-device# setprop telephony.test.forceRoaming true
+For more on atest run `atest --help`
diff --git a/TEST_MAPPING b/TEST_MAPPING
new file mode 100644
index 0000000..e75dcb0
--- /dev/null
+++ b/TEST_MAPPING
@@ -0,0 +1,12 @@
+{
+  "presubmit": [
+    {
+      "name": "TeleServiceTests",
+      "options": [
+        {
+          "exclude-annotation": "androidx.test.filters.FlakyTest"
+        }
+      ]
+    }
+  ]
+}
diff --git a/jarjar-rules-shared.txt b/jarjar-rules-shared.txt
new file mode 100644
index 0000000..2bac95f
--- /dev/null
+++ b/jarjar-rules-shared.txt
@@ -0,0 +1,24 @@
+rule android.net.NetworkFactory* com.android.internal.telephony.NetworkFactory@1
+rule android.os.BasicShellCommandHandler* com.android.internal.telephony.BasicShellCommandHandler@1
+rule android.os.RegistrantList* com.android.internal.telephony.RegistrantList@1
+rule android.os.Registrant* com.android.internal.telephony.Registrant@1
+rule android.hidl.** android.internal.hidl.@1
+rule android.sysprop.** android.internal.telephony.sysprop.@1
+rule android.util.LocalLog* com.android.internal.telephony.LocalLog@1
+rule android.util.TimeUtils* com.android.internal.telephony.TimeUtils@1
+rule com.android.internal.os.SomeArgs* com.android.internal.telephony.SomeArgs@1
+rule com.android.internal.util.AsyncChannel* com.android.internal.telephony.AsyncChannel@1
+rule com.android.internal.util.AsyncService* com.android.internal.telephony.AsyncService@1
+rule com.android.internal.util.BitwiseInputStream* com.android.internal.telephony.BitwiseInputStream@1
+rule com.android.internal.util.FastXmlSerializer* com.android.internal.telephony.FastXmlSerializer@1
+rule com.android.internal.util.HexDump* com.android.internal.telephony.HexDump@1
+rule com.android.internal.util.IState* com.android.internal.telephony.IState@1
+rule com.android.internal.util.IndentingPrintWriter* com.android.internal.telephony.IndentingPrintWriter@1
+rule com.android.internal.util.Preconditions* com.android.internal.telephony.Preconditions@1
+rule com.android.internal.util.State* com.android.internal.telephony.State@1
+rule com.android.internal.util.StateMachine* com.android.internal.telephony.StateMachine@1
+rule com.android.internal.util.UserIcons* com.android.internal.telephony.UserIcons@1
+rule com.google.i18n.phonenumbers.** com.android.internal.telephony.phonenumbers.@1
+
+# Module library in frameworks/libs/net
+rule com.android.net.module.util.** com.android.internal.telephony.util.@1
diff --git a/proto/Android.bp b/proto/Android.bp
index a36f7a5..d7e379f 100644
--- a/proto/Android.bp
+++ b/proto/Android.bp
@@ -22,8 +22,8 @@
         ],
     },
     srcs: ["src/**/*.proto"],
-    no_framework_libs: true,
+    sdk_version: "system_current",
     jarjar_rules: "jarjar-rules.txt",
     // Pin java_version until jarjar is certified to support later versions. http://b/72703434
     java_version: "1.8",
-}
\ No newline at end of file
+}
diff --git a/proto/src/persist_atoms.proto b/proto/src/persist_atoms.proto
new file mode 100644
index 0000000..6600ff1
--- /dev/null
+++ b/proto/src/persist_atoms.proto
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2020 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.
+ */
+
+syntax = "proto2";
+
+package telephonyStatsLog;
+
+option java_package = "com.android.internal.telephony";
+option java_outer_classname = "PersistAtomsProto";
+
+// Holds atoms to store on persist storage in case of power cycle or process crash.
+// NOTE: using int64 rather than google.protobuf.Timestamp for timestamps simplifies implementation.
+// Next id: 5
+message PersistAtoms {
+    /* Aggregated RAT usage during the call. */
+    repeated RawVoiceCallRatUsage raw_voice_call_rat_usage = 1;
+
+    /* Timestamp of last voice_call_rat_usages pull. */
+    optional int64 raw_voice_call_rat_usage_pull_timestamp_millis = 2;
+
+    /* Per call statistics and information. */
+    repeated VoiceCallSession voice_call_session = 3;
+
+    /* Timestamp of last voice_call_sessions pull. */
+    optional int64 voice_call_session_pull_timestamp_millis = 4;
+}
+
+// The canonical versions of the following enums live in:
+//   frameworks/base/core/proto/android/telephony/enums.proto
+// The canonical versions of the following atoms live in:
+//   frameworks/base/cmds/statsd/src/atoms.proto
+// We cannot link against framework's and statsd's protolite libraries as it is "for test only".
+// NOTE: StatsLog functions use int in place of enum
+
+message VoiceCallSession {
+    optional int32 bearer_at_start = 1;
+    optional int32 bearer_at_end = 2;
+    optional int32 direction = 3;
+    optional int32 setup_duration = 4;
+    optional bool setup_failed = 5;
+    optional int32 disconnect_reason_code = 6;
+    optional int32 disconnect_extra_code = 7;
+    optional string disconnect_extra_message = 8;
+    optional int32 rat_at_start = 9;
+    optional int32 rat_at_end = 10;
+    optional int64 rat_switch_count = 11;
+    optional int64 codec_bitmask = 12;
+    optional int32 concurrent_call_count_at_start = 13;
+    optional int32 concurrent_call_count_at_end = 14;
+    optional int32 sim_slot_index = 15;
+    optional bool is_multi_sim = 16;
+    optional bool is_esim = 17;
+    optional int32 carrier_id = 18;
+    optional bool srvcc_completed = 19;
+    optional int64 srvcc_failure_count = 20;
+    optional int64 srvcc_cancellation_count = 21;
+    optional bool rtt_enabled = 22;
+    optional bool is_emergency = 23;
+    optional bool is_roaming = 24;
+
+    // Internal use only
+    optional int64 setup_begin_millis = 10001;
+}
+
+// Internal use only
+message RawVoiceCallRatUsage {
+    optional int32 carrier_id = 1;
+    optional int32 rat = 2;
+    optional int64 total_duration_millis = 3;
+    optional int64 call_count = 4;
+}
diff --git a/proto/src/telephony.proto b/proto/src/telephony.proto
index e4ce721..6e81ec0 100644
--- a/proto/src/telephony.proto
+++ b/proto/src/telephony.proto
@@ -273,6 +273,46 @@
     ROAMING_TYPE_INTERNATIONAL = 3;
   }
 
+  // Frequency range
+  enum FrequencyRange {
+    // Unknown. The default value.
+    FREQUENCY_RANGE_UNKNOWN = 0;
+
+    // Frequency range is below 1GHz.
+    FREQUENCY_RANGE_LOW = 1;
+
+    // Frequency range is between 1GHz and 3GHz.
+    FREQUENCY_RANGE_MID = 2;
+
+    // Frequency range is between 3GHz and 6GHz.
+    FREQUENCY_RANGE_HIGH = 3;
+
+    // Frequency range is above 6GHz (millimeter wave frequency).
+    FREQUENCY_RANGE_MMWAVE = 4;
+  }
+
+  // NR (5G) state
+  enum NrState {
+    // The device isn't camped on an LTE cell
+    // or the LTE cell doesn't support EN-DC.
+    NR_STATE_NONE = 0;
+
+    // The device is camped on an LTE cell that supports EN-DC
+    // but either DCNR is restricted
+    // or NR is not supported by the selected PLMN.
+    NR_STATE_RESTRICTED = 1;
+
+    // The device is camped on an LTE cell that supports EN-DC
+    // and both DCNR is not restricted and NR is supported
+    // by the selected PLMN.
+    NR_STATE_NOT_RESTRICTED = 2;
+
+    // The device is camped on an LTE cell that supports EN-DC
+    // and connected to at least one 5G cell
+    // as a secondary serving cell.
+    NR_STATE_CONNECTED = 3;
+  }
+
   // Domain type
   enum Domain {
     // Unknown
@@ -317,6 +357,12 @@
   // Current Channel Number
   optional int32 channel_number = 7;
 
+  // Current NR frequency range
+  optional FrequencyRange nr_frequency_range = 8;
+
+  // Current NR state
+  optional NrState nr_state = 9;
+
   // Network registration info
   repeated NetworkRegistrationInfo networkRegistrationInfo = 10;
 }
@@ -369,9 +415,7 @@
 
   RAT_LTE_CA = 19;
 
-  RAT_NR_NSA = 20;
-
-  RAT_NR_SA = 21;
+  RAT_NR = 20;
 }
 
 // The information about IMS errors
@@ -757,7 +801,7 @@
     // along with the event if it is available
     MODEM_RESTART = 11;
 
-    // System time overwritten by NITZ (Network time)
+    // A system time update suggestion was made from a received NITZ (Network time) signal
     NITZ_TIME = 12;
 
     // Carrier Identification Matching Event
@@ -787,6 +831,9 @@
     // Emergency Number update event (Device HAL >= 1.4).
     EMERGENCY_NUMBER_REPORT = 21;
 
+    // Network capabilities change event.
+    NETWORK_CAPABILITIES_CHANGED = 22;
+
     // Signal strength
     SIGNAL_STRENGTH = 23;
   }
@@ -1691,6 +1738,11 @@
     optional string preferApn = 12;
   }
 
+  message NetworkCapabilitiesInfo {
+    // Is network unmetered
+    optional bool is_network_unmetered = 1;
+  }
+
   // Time when event happened on device, in milliseconds since epoch
   optional int64 timestamp_millis = 1;
 
@@ -1733,7 +1785,7 @@
   // Modem restart event
   optional ModemRestart modem_restart = 14;
 
-  // NITZ time in milliseconds
+  // NITZ time in milliseconds (see TelephonyEvent.Type.NITZ_TIME)
   optional int64 nitz_timestamp_millis = 15;
 
   // Carrier id matching event
@@ -1764,8 +1816,18 @@
   // Updated Emergency Call info.
   optional EmergencyNumberInfo updated_emergency_number = 25;
 
+  // NetworkCapabilities changed info.
+  optional NetworkCapabilitiesInfo network_capabilities = 26;
+
   // Signal strength
   optional int32 signal_strength = 27;
+
+  // Indicate the version of emergency number database in Android platform
+  optional int32 emergency_number_database_version = 28;
+  // [
+  //  (datapol.semantic_type) = ST_SOFTWARE_ID,
+  //  (datapol.qualifier) = { is_public: true }
+  //]
 }
 
 message ActiveSubscriptionInfo {
@@ -1777,6 +1839,14 @@
 
   /** whether subscription is opportunistic (0 - false, 1 - true, -1 - unknown). */
   optional int32 is_opportunistic = 3;
+
+  /** The mccmnc associated with the subscription. Useful for differentiating
+   * between subscriptions with different mccmnc but same carrier_id (eg. Fi
+   * Sprint vs. Fi T-Mobile).*/
+  optional string sim_mccmnc = 4 /*[
+    (datapol.semantic_type) = ST_LOCATION,
+    (datapol.location_qualifier) = { precise_location: false }
+  ]*/;
 };
 
 enum SimState {
@@ -2075,6 +2145,13 @@
 
       // Indicate the emergency call information dialed from the CS call
       optional EmergencyNumberInfo emergency_number_info = 8;
+
+      // Indicate the version of emergency number database in Android platform
+      optional int32 emergency_number_database_version = 9;
+      // [
+      //  (datapol.semantic_type) = ST_SOFTWARE_ID,
+      //  (datapol.qualifier) = { is_public: true }
+      //]
     }
 
     // Single Radio Voice Call Continuity(SRVCC) progress state
@@ -2171,6 +2248,18 @@
 
       // the codec type of an ongoing call
       optional AudioCodec codec_type = 11;
+
+      // true if no incoming RTP is received for a continuous duration of 4 seconds
+      optional bool rtp_inactivity_detected = 12;
+
+      // true if only silence RTP packets are received for 20 seconds immediately
+      // after call is connected
+      optional bool rx_silence_detected = 13;
+
+      // true if only silence RTP packets are sent for 20 seconds immediately
+      // after call is connected
+      optional bool tx_silence_detected = 14;
+
     }
 
     message CallQualitySummary {
@@ -2301,6 +2390,13 @@
 
     // Emergency call info
     optional EmergencyNumberInfo ims_emergency_number_info = 27;
+
+    // Indicate the version of emergency number database in Android platform
+    optional int32 emergency_number_database_version = 28;
+    // [
+    //  (datapol.semantic_type) = ST_SOFTWARE_ID,
+    //  (datapol.qualifier) = { is_public: true }
+    //]
   }
 
   // Time when call has started, in minutes since epoch,
@@ -2515,6 +2611,9 @@
 
     // Indicates if the incoming SMS was blocked
     optional bool blocked = 17;
+
+    // Optional xMS message unique id
+    optional int64 message_id = 18;
   }
 
   // Time when session has started, in minutes since epoch,
diff --git a/src/java/android/telephony/CarrierMessagingServiceManager.java b/src/java/android/telephony/CarrierMessagingServiceManager.java
deleted file mode 100644
index 768d97b..0000000
--- a/src/java/android/telephony/CarrierMessagingServiceManager.java
+++ /dev/null
@@ -1,99 +0,0 @@
-/*
- * Copyright (C) 2014 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 android.telephony;
-
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.content.ServiceConnection;
-import android.os.IBinder;
-import android.service.carrier.CarrierMessagingService;
-import android.service.carrier.ICarrierMessagingService;
-
-import com.android.internal.util.Preconditions;
-
-/**
- * Provides basic structure for platform to connect to the carrier messaging service.
- * <p>
- * <code>
- * CarrierMessagingServiceManager carrierMessagingServiceManager =
- *     new CarrierMessagingServiceManagerImpl();
- * if (carrierMessagingServiceManager.bindToCarrierMessagingService(context, carrierPackageName)) {
- *   // wait for onServiceReady callback
- * } else {
- *   // Unable to bind: handle error.
- * }
- * </code>
- * <p> Upon completion {@link #disposeConnection} should be called to unbind the
- * CarrierMessagingService.
- * @hide
- */
-public abstract class CarrierMessagingServiceManager {
-    // Populated by bindToCarrierMessagingService. bindToCarrierMessagingService must complete
-    // prior to calling disposeConnection so that mCarrierMessagingServiceConnection is initialized.
-    private volatile CarrierMessagingServiceConnection mCarrierMessagingServiceConnection;
-
-    /**
-     * Binds to the carrier messaging service under package {@code carrierPackageName}. This method
-     * should be called exactly once.
-     *
-     * @param context the context
-     * @param carrierPackageName the carrier package name
-     * @return true upon successfully binding to a carrier messaging service, false otherwise
-     */
-    public boolean bindToCarrierMessagingService(Context context, String carrierPackageName) {
-        Preconditions.checkState(mCarrierMessagingServiceConnection == null);
-
-        Intent intent = new Intent(CarrierMessagingService.SERVICE_INTERFACE);
-        intent.setPackage(carrierPackageName);
-        mCarrierMessagingServiceConnection = new CarrierMessagingServiceConnection();
-        return context.bindService(intent, mCarrierMessagingServiceConnection,
-                Context.BIND_AUTO_CREATE);
-    }
-
-    /**
-     * Unbinds the carrier messaging service. This method should be called exactly once.
-     *
-     * @param context the context
-     */
-    public void disposeConnection(Context context) {
-        Preconditions.checkNotNull(mCarrierMessagingServiceConnection);
-        context.unbindService(mCarrierMessagingServiceConnection);
-        mCarrierMessagingServiceConnection = null;
-    }
-
-    /**
-     * Implemented by subclasses to use the carrier messaging service once it is ready.
-     *
-     * @param carrierMessagingService the carrier messaing service interface
-     */
-    protected abstract void onServiceReady(ICarrierMessagingService carrierMessagingService);
-
-    /**
-     * A basic {@link ServiceConnection}.
-     */
-    private final class CarrierMessagingServiceConnection implements ServiceConnection {
-        @Override
-        public void onServiceConnected(ComponentName name, IBinder service) {
-            onServiceReady(ICarrierMessagingService.Stub.asInterface(service));
-        }
-
-        @Override
-        public void onServiceDisconnected(ComponentName name) {
-        }
-    }
-}
diff --git a/src/java/android/telephony/CellBroadcastMessage.java b/src/java/android/telephony/CellBroadcastMessage.java
deleted file mode 100644
index 3cb364d..0000000
--- a/src/java/android/telephony/CellBroadcastMessage.java
+++ /dev/null
@@ -1,452 +0,0 @@
-/*
- * Copyright (C) 2011 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 android.telephony;
-
-import android.annotation.UnsupportedAppUsage;
-import android.content.ContentValues;
-import android.content.Context;
-import android.database.Cursor;
-import android.os.Parcel;
-import android.os.Parcelable;
-import android.provider.Telephony;
-import android.text.format.DateUtils;
-
-/**
- * Application wrapper for {@link SmsCbMessage}. This is Parcelable so that
- * decoded broadcast message objects can be passed between running Services.
- * New broadcasts are received by the CellBroadcastReceiver app, which exports
- * the database of previously received broadcasts at "content://cellbroadcasts/".
- * The "android.permission.READ_CELL_BROADCASTS" permission is required to read
- * from the ContentProvider, and writes to the database are not allowed.<p>
- *
- * Use {@link #createFromCursor} to create CellBroadcastMessage objects from rows
- * in the database cursor returned by the ContentProvider.
- *
- * {@hide}
- */
-public class CellBroadcastMessage implements Parcelable {
-
-    /** Identifier for getExtra() when adding this object to an Intent. */
-    public static final String SMS_CB_MESSAGE_EXTRA =
-            "com.android.cellbroadcastreceiver.SMS_CB_MESSAGE";
-
-    /** SmsCbMessage. */
-    private final SmsCbMessage mSmsCbMessage;
-
-    private final long mDeliveryTime;
-    private boolean mIsRead;
-
-    /**
-     * Indicates the subId
-     *
-     * @hide
-     */
-    private int mSubId = 0;
-
-    /**
-     * set Subscription information
-     *
-     * @hide
-     */
-    public void setSubId(int subId) {
-        mSubId = subId;
-    }
-
-    /**
-     * get Subscription information
-     *
-     * @hide
-     */
-    public int getSubId() {
-        return mSubId;
-    }
-
-    @UnsupportedAppUsage
-    public CellBroadcastMessage(SmsCbMessage message) {
-        mSmsCbMessage = message;
-        mDeliveryTime = System.currentTimeMillis();
-        mIsRead = false;
-    }
-
-    private CellBroadcastMessage(SmsCbMessage message, long deliveryTime, boolean isRead) {
-        mSmsCbMessage = message;
-        mDeliveryTime = deliveryTime;
-        mIsRead = isRead;
-    }
-
-    private CellBroadcastMessage(Parcel in) {
-        mSmsCbMessage = new SmsCbMessage(in);
-        mDeliveryTime = in.readLong();
-        mIsRead = (in.readInt() != 0);
-        mSubId = in.readInt();
-    }
-
-    /** Parcelable: no special flags. */
-    @Override
-    public int describeContents() {
-        return 0;
-    }
-
-    @Override
-    public void writeToParcel(Parcel out, int flags) {
-        mSmsCbMessage.writeToParcel(out, flags);
-        out.writeLong(mDeliveryTime);
-        out.writeInt(mIsRead ? 1 : 0);
-        out.writeInt(mSubId);
-    }
-
-    public static final Parcelable.Creator<CellBroadcastMessage> CREATOR
-            = new Parcelable.Creator<CellBroadcastMessage>() {
-        @Override
-        public CellBroadcastMessage createFromParcel(Parcel in) {
-            return new CellBroadcastMessage(in);
-        }
-
-        @Override
-        public CellBroadcastMessage[] newArray(int size) {
-            return new CellBroadcastMessage[size];
-        }
-    };
-
-    /**
-     * Create a CellBroadcastMessage from a row in the database.
-     * @param cursor an open SQLite cursor pointing to the row to read
-     * @return the new CellBroadcastMessage
-     * @throws IllegalArgumentException if one of the required columns is missing
-     */
-    @UnsupportedAppUsage
-    public static CellBroadcastMessage createFromCursor(Cursor cursor) {
-        int geoScope = cursor.getInt(
-                cursor.getColumnIndexOrThrow(Telephony.CellBroadcasts.GEOGRAPHICAL_SCOPE));
-        int serialNum = cursor.getInt(
-                cursor.getColumnIndexOrThrow(Telephony.CellBroadcasts.SERIAL_NUMBER));
-        int category = cursor.getInt(
-                cursor.getColumnIndexOrThrow(Telephony.CellBroadcasts.SERVICE_CATEGORY));
-        String language = cursor.getString(
-                cursor.getColumnIndexOrThrow(Telephony.CellBroadcasts.LANGUAGE_CODE));
-        String body = cursor.getString(
-                cursor.getColumnIndexOrThrow(Telephony.CellBroadcasts.MESSAGE_BODY));
-        int format = cursor.getInt(
-                cursor.getColumnIndexOrThrow(Telephony.CellBroadcasts.MESSAGE_FORMAT));
-        int priority = cursor.getInt(
-                cursor.getColumnIndexOrThrow(Telephony.CellBroadcasts.MESSAGE_PRIORITY));
-
-        String plmn;
-        int plmnColumn = cursor.getColumnIndex(Telephony.CellBroadcasts.PLMN);
-        if (plmnColumn != -1 && !cursor.isNull(plmnColumn)) {
-            plmn = cursor.getString(plmnColumn);
-        } else {
-            plmn = null;
-        }
-
-        int lac;
-        int lacColumn = cursor.getColumnIndex(Telephony.CellBroadcasts.LAC);
-        if (lacColumn != -1 && !cursor.isNull(lacColumn)) {
-            lac = cursor.getInt(lacColumn);
-        } else {
-            lac = -1;
-        }
-
-        int cid;
-        int cidColumn = cursor.getColumnIndex(Telephony.CellBroadcasts.CID);
-        if (cidColumn != -1 && !cursor.isNull(cidColumn)) {
-            cid = cursor.getInt(cidColumn);
-        } else {
-            cid = -1;
-        }
-
-        SmsCbLocation location = new SmsCbLocation(plmn, lac, cid);
-
-        SmsCbEtwsInfo etwsInfo;
-        int etwsWarningTypeColumn = cursor.getColumnIndex(
-                Telephony.CellBroadcasts.ETWS_WARNING_TYPE);
-        if (etwsWarningTypeColumn != -1 && !cursor.isNull(etwsWarningTypeColumn)) {
-            int warningType = cursor.getInt(etwsWarningTypeColumn);
-            etwsInfo = new SmsCbEtwsInfo(warningType, false, false, false, null);
-        } else {
-            etwsInfo = null;
-        }
-
-        SmsCbCmasInfo cmasInfo;
-        int cmasMessageClassColumn = cursor.getColumnIndex(
-                Telephony.CellBroadcasts.CMAS_MESSAGE_CLASS);
-        if (cmasMessageClassColumn != -1 && !cursor.isNull(cmasMessageClassColumn)) {
-            int messageClass = cursor.getInt(cmasMessageClassColumn);
-
-            int cmasCategory;
-            int cmasCategoryColumn = cursor.getColumnIndex(
-                    Telephony.CellBroadcasts.CMAS_CATEGORY);
-            if (cmasCategoryColumn != -1 && !cursor.isNull(cmasCategoryColumn)) {
-                cmasCategory = cursor.getInt(cmasCategoryColumn);
-            } else {
-                cmasCategory = SmsCbCmasInfo.CMAS_CATEGORY_UNKNOWN;
-            }
-
-            int responseType;
-            int cmasResponseTypeColumn = cursor.getColumnIndex(
-                    Telephony.CellBroadcasts.CMAS_RESPONSE_TYPE);
-            if (cmasResponseTypeColumn != -1 && !cursor.isNull(cmasResponseTypeColumn)) {
-                responseType = cursor.getInt(cmasResponseTypeColumn);
-            } else {
-                responseType = SmsCbCmasInfo.CMAS_RESPONSE_TYPE_UNKNOWN;
-            }
-
-            int severity;
-            int cmasSeverityColumn = cursor.getColumnIndex(
-                    Telephony.CellBroadcasts.CMAS_SEVERITY);
-            if (cmasSeverityColumn != -1 && !cursor.isNull(cmasSeverityColumn)) {
-                severity = cursor.getInt(cmasSeverityColumn);
-            } else {
-                severity = SmsCbCmasInfo.CMAS_SEVERITY_UNKNOWN;
-            }
-
-            int urgency;
-            int cmasUrgencyColumn = cursor.getColumnIndex(
-                    Telephony.CellBroadcasts.CMAS_URGENCY);
-            if (cmasUrgencyColumn != -1 && !cursor.isNull(cmasUrgencyColumn)) {
-                urgency = cursor.getInt(cmasUrgencyColumn);
-            } else {
-                urgency = SmsCbCmasInfo.CMAS_URGENCY_UNKNOWN;
-            }
-
-            int certainty;
-            int cmasCertaintyColumn = cursor.getColumnIndex(
-                    Telephony.CellBroadcasts.CMAS_CERTAINTY);
-            if (cmasCertaintyColumn != -1 && !cursor.isNull(cmasCertaintyColumn)) {
-                certainty = cursor.getInt(cmasCertaintyColumn);
-            } else {
-                certainty = SmsCbCmasInfo.CMAS_CERTAINTY_UNKNOWN;
-            }
-
-            cmasInfo = new SmsCbCmasInfo(messageClass, cmasCategory, responseType, severity,
-                    urgency, certainty);
-        } else {
-            cmasInfo = null;
-        }
-
-        SmsCbMessage msg = new SmsCbMessage(format, geoScope, serialNum, location, category,
-                language, body, priority, etwsInfo, cmasInfo);
-
-        long deliveryTime = cursor.getLong(cursor.getColumnIndexOrThrow(
-                Telephony.CellBroadcasts.DELIVERY_TIME));
-        boolean isRead = (cursor.getInt(cursor.getColumnIndexOrThrow(
-                Telephony.CellBroadcasts.MESSAGE_READ)) != 0);
-
-        return new CellBroadcastMessage(msg, deliveryTime, isRead);
-    }
-
-    /**
-     * Return a ContentValues object for insertion into the database.
-     * @return a new ContentValues object containing this object's data
-     */
-    @UnsupportedAppUsage
-    public ContentValues getContentValues() {
-        ContentValues cv = new ContentValues(16);
-        SmsCbMessage msg = mSmsCbMessage;
-        cv.put(Telephony.CellBroadcasts.GEOGRAPHICAL_SCOPE, msg.getGeographicalScope());
-        SmsCbLocation location = msg.getLocation();
-        if (location.getPlmn() != null) {
-            cv.put(Telephony.CellBroadcasts.PLMN, location.getPlmn());
-        }
-        if (location.getLac() != -1) {
-            cv.put(Telephony.CellBroadcasts.LAC, location.getLac());
-        }
-        if (location.getCid() != -1) {
-            cv.put(Telephony.CellBroadcasts.CID, location.getCid());
-        }
-        cv.put(Telephony.CellBroadcasts.SERIAL_NUMBER, msg.getSerialNumber());
-        cv.put(Telephony.CellBroadcasts.SERVICE_CATEGORY, msg.getServiceCategory());
-        cv.put(Telephony.CellBroadcasts.LANGUAGE_CODE, msg.getLanguageCode());
-        cv.put(Telephony.CellBroadcasts.MESSAGE_BODY, msg.getMessageBody());
-        cv.put(Telephony.CellBroadcasts.DELIVERY_TIME, mDeliveryTime);
-        cv.put(Telephony.CellBroadcasts.MESSAGE_READ, mIsRead);
-        cv.put(Telephony.CellBroadcasts.MESSAGE_FORMAT, msg.getMessageFormat());
-        cv.put(Telephony.CellBroadcasts.MESSAGE_PRIORITY, msg.getMessagePriority());
-
-        SmsCbEtwsInfo etwsInfo = mSmsCbMessage.getEtwsWarningInfo();
-        if (etwsInfo != null) {
-            cv.put(Telephony.CellBroadcasts.ETWS_WARNING_TYPE, etwsInfo.getWarningType());
-        }
-
-        SmsCbCmasInfo cmasInfo = mSmsCbMessage.getCmasWarningInfo();
-        if (cmasInfo != null) {
-            cv.put(Telephony.CellBroadcasts.CMAS_MESSAGE_CLASS, cmasInfo.getMessageClass());
-            cv.put(Telephony.CellBroadcasts.CMAS_CATEGORY, cmasInfo.getCategory());
-            cv.put(Telephony.CellBroadcasts.CMAS_RESPONSE_TYPE, cmasInfo.getResponseType());
-            cv.put(Telephony.CellBroadcasts.CMAS_SEVERITY, cmasInfo.getSeverity());
-            cv.put(Telephony.CellBroadcasts.CMAS_URGENCY, cmasInfo.getUrgency());
-            cv.put(Telephony.CellBroadcasts.CMAS_CERTAINTY, cmasInfo.getCertainty());
-        }
-
-        return cv;
-    }
-
-    /**
-     * Set or clear the "read message" flag.
-     * @param isRead true if the message has been read; false if not
-     */
-    public void setIsRead(boolean isRead) {
-        mIsRead = isRead;
-    }
-
-    @UnsupportedAppUsage
-    public String getLanguageCode() {
-        return mSmsCbMessage.getLanguageCode();
-    }
-
-    @UnsupportedAppUsage
-    public int getServiceCategory() {
-        return mSmsCbMessage.getServiceCategory();
-    }
-
-    @UnsupportedAppUsage
-    public long getDeliveryTime() {
-        return mDeliveryTime;
-    }
-
-    @UnsupportedAppUsage
-    public String getMessageBody() {
-        return mSmsCbMessage.getMessageBody();
-    }
-
-    @UnsupportedAppUsage
-    public boolean isRead() {
-        return mIsRead;
-    }
-
-    @UnsupportedAppUsage
-    public int getSerialNumber() {
-        return mSmsCbMessage.getSerialNumber();
-    }
-
-    public SmsCbCmasInfo getCmasWarningInfo() {
-        return mSmsCbMessage.getCmasWarningInfo();
-    }
-
-    @UnsupportedAppUsage
-    public SmsCbEtwsInfo getEtwsWarningInfo() {
-        return mSmsCbMessage.getEtwsWarningInfo();
-    }
-
-    /**
-     * Return whether the broadcast is an emergency (PWS) message type.
-     * This includes lower priority test messages and Amber alerts.
-     *
-     * All public alerts show the flashing warning icon in the dialog,
-     * but only emergency alerts play the alert sound and speak the message.
-     *
-     * @return true if the message is PWS type; false otherwise
-     */
-    public boolean isPublicAlertMessage() {
-        return mSmsCbMessage.isEmergencyMessage();
-    }
-
-    /**
-     * Returns whether the broadcast is an emergency (PWS) message type,
-     * including test messages and AMBER alerts.
-     *
-     * @return true if the message is PWS type (ETWS or CMAS)
-     */
-    @UnsupportedAppUsage
-    public boolean isEmergencyAlertMessage() {
-        return mSmsCbMessage.isEmergencyMessage();
-    }
-
-    /**
-     * Return whether the broadcast is an ETWS emergency message type.
-     * @return true if the message is ETWS emergency type; false otherwise
-     */
-    @UnsupportedAppUsage
-    public boolean isEtwsMessage() {
-        return mSmsCbMessage.isEtwsMessage();
-    }
-
-    /**
-     * Return whether the broadcast is a CMAS emergency message type.
-     * @return true if the message is CMAS emergency type; false otherwise
-     */
-    @UnsupportedAppUsage
-    public boolean isCmasMessage() {
-        return mSmsCbMessage.isCmasMessage();
-    }
-
-    /**
-     * Return the CMAS message class.
-     * @return the CMAS message class, e.g. {@link SmsCbCmasInfo#CMAS_CLASS_SEVERE_THREAT}, or
-     *  {@link SmsCbCmasInfo#CMAS_CLASS_UNKNOWN} if this is not a CMAS alert
-     */
-    public int getCmasMessageClass() {
-        if (mSmsCbMessage.isCmasMessage()) {
-            return mSmsCbMessage.getCmasWarningInfo().getMessageClass();
-        } else {
-            return SmsCbCmasInfo.CMAS_CLASS_UNKNOWN;
-        }
-    }
-
-    /**
-     * Return whether the broadcast is an ETWS popup alert.
-     * This method checks the message ID and the message code.
-     * @return true if the message indicates an ETWS popup alert
-     */
-    public boolean isEtwsPopupAlert() {
-        SmsCbEtwsInfo etwsInfo = mSmsCbMessage.getEtwsWarningInfo();
-        return etwsInfo != null && etwsInfo.isPopupAlert();
-    }
-
-    /**
-     * Return whether the broadcast is an ETWS emergency user alert.
-     * This method checks the message ID and the message code.
-     * @return true if the message indicates an ETWS emergency user alert
-     */
-    public boolean isEtwsEmergencyUserAlert() {
-        SmsCbEtwsInfo etwsInfo = mSmsCbMessage.getEtwsWarningInfo();
-        return etwsInfo != null && etwsInfo.isEmergencyUserAlert();
-    }
-
-    /**
-     * Return whether the broadcast is an ETWS test message.
-     * @return true if the message is an ETWS test message; false otherwise
-     */
-    public boolean isEtwsTestMessage() {
-        SmsCbEtwsInfo etwsInfo = mSmsCbMessage.getEtwsWarningInfo();
-        return etwsInfo != null &&
-                etwsInfo.getWarningType() == SmsCbEtwsInfo.ETWS_WARNING_TYPE_TEST_MESSAGE;
-    }
-
-    /**
-     * Return the abbreviated date string for the message delivery time.
-     * @param context the context object
-     * @return a String to use in the broadcast list UI
-     */
-    public String getDateString(Context context) {
-        int flags = DateUtils.FORMAT_NO_NOON_MIDNIGHT | DateUtils.FORMAT_SHOW_TIME |
-                DateUtils.FORMAT_ABBREV_ALL | DateUtils.FORMAT_SHOW_DATE |
-                DateUtils.FORMAT_CAP_AMPM;
-        return DateUtils.formatDateTime(context, mDeliveryTime, flags);
-    }
-
-    /**
-     * Return the date string for the message delivery time, suitable for text-to-speech.
-     * @param context the context object
-     * @return a String for populating the list item AccessibilityEvent for TTS
-     */
-    @UnsupportedAppUsage
-    public String getSpokenDateString(Context context) {
-        int flags = DateUtils.FORMAT_SHOW_TIME | DateUtils.FORMAT_SHOW_DATE;
-        return DateUtils.formatDateTime(context, mDeliveryTime, flags);
-    }
-}
diff --git a/src/java/com/android/internal/telephony/AppSmsManager.java b/src/java/com/android/internal/telephony/AppSmsManager.java
index 30de674..f473c9a 100644
--- a/src/java/com/android/internal/telephony/AppSmsManager.java
+++ b/src/java/com/android/internal/telephony/AppSmsManager.java
@@ -20,16 +20,10 @@
 import android.annotation.Nullable;
 import android.app.AppOpsManager;
 import android.app.PendingIntent;
-import android.app.role.IRoleManager;
 import android.content.Context;
 import android.content.Intent;
 import android.os.Binder;
-import android.os.Bundle;
-import android.os.RemoteException;
-import android.os.ServiceManager;
-import android.os.ServiceManager.ServiceNotFoundException;
 import android.provider.Telephony.Sms.Intents;
-import android.telephony.IFinancialSmsCallback;
 import android.telephony.SmsManager;
 import android.telephony.SmsMessage;
 import android.telephony.SubscriptionManager;
@@ -147,24 +141,6 @@
     }
 
     /**
-     * Get filtered SMS messages for financial app.
-     */
-    public void getSmsMessagesForFinancialApp(
-            String callingPkg, Bundle params, final IFinancialSmsCallback callback) {
-        try {
-            IRoleManager roleManager = IRoleManager.Stub.asInterface(
-                    ServiceManager.getServiceOrThrow(Context.ROLE_SERVICE));
-            roleManager.getSmsMessagesForFinancialApp(callingPkg, params, callback);
-        } catch (RemoteException e) {
-            Log.e(LOG_TAG, "Receive RemoteException.");
-            // do nothing
-        } catch (ServiceNotFoundException e) {
-            Log.e(LOG_TAG, "Service not found.");
-            // do nothing
-        }
-    }
-
-    /**
      * Handle an incoming SMS_DELIVER_ACTION intent if it is an app-only SMS.
      */
     public boolean handleSmsReceivedIntent(Intent intent) {
diff --git a/src/java/com/android/internal/telephony/AsyncEmergencyContactNotifier.java b/src/java/com/android/internal/telephony/AsyncEmergencyContactNotifier.java
index 820a052..e129db2 100644
--- a/src/java/com/android/internal/telephony/AsyncEmergencyContactNotifier.java
+++ b/src/java/com/android/internal/telephony/AsyncEmergencyContactNotifier.java
@@ -19,7 +19,8 @@
 import android.content.Context;
 import android.os.AsyncTask;
 import android.provider.BlockedNumberContract;
-import android.telephony.Rlog;
+
+import com.android.telephony.Rlog;
 
 /**
  * An {@link AsyncTask} that notifies the Blocked number provider that emergency services were
diff --git a/src/java/com/android/internal/telephony/BaseCommands.java b/src/java/com/android/internal/telephony/BaseCommands.java
index f72fdcd..24d27f8 100644
--- a/src/java/com/android/internal/telephony/BaseCommands.java
+++ b/src/java/com/android/internal/telephony/BaseCommands.java
@@ -17,14 +17,19 @@
 
 package com.android.internal.telephony;
 
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
 import android.content.Context;
 import android.os.AsyncResult;
 import android.os.Handler;
 import android.os.Message;
 import android.os.Registrant;
 import android.os.RegistrantList;
+import android.telephony.Annotation.RadioPowerState;
 import android.telephony.TelephonyManager;
+import android.telephony.emergency.EmergencyNumber;
+
+import java.util.ArrayList;
+import java.util.List;
 
 /**
  * {@hide}
@@ -100,6 +105,8 @@
     protected RegistrantList mPhysicalChannelConfigurationRegistrants = new RegistrantList();
     protected RegistrantList mLceInfoRegistrants = new RegistrantList();
     protected RegistrantList mEmergencyNumberListRegistrants = new RegistrantList();
+    protected RegistrantList mUiccApplicationsEnablementRegistrants = new RegistrantList();
+    protected RegistrantList mBarringInfoChangedRegistrants = new RegistrantList();
 
     @UnsupportedAppUsage
     protected Registrant mGsmSmsRegistrant;
@@ -139,6 +146,12 @@
     protected Registrant mCatCcAlphaRegistrant;
     @UnsupportedAppUsage
     protected Registrant mSsRegistrant;
+    protected Registrant mRegistrationFailedRegistrant;
+
+    // Lock that mLastEmergencyNumberListIndication uses.
+    private Object mLastEmergencyNumberListIndicationLock = new Object();
+    // Cache last emergency number list indication from radio
+    private final List<EmergencyNumber> mLastEmergencyNumberListIndication = new ArrayList<>();
 
     // Preferred network type received from PhoneFactory.
     // This is used when establishing a connection to the
@@ -160,17 +173,15 @@
     //***** CommandsInterface implementation
 
     @Override
-    public @TelephonyManager.RadioPowerState int getRadioState() {
+    public @RadioPowerState int getRadioState() {
         return mState;
     }
 
     @Override
     public void registerForRadioStateChanged(Handler h, int what, Object obj) {
-        Registrant r = new Registrant (h, what, obj);
-
         synchronized (mStateMonitor) {
-            mRadioStateChangedRegistrants.add(r);
-            r.notifyRegistrant();
+            mRadioStateChangedRegistrants.addUnique(h, what, obj);
+            Message.obtain(h, what, new AsyncResult(obj, null, null)).sendToTarget();
         }
     }
 
@@ -182,8 +193,7 @@
     }
 
     public void registerForImsNetworkStateChanged(Handler h, int what, Object obj) {
-        Registrant r = new Registrant (h, what, obj);
-        mImsNetworkStateChangedRegistrants.add(r);
+        mImsNetworkStateChangedRegistrants.addUnique(h, what, obj);
     }
 
     public void unregisterForImsNetworkStateChanged(Handler h) {
@@ -192,13 +202,11 @@
 
     @Override
     public void registerForOn(Handler h, int what, Object obj) {
-        Registrant r = new Registrant (h, what, obj);
-
         synchronized (mStateMonitor) {
-            mOnRegistrants.add(r);
+            mOnRegistrants.addUnique(h, what, obj);
 
             if (mState == TelephonyManager.RADIO_POWER_ON) {
-                r.notifyRegistrant(new AsyncResult(null, null, null));
+                Message.obtain(h, what, new AsyncResult(obj, null, null)).sendToTarget();
             }
         }
     }
@@ -212,13 +220,11 @@
 
     @Override
     public void registerForAvailable(Handler h, int what, Object obj) {
-        Registrant r = new Registrant (h, what, obj);
-
         synchronized (mStateMonitor) {
-            mAvailRegistrants.add(r);
+            mAvailRegistrants.addUnique(h, what, obj);
 
             if (mState != TelephonyManager.RADIO_POWER_UNAVAILABLE) {
-                r.notifyRegistrant(new AsyncResult(null, null, null));
+                Message.obtain(h, what, new AsyncResult(obj, null, null)).sendToTarget();
             }
         }
     }
@@ -232,13 +238,11 @@
 
     @Override
     public void registerForNotAvailable(Handler h, int what, Object obj) {
-        Registrant r = new Registrant (h, what, obj);
-
         synchronized (mStateMonitor) {
-            mNotAvailRegistrants.add(r);
+            mNotAvailRegistrants.addUnique(h, what, obj);
 
             if (mState == TelephonyManager.RADIO_POWER_UNAVAILABLE) {
-                r.notifyRegistrant(new AsyncResult(null, null, null));
+                Message.obtain(h, what, new AsyncResult(obj, null, null)).sendToTarget();
             }
         }
     }
@@ -252,14 +256,12 @@
 
     @Override
     public void registerForOffOrNotAvailable(Handler h, int what, Object obj) {
-        Registrant r = new Registrant (h, what, obj);
-
         synchronized (mStateMonitor) {
-            mOffOrNotAvailRegistrants.add(r);
+            mOffOrNotAvailRegistrants.addUnique(h, what, obj);
 
             if (mState == TelephonyManager.RADIO_POWER_OFF
                     || mState == TelephonyManager.RADIO_POWER_UNAVAILABLE) {
-                r.notifyRegistrant(new AsyncResult(null, null, null));
+                Message.obtain(h, what, new AsyncResult(obj, null, null)).sendToTarget();
             }
         }
     }
@@ -272,9 +274,7 @@
 
     @Override
     public void registerForCallStateChanged(Handler h, int what, Object obj) {
-        Registrant r = new Registrant (h, what, obj);
-
-        mCallStateRegistrants.add(r);
+        mCallStateRegistrants.addUnique(h, what, obj);
     }
 
     @Override
@@ -284,9 +284,7 @@
 
     @Override
     public void registerForNetworkStateChanged(Handler h, int what, Object obj) {
-        Registrant r = new Registrant (h, what, obj);
-
-        mNetworkStateRegistrants.add(r);
+        mNetworkStateRegistrants.addUnique(h, what, obj);
     }
 
     @Override
@@ -296,9 +294,7 @@
 
     @Override
     public void registerForDataCallListChanged(Handler h, int what, Object obj) {
-        Registrant r = new Registrant (h, what, obj);
-
-        mDataCallListChangedRegistrants.add(r);
+        mDataCallListChangedRegistrants.addUnique(h, what, obj);
     }
 
     @Override
@@ -308,8 +304,7 @@
 
     @Override
     public void registerForVoiceRadioTechChanged(Handler h, int what, Object obj) {
-        Registrant r = new Registrant (h, what, obj);
-        mVoiceRadioTechChangedRegistrants.add(r);
+        mVoiceRadioTechChangedRegistrants.addUnique(h, what, obj);
     }
 
     @Override
@@ -319,8 +314,7 @@
 
     @Override
     public void registerForIccStatusChanged(Handler h, int what, Object obj) {
-        Registrant r = new Registrant (h, what, obj);
-        mIccStatusChangedRegistrants.add(r);
+        mIccStatusChangedRegistrants.addUnique(h, what, obj);
     }
 
     @Override
@@ -330,8 +324,7 @@
 
     @Override
     public void registerForIccSlotStatusChanged(Handler h, int what, Object obj) {
-        Registrant r = new Registrant(h, what, obj);
-        mIccSlotStatusChangedRegistrants.add(r);
+        mIccSlotStatusChangedRegistrants.addUnique(h, what, obj);
     }
 
     @Override
@@ -523,8 +516,7 @@
 
     @Override
     public void registerForIccRefresh(Handler h, int what, Object obj) {
-        Registrant r = new Registrant (h, what, obj);
-        mIccRefreshRegistrants.add(r);
+        mIccRefreshRegistrants.addUnique(h, what, obj);
     }
     @Override
     public void setOnIccRefresh(Handler h, int what, Object obj) {
@@ -579,9 +571,18 @@
     }
 
     @Override
+    public void setOnRegistrationFailed(Handler h, int what, Object obj) {
+        mRegistrationFailedRegistrant = new Registrant(h, what, obj);
+    }
+
+    @Override
+    public void unSetOnRegistrationFailed(Handler h) {
+        mRegistrationFailedRegistrant.clear();
+    }
+
+    @Override
     public void registerForInCallVoicePrivacyOn(Handler h, int what, Object obj) {
-        Registrant r = new Registrant (h, what, obj);
-        mVoicePrivacyOnRegistrants.add(r);
+        mVoicePrivacyOnRegistrants.addUnique(h, what, obj);
     }
 
     @Override
@@ -591,8 +592,7 @@
 
     @Override
     public void registerForInCallVoicePrivacyOff(Handler h, int what, Object obj) {
-        Registrant r = new Registrant (h, what, obj);
-        mVoicePrivacyOffRegistrants.add(r);
+        mVoicePrivacyOffRegistrants.addUnique(h, what, obj);
     }
 
     @Override
@@ -615,8 +615,7 @@
 
     @Override
     public void registerForDisplayInfo(Handler h, int what, Object obj) {
-        Registrant r = new Registrant (h, what, obj);
-        mDisplayInfoRegistrants.add(r);
+        mDisplayInfoRegistrants.addUnique(h, what, obj);
     }
 
     @Override
@@ -626,8 +625,7 @@
 
     @Override
     public void registerForCallWaitingInfo(Handler h, int what, Object obj) {
-        Registrant r = new Registrant (h, what, obj);
-        mCallWaitingInfoRegistrants.add(r);
+        mCallWaitingInfoRegistrants.addUnique(h, what, obj);
     }
 
     @Override
@@ -637,8 +635,7 @@
 
     @Override
     public void registerForSignalInfo(Handler h, int what, Object obj) {
-        Registrant r = new Registrant (h, what, obj);
-        mSignalInfoRegistrants.add(r);
+        mSignalInfoRegistrants.addUnique(h, what, obj);
     }
 
     public void setOnUnsolOemHookRaw(Handler h, int what, Object obj) {
@@ -659,8 +656,7 @@
 
     @Override
     public void registerForCdmaOtaProvision(Handler h,int what, Object obj){
-        Registrant r = new Registrant (h, what, obj);
-        mOtaProvisionRegistrants.add(r);
+        mOtaProvisionRegistrants.addUnique(h, what, obj);
     }
 
     @Override
@@ -670,8 +666,7 @@
 
     @Override
     public void registerForNumberInfo(Handler h,int what, Object obj) {
-        Registrant r = new Registrant (h, what, obj);
-        mNumberInfoRegistrants.add(r);
+        mNumberInfoRegistrants.addUnique(h, what, obj);
     }
 
     @Override
@@ -681,8 +676,7 @@
 
      @Override
     public void registerForRedirectedNumberInfo(Handler h,int what, Object obj) {
-        Registrant r = new Registrant (h, what, obj);
-        mRedirNumInfoRegistrants.add(r);
+        mRedirNumInfoRegistrants.addUnique(h, what, obj);
     }
 
     @Override
@@ -692,8 +686,7 @@
 
     @Override
     public void registerForLineControlInfo(Handler h, int what, Object obj) {
-        Registrant r = new Registrant (h, what, obj);
-        mLineControlInfoRegistrants.add(r);
+        mLineControlInfoRegistrants.addUnique(h, what, obj);
     }
 
     @Override
@@ -703,8 +696,7 @@
 
     @Override
     public void registerFoT53ClirlInfo(Handler h,int what, Object obj) {
-        Registrant r = new Registrant (h, what, obj);
-        mT53ClirInfoRegistrants.add(r);
+        mT53ClirInfoRegistrants.addUnique(h, what, obj);
     }
 
     @Override
@@ -714,8 +706,7 @@
 
     @Override
     public void registerForT53AudioControlInfo(Handler h,int what, Object obj) {
-        Registrant r = new Registrant (h, what, obj);
-        mT53AudCntrlInfoRegistrants.add(r);
+        mT53AudCntrlInfoRegistrants.addUnique(h, what, obj);
     }
 
     @Override
@@ -725,8 +716,7 @@
 
     @Override
     public void registerForRingbackTone(Handler h, int what, Object obj) {
-        Registrant r = new Registrant (h, what, obj);
-        mRingbackToneRegistrants.add(r);
+        mRingbackToneRegistrants.addUnique(h, what, obj);
     }
 
     @Override
@@ -736,8 +726,7 @@
 
     @Override
     public void registerForResendIncallMute(Handler h, int what, Object obj) {
-        Registrant r = new Registrant (h, what, obj);
-        mResendIncallMuteRegistrants.add(r);
+        mResendIncallMuteRegistrants.addUnique(h, what, obj);
     }
 
     @Override
@@ -747,8 +736,7 @@
 
     @Override
     public void registerForCdmaSubscriptionChanged(Handler h, int what, Object obj) {
-        Registrant r = new Registrant (h, what, obj);
-        mCdmaSubscriptionChangedRegistrants.add(r);
+        mCdmaSubscriptionChangedRegistrants.addUnique(h, what, obj);
     }
 
     @Override
@@ -758,8 +746,7 @@
 
     @Override
     public void registerForCdmaPrlChanged(Handler h, int what, Object obj) {
-        Registrant r = new Registrant (h, what, obj);
-        mCdmaPrlChangedRegistrants.add(r);
+        mCdmaPrlChangedRegistrants.addUnique(h, what, obj);
     }
 
     @Override
@@ -769,8 +756,7 @@
 
     @Override
     public void registerForExitEmergencyCallbackMode(Handler h, int what, Object obj) {
-        Registrant r = new Registrant (h, what, obj);
-        mExitEmergencyCallbackModeRegistrants.add(r);
+        mExitEmergencyCallbackModeRegistrants.addUnique(h, what, obj);
     }
 
     @Override
@@ -780,8 +766,7 @@
 
     @Override
     public void registerForHardwareConfigChanged(Handler h, int what, Object obj) {
-        Registrant r = new Registrant (h, what, obj);
-        mHardwareConfigChangeRegistrants.add(r);
+        mHardwareConfigChangeRegistrants.addUnique(h, what, obj);
     }
 
     @Override
@@ -791,8 +776,7 @@
 
     @Override
     public void registerForNetworkScanResult(Handler h, int what, Object obj) {
-        Registrant r = new Registrant(h, what, obj);
-        mRilNetworkScanResultRegistrants.add(r);
+        mRilNetworkScanResultRegistrants.addUnique(h, what, obj);
     }
 
     @Override
@@ -805,10 +789,10 @@
      */
     @Override
     public void registerForRilConnected(Handler h, int what, Object obj) {
-        Registrant r = new Registrant (h, what, obj);
-        mRilConnectedRegistrants.add(r);
+        mRilConnectedRegistrants.addUnique(h, what, obj);
         if (mRilVersion != -1) {
-            r.notifyRegistrant(new AsyncResult(null, new Integer(mRilVersion), null));
+            Message.obtain(h, what, new AsyncResult(obj, new Integer(mRilVersion), null))
+                    .sendToTarget();
         }
     }
 
@@ -818,8 +802,7 @@
     }
 
     public void registerForSubscriptionStatusChanged(Handler h, int what, Object obj) {
-        Registrant r = new Registrant (h, what, obj);
-        mSubscriptionStatusRegistrants.add(r);
+        mSubscriptionStatusRegistrants.addUnique(h, what, obj);
     }
 
     public void unregisterForSubscriptionStatusChanged(Handler h) {
@@ -828,8 +811,15 @@
 
     @Override
     public void registerForEmergencyNumberList(Handler h, int what, Object obj) {
-        Registrant r = new Registrant(h, what, obj);
-        mEmergencyNumberListRegistrants.add(r);
+        mEmergencyNumberListRegistrants.addUnique(h, what, obj);
+        // Notify the last emergency number list from radio to new registrants because they may
+        // miss the latest indication (e.g. constructed in a delay after HAL is registrated).
+        List<EmergencyNumber> lastEmergencyNumberListIndication =
+                getLastEmergencyNumberListIndication();
+        if (lastEmergencyNumberListIndication != null) {
+            mEmergencyNumberListRegistrants.notifyRegistrants(new AsyncResult(
+                    null, lastEmergencyNumberListIndication, null));
+        }
     }
 
     @Override
@@ -887,6 +877,20 @@
         }
     }
 
+    protected void cacheEmergencyNumberListIndication(
+            List<EmergencyNumber> emergencyNumberListIndication) {
+        synchronized (mLastEmergencyNumberListIndicationLock) {
+            mLastEmergencyNumberListIndication.clear();
+            mLastEmergencyNumberListIndication.addAll(emergencyNumberListIndication);
+        }
+    }
+
+    private List<EmergencyNumber> getLastEmergencyNumberListIndication() {
+        synchronized (mLastEmergencyNumberListIndicationLock) {
+            return new ArrayList<>(mLastEmergencyNumberListIndication);
+        }
+    }
+
     /**
      * {@inheritDoc}
      */
@@ -900,8 +904,7 @@
      */
     @Override
     public void registerForCellInfoList(Handler h, int what, Object obj) {
-        Registrant r = new Registrant (h, what, obj);
-        mRilCellInfoListRegistrants.add(r);
+        mRilCellInfoListRegistrants.addUnique(h, what, obj);
     }
     @Override
     public void unregisterForCellInfoList(Handler h) {
@@ -910,8 +913,7 @@
 
     @Override
     public void registerForPhysicalChannelConfiguration(Handler h, int what, Object obj) {
-        Registrant r = new Registrant(h, what, obj);
-        mPhysicalChannelConfigurationRegistrants.add(r);
+        mPhysicalChannelConfigurationRegistrants.addUnique(h, what, obj);
     }
 
     @Override
@@ -921,9 +923,7 @@
 
     @Override
     public void registerForSrvccStateChanged(Handler h, int what, Object obj) {
-        Registrant r = new Registrant (h, what, obj);
-
-        mSrvccStateRegistrants.add(r);
+        mSrvccStateRegistrants.addUnique(h, what, obj);
     }
 
     @Override
@@ -960,8 +960,7 @@
 
     @Override
     public void registerForRadioCapabilityChanged(Handler h, int what, Object obj) {
-        Registrant r = new Registrant(h, what, obj);
-        mPhoneRadioCapabilityChangedRegistrants.add(r);
+        mPhoneRadioCapabilityChangedRegistrants.addUnique(h, what, obj);
     }
 
     @Override
@@ -983,10 +982,8 @@
 
     @Override
     public void registerForLceInfo(Handler h, int what, Object obj) {
-        Registrant r = new Registrant(h, what, obj);
-
         synchronized (mStateMonitor) {
-            mLceInfoRegistrants.add(r);
+            mLceInfoRegistrants.addUnique(h, what, obj);
         }
     }
 
@@ -999,7 +996,7 @@
 
     @Override
     public void registerForModemReset(Handler h, int what, Object obj) {
-        mModemResetRegistrants.add(new Registrant(h, what, obj));
+        mModemResetRegistrants.addUnique(h, what, obj);
     }
 
     @Override
@@ -1009,7 +1006,7 @@
 
     @Override
     public void registerForPcoData(Handler h, int what, Object obj) {
-        mPcoDataRegistrants.add(new Registrant(h, what, obj));
+        mPcoDataRegistrants.addUnique(h, what, obj);
     }
 
     @Override
@@ -1019,7 +1016,7 @@
 
     @Override
     public void registerForCarrierInfoForImsiEncryption(Handler h, int what, Object obj) {
-        mCarrierInfoForImsiEncryptionRegistrants.add(new Registrant(h, what, obj));
+        mCarrierInfoForImsiEncryptionRegistrants.addUnique(h, what, obj);
     }
 
     @Override
@@ -1029,10 +1026,8 @@
 
     @Override
     public void registerForNattKeepaliveStatus(Handler h, int what, Object obj) {
-        Registrant r = new Registrant(h, what, obj);
-
         synchronized (mStateMonitor) {
-            mNattKeepaliveStatusRegistrants.add(r);
+            mNattKeepaliveStatusRegistrants.addUnique(h, what, obj);
         }
     }
 
@@ -1042,4 +1037,48 @@
             mNattKeepaliveStatusRegistrants.remove(h);
         }
     }
+
+    /**
+     * Registers the handler for RIL_UNSOL_UICC_APPLICATIONS_ENABLEMENT_CHANGED events.
+     *
+     * @param h Handler for notification message.
+     * @param what User-defined message code.
+     * @param obj User object.
+     */
+    @Override
+    public void registerUiccApplicationEnablementChanged(Handler h, int what, Object obj) {
+        mUiccApplicationsEnablementRegistrants.addUnique(h, what, obj);
+    }
+
+    /**
+     * Unregisters the handler for RIL_UNSOL_UICC_APPLICATIONS_ENABLEMENT_CHANGED events.
+     *
+     * @param h Handler for notification message.
+     */
+    @Override
+    public void unregisterUiccApplicationEnablementChanged(Handler h) {
+        mUiccApplicationsEnablementRegistrants.remove(h);
+    }
+
+    /**
+     * Registers the handler for RIL_UNSOL_BARRING_INFO_CHANGED events.
+     *
+     * @param h Handler for notification message.
+     * @param what User-defined message code.
+     * @param obj User object.
+     */
+    @Override
+    public void registerForBarringInfoChanged(Handler h, int what, Object obj) {
+        mBarringInfoChangedRegistrants.addUnique(h, what, obj);
+    }
+
+    /**
+     * Unregisters the handler for RIL_UNSOL_BARRING_INFO_CHANGED events.
+     *
+     * @param h Handler for notification message.
+     */
+    @Override
+    public void unregisterForBarringInfoChanged(Handler h) {
+        mBarringInfoChangedRegistrants.remove(h);
+    }
 }
diff --git a/src/java/com/android/internal/telephony/BlockChecker.java b/src/java/com/android/internal/telephony/BlockChecker.java
index 1a63db6..cf7ae4d 100644
--- a/src/java/com/android/internal/telephony/BlockChecker.java
+++ b/src/java/com/android/internal/telephony/BlockChecker.java
@@ -3,7 +3,8 @@
 import android.content.Context;
 import android.os.Bundle;
 import android.provider.BlockedNumberContract;
-import android.telephony.Rlog;
+
+import com.android.telephony.Rlog;
 
 /**
  * {@hide} Checks for blocked phone numbers against {@link BlockedNumberContract}
@@ -75,7 +76,6 @@
         } catch (Exception e) {
             Rlog.e(TAG, "Exception checking for blocked number: " + e);
         }
-
         int durationMillis = (int) ((System.nanoTime() - startTimeNano) / 1000000);
         if (durationMillis > 500 || VDBG) {
             Rlog.d(TAG, "Blocked number lookup took: " + durationMillis + " ms.");
diff --git a/src/java/com/android/internal/telephony/BtSmsInterfaceManager.java b/src/java/com/android/internal/telephony/BtSmsInterfaceManager.java
index 408d7ab..7271bf3 100644
--- a/src/java/com/android/internal/telephony/BtSmsInterfaceManager.java
+++ b/src/java/com/android/internal/telephony/BtSmsInterfaceManager.java
@@ -17,12 +17,10 @@
 
 package com.android.internal.telephony;
 
-import android.app.ActivityThread;
 import android.app.PendingIntent;
-import android.bluetooth.BluetoothAdapter;
 import android.bluetooth.BluetoothDevice;
-import android.bluetooth.BluetoothMapClient;
 import android.bluetooth.BluetoothProfile;
+import android.content.Context;
 import android.net.Uri;
 import android.telecom.PhoneAccount;
 import android.telephony.SmsManager;
@@ -40,23 +38,29 @@
     /**
      * Sends text through connected Bluetooth device
      */
-    public void sendText(String destAddr, String text, PendingIntent sentIntent,
+    public void sendText(Context context, String destAddr, String text, PendingIntent sentIntent,
             PendingIntent deliveryIntent, SubscriptionInfo info) {
+        /*
+        This is to remove the usage of hidden constant MAP_CLIENT and hidden API
+        BluetoothMapClient.sendMessage(). This code is currently not functional anyway; it will be
+        re-enabled in a later release.
         BluetoothAdapter btAdapter = BluetoothAdapter.getDefaultAdapter();
         if (btAdapter == null) {
             // No bluetooth service on this platform?
-            sendErrorInPendingIntent(sentIntent, SmsManager.RESULT_ERROR_NO_SERVICE);
+            sendErrorInPendingIntent(sentIntent, SmsManager.RESULT_NO_BLUETOOTH_SERVICE);
             return;
         }
         BluetoothDevice device = btAdapter.getRemoteDevice(info.getIccId());
         if (device == null) {
             Log.d(LOG_TAG, "Bluetooth device addr invalid: " + info.getIccId());
-            sendErrorInPendingIntent(sentIntent, SmsManager.RESULT_ERROR_NO_SERVICE);
+            sendErrorInPendingIntent(sentIntent, SmsManager.RESULT_INVALID_BLUETOOTH_ADDRESS);
             return;
         }
-        btAdapter.getProfileProxy(ActivityThread.currentApplication().getApplicationContext(),
+        btAdapter.getProfileProxy(context.getApplicationContext(),
                 new MapMessageSender(destAddr, text, device, sentIntent, deliveryIntent),
                 BluetoothProfile.MAP_CLIENT);
+        */
+        throw new RuntimeException("Can't send message through BluetoothMapClient");
     }
 
     private void sendErrorInPendingIntent(PendingIntent intent, int errorCode) {
@@ -96,6 +100,7 @@
         @Override
         public void onServiceConnected(int profile, BluetoothProfile proxy) {
             Log.d(LOG_TAG, "Service connected");
+            /*
             if (profile != BluetoothProfile.MAP_CLIENT) {
                 return;
             }
@@ -107,13 +112,15 @@
             }
             BluetoothAdapter.getDefaultAdapter()
                     .closeProfileProxy(BluetoothProfile.MAP_CLIENT, mapProfile);
+            */
+            throw new RuntimeException("Can't send message through BluetoothMapClient");
         }
 
         @Override
         public void onServiceDisconnected(int profile) {
             if (mMessage != null) {
                 Log.d(LOG_TAG, "Bluetooth disconnected before sending the message");
-                sendErrorInPendingIntent(mSentIntent, SmsManager.RESULT_ERROR_NO_SERVICE);
+                sendErrorInPendingIntent(mSentIntent, SmsManager.RESULT_BLUETOOTH_DISCONNECTED);
                 mMessage = null;
             }
         }
diff --git a/src/java/com/android/internal/telephony/Call.java b/src/java/com/android/internal/telephony/Call.java
index 37fefd9..50f65d2 100644
--- a/src/java/com/android/internal/telephony/Call.java
+++ b/src/java/com/android/internal/telephony/Call.java
@@ -16,25 +16,36 @@
 
 package com.android.internal.telephony;
 
-import android.telecom.ConferenceParticipant;
+import android.compat.annotation.UnsupportedAppUsage;
+
+import com.android.ims.internal.ConferenceParticipant;
+import com.android.telephony.Rlog;
 
 import java.util.ArrayList;
 import java.util.List;
 
-import android.annotation.UnsupportedAppUsage;
-import android.telephony.Rlog;
-
 /**
  * {@hide}
  */
 public abstract class Call {
     protected final String LOG_TAG = "Call";
 
-    /* Enums */
+    @UnsupportedAppUsage
+    public Call() {
+    }
 
+    /* Enums */
+    @UnsupportedAppUsage(implicitMember = "values()[Lcom/android/internal/telephony/Call$State;")
     public enum State {
-        @UnsupportedAppUsage
-        IDLE, ACTIVE, HOLDING, DIALING, ALERTING, INCOMING, WAITING, DISCONNECTED, DISCONNECTING;
+        @UnsupportedAppUsage IDLE,
+        ACTIVE,
+        @UnsupportedAppUsage HOLDING,
+        @UnsupportedAppUsage DIALING,
+        @UnsupportedAppUsage ALERTING,
+        @UnsupportedAppUsage INCOMING,
+        @UnsupportedAppUsage WAITING,
+        @UnsupportedAppUsage DISCONNECTED,
+        @UnsupportedAppUsage DISCONNECTING;
 
         @UnsupportedAppUsage
         public boolean isAlive() {
@@ -74,7 +85,9 @@
     public State mState = State.IDLE;
 
     @UnsupportedAppUsage
-    public ArrayList<Connection> mConnections = new ArrayList<Connection>();
+    public ArrayList<Connection> mConnections = new ArrayList<>();
+
+    private Object mLock = new Object();
 
     /* Instance Methods */
 
@@ -83,7 +96,30 @@
      */
 
     @UnsupportedAppUsage
-    public abstract List<Connection> getConnections();
+    public ArrayList<Connection> getConnections() {
+        synchronized (mLock) {
+            return (ArrayList<Connection>) mConnections.clone();
+        }
+    }
+
+    /**
+     * Get mConnections field from another Call instance.
+     * @param other
+     */
+    public void copyConnectionFrom(Call other) {
+        mConnections = other.getConnections();
+    }
+
+    /**
+     * Get connections count of this instance.
+     * @return the count to return
+     */
+    public int getConnectionsCount() {
+        synchronized (mLock) {
+            return mConnections.size();
+        }
+    }
+
     @UnsupportedAppUsage
     public abstract Phone getPhone();
     @UnsupportedAppUsage
@@ -91,6 +127,8 @@
     @UnsupportedAppUsage
     public abstract void hangup() throws CallStateException;
 
+    public abstract void hangup(@android.telecom.Call.RejectReason int rejectReason)
+            throws CallStateException;
 
     /**
      * hasConnection
@@ -117,6 +155,37 @@
     }
 
     /**
+     * removeConnection
+     *
+     * @param conn the connection to be removed
+     */
+    public void removeConnection(Connection conn) {
+        synchronized (mLock) {
+            mConnections.remove(conn);
+        }
+    }
+
+    /**
+     * addConnection
+     *
+     * @param conn the connection to be added
+     */
+    public void addConnection(Connection conn) {
+        synchronized (mLock) {
+            mConnections.add(conn);
+        }
+    }
+
+    /**
+     * clearConnection
+     */
+    public void clearConnections() {
+        synchronized (mLock) {
+            mConnections.clear();
+        }
+    }
+
+    /**
      * getState
      * @return state of class call
      */
@@ -276,14 +345,13 @@
      * Called when it's time to clean up disconnected Connection objects
      */
     public void clearDisconnected() {
-        for (int i = mConnections.size() - 1 ; i >= 0 ; i--) {
-            Connection c = mConnections.get(i);
-            if (c.getState() == State.DISCONNECTED) {
-                mConnections.remove(i);
+        for (Connection conn : getConnections()) {
+            if (conn.getState() == State.DISCONNECTED) {
+                removeConnection(conn);
             }
         }
 
-        if (mConnections.size() == 0) {
+        if (getConnectionsCount() == 0) {
             setState(State.IDLE);
         }
     }
diff --git a/src/java/com/android/internal/telephony/CallFailCause.java b/src/java/com/android/internal/telephony/CallFailCause.java
index 6e4e01c..206eaf3 100644
--- a/src/java/com/android/internal/telephony/CallFailCause.java
+++ b/src/java/com/android/internal/telephony/CallFailCause.java
@@ -149,9 +149,43 @@
     int DIAL_MODIFIED_TO_SS   = 245;
     int DIAL_MODIFIED_TO_DIAL = 246;
 
+    // The call cannot be established because RADIO is OFF
+    int RADIO_OFF = 247;
+
+    // The call cannot be established because of no valid SIM
+    int NO_VALID_SIM = 249;
+
+    // The call is dropped or failed internally by modem
+    int RADIO_INTERNAL_ERROR = 250;
+
+    // Call failed because of UE timer expired while waiting for a response from network
+    int NETWORK_RESP_TIMEOUT = 251;
+
+    // Call failed because of a network reject
+    int NETWORK_REJECT                                   = 252;
+    // Call failed because of radio access failure. ex. RACH failure
+    int RADIO_ACCESS_FAILURE                             = 253;
+    // Call failed/dropped because of a Radio Link Failure (RLF).
+    int RADIO_LINK_FAILURE                               = 254;
+    // Call failed/dropped because of radio link lost
+    int RADIO_LINK_LOST                                  = 255;
+    // Call failed because of a radio uplink issue
+    int RADIO_UPLINK_FAILURE                             = 256;
+    // Call failed because of a RRC (Radio Resource Control) connection setup failure
+    int RADIO_SETUP_FAILURE                              = 257;
+    // Call failed/dropped because of RRC (Radio Resource Control) connection release from NW
+    int RADIO_RELEASE_NORMAL                             = 258;
+    /**
+     * Call failed/dropped because of RRC (Radio Resource Control) abnormally released by
+     * modem/network.
+     */
+    int RADIO_RELEASE_ABNORMAL                           = 259;
     //Access class blocked - TS 31.121 5.2.1
     int ACCESS_CLASS_BLOCKED = 260;
 
+    /** Call failed/dropped because of a network detach. */
+    int NETWORK_DETACH                                   = 261;
+
     //Emergency Redial
     int EMERGENCY_TEMP_FAILURE = 325;
     int EMERGENCY_PERM_FAILURE = 326;
@@ -171,6 +205,269 @@
     // Access Blocked by CDMA Network.
     int CDMA_ACCESS_BLOCKED            = 1009;
 
+    /** Mapped from ImsReasonInfo */
+    // The passed argument is an invalid
+    int LOCAL_ILLEGAL_ARGUMENT                           = 1200;
+    // The operation is invoked in invalid call state
+    int LOCAL_ILLEGAL_STATE                              = 1201;
+    // IMS service internal error
+    int LOCAL_INTERNAL_ERROR                             = 1202;
+    // IMS service goes down (service connection is lost)
+    int LOCAL_IMS_SERVICE_DOWN                           = 1203;
+    // No pending incoming call exists
+    int LOCAL_NO_PENDING_CALL                            = 1204;
+    // Service unavailable; by power off
+    int LOCAL_POWER_OFF                                  = 1205;
+    // Service unavailable; by low battery
+    int LOCAL_LOW_BATTERY                                = 1206;
+    // Service unavailable; by out of service (data service state)
+    int LOCAL_NETWORK_NO_SERVICE                         = 1207;
+
+    /* Service unavailable; by no LTE coverage
+     * (VoLTE is not supported even though IMS is registered)
+     */
+    int LOCAL_NETWORK_NO_LTE_COVERAGE                    = 1208;
+    // Service unavailable; by located in roaming area
+    int LOCAL_NETWORK_ROAMING                            = 1209;
+    // Service unavailable; by IP changed
+    int LOCAL_NETWORK_IP_CHANGED                         = 1210;
+    // Service unavailable; other
+    int LOCAL_SERVICE_UNAVAILABLE                        = 1211;
+    // Service unavailable; IMS connection is lost (IMS is not registered)
+    int LOCAL_NOT_REGISTERED                             = 1212;
+    // Max call exceeded
+    int LOCAL_MAX_CALL_EXCEEDED                          = 1213;
+    // Call decline
+    int LOCAL_CALL_DECLINE                               = 1214;
+    // SRVCC is in progress
+    int LOCAL_CALL_VCC_ON_PROGRESSING                    = 1215;
+    // Resource reservation is failed (QoS precondition)
+    int LOCAL_CALL_RESOURCE_RESERVATION_FAILED           = 1216;
+    /** Retry CS call; VoLTE service can't be provided by the network or remote end
+     *  Resolve the extra code(EXTRA_CODE_CALL_RETRY_*) if the below code is set
+     */
+    int LOCAL_CALL_CS_RETRY_REQUIRED                     = 1217;
+    // Retry VoLTE call; VoLTE service can't be provided by the network temporarily
+    int LOCAL_CALL_VOLTE_RETRY_REQUIRED                  = 1218;
+    // IMS call is already terminated (in TERMINATED state)
+    int LOCAL_CALL_TERMINATED                            = 1219;
+    // Handover not feasible
+    int LOCAL_HO_NOT_FEASIBLE                            = 1220;
+
+    // 1xx waiting timer is expired after sending INVITE request (MO only)
+    int TIMEOUT_1XX_WAITING                              = 1221;
+    /** User no answer during call setup operation (MO/MT)
+     *  MO : 200 OK to INVITE request is not received,
+     *  MT : No action from user after alerting the call
+     */
+    int TIMEOUT_NO_ANSWER                                = 1222;
+    /** User no answer during call update operation (MO/MT)
+     *  MO : 200 OK to re-INVITE request is not received,
+     *  MT : No action from user after alerting the call
+     */
+    int TIMEOUT_NO_ANSWER_CALL_UPDATE                    = 1223;
+
+    /**
+     * STATUSCODE (SIP response code) (IMS -> Telephony)
+     */
+    // SIP request is redirected
+    int SIP_REDIRECTED                                   = 1300;
+    /** 4xx responses */
+    // 400 : Bad Request
+    int SIP_BAD_REQUEST                                  = 1310;
+    // 403 : Forbidden
+    int SIP_FORBIDDEN                                    = 1311;
+    // 404 : Not Found
+    int SIP_NOT_FOUND                                    = 1312;
+    /** 415 : Unsupported Media Type
+     *  416 : Unsupported URI Scheme
+     *  420 : Bad Extension
+     */
+    int SIP_NOT_SUPPORTED                                = 1313;
+    // 408 : Request Timeout
+    int SIP_REQUEST_TIMEOUT                              = 1314;
+    // 480 : Temporarily Unavailable
+    int SIP_TEMPRARILY_UNAVAILABLE                       = 1315;
+    // 484 : Address Incomplete
+    int SIP_BAD_ADDRESS                                  = 1316;
+    /** 486 : Busy Here
+     *  600 : Busy Everywhere
+     */
+    int SIP_BUSY                                         = 1317;
+    // 487 : Request Terminated
+    int SIP_REQUEST_CANCELLED                            = 1318;
+    /** 406 : Not Acceptable
+     *  488 : Not Acceptable Here
+     *  606 : Not Acceptable
+     */
+    int SIP_NOT_ACCEPTABLE                               = 1319;
+    /** 410 : Gone
+     *  604 : Does Not Exist Anywhere
+     */
+    int SIP_NOT_REACHABLE                                = 1320;
+    // Others
+    int SIP_CLIENT_ERROR                                 = 1321;
+    // 481 : Transaction Does Not Exist
+    int SIP_TRANSACTION_DOES_NOT_EXIST                   = 1322;
+    /** 5xx responses
+     *  501 : Server Internal Error
+     */
+    int SIP_SERVER_INTERNAL_ERROR                        = 1330;
+    // 503 : Service Unavailable
+    int SIP_SERVICE_UNAVAILABLE                          = 1331;
+    // 504 : Server Time-out
+    int SIP_SERVER_TIMEOUT                               = 1332;
+    // Other
+    int SIP_SERVER_ERROR                                 = 1333;
+    /** 6xx responses
+     *  603 : Decline
+     */
+    int SIP_USER_REJECTED                                = 1340;
+    // Others
+    int SIP_GLOBAL_ERROR                                 = 1341;
+    // Emergency failure
+    int IMS_EMERGENCY_TEMP_FAILURE                       = 1342;
+    int IMS_EMERGENCY_PERM_FAILURE                       = 1343;
+    // Media resource initialization failed
+    int MEDIA_INIT_FAILED                                = 1400;
+    // RTP timeout (no audio / video traffic in the session)
+    int MEDIA_NO_DATA                                    = 1401;
+    // Media is not supported; so dropped the call
+    int MEDIA_NOT_ACCEPTABLE                             = 1402;
+    // Unknown media related errors
+    int MEDIA_UNSPECIFIED                                = 1403;
+    // User triggers the call end
+    int USER_TERMINATED                                  = 1500;
+    // No action while an incoming call is ringing
+    int USER_NOANSWER                                    = 1501;
+    // User ignores an incoming call
+    int USER_IGNORE                                      = 1502;
+    // User declines an incoming call
+    int USER_DECLINE                                     = 1503;
+    // Device declines/ends a call due to low battery
+    int LOW_BATTERY                                      = 1504;
+    // Device declines call due to blacklisted call ID
+    int BLACKLISTED_CALL_ID                              = 1505;
+    // The call is terminated by the network or remote user
+    int USER_TERMINATED_BY_REMOTE                        = 1510;
+
+    /**
+     * UT
+     */
+    int UT_NOT_SUPPORTED                                 = 1800;
+    int UT_SERVICE_UNAVAILABLE                           = 1801;
+    int UT_OPERATION_NOT_ALLOWED                         = 1802;
+    int UT_NETWORK_ERROR                                 = 1803;
+    int UT_CB_PASSWORD_MISMATCH                          = 1804;
+
+    /**
+     * ECBM
+     */
+    int ECBM_NOT_SUPPORTED                               = 1900;
+
+    /**
+     * Fail code used to indicate that Multi-endpoint is not supported by the Ims framework.
+     */
+    int MULTIENDPOINT_NOT_SUPPORTED                      = 1901;
+
+    /**
+     * CALL DROP error codes (Call could drop because of many reasons like Network not available,
+     *  handover, failed, etc)
+     */
+
+    /**
+     * CALL DROP error code for the case when a device is ePDG capable and when the user is on an
+     * active wifi call and at the edge of coverage and there is no qualified LTE network available
+     * to handover the call to. We get a handover NOT_TRIGERRED message from the modem. This error
+     * code is received as part of the handover message.
+     */
+    int CALL_DROP_IWLAN_TO_LTE_UNAVAILABLE               = 2000;
+
+    // MT call has ended due to a release from the network because the call was answered elsewhere
+    int ANSWERED_ELSEWHERE                               = 2100;
+
+    // For MultiEndpoint - Call Pull request has failed
+    int CALL_PULL_OUT_OF_SYNC                            = 2101;
+
+    // For MultiEndpoint - Call has been pulled from primary to secondary
+    int CALL_PULLED                                      = 2102;
+
+    /**
+     * Supplementary services (HOLD/RESUME) failure error codes.
+     * Values for Supplemetary services failure - Failed, Cancelled and Re-Invite collision.
+     */
+    int SUPP_SVC_FAILED                                  = 2300;
+    int SUPP_SVC_CANCELLED                               = 2301;
+    int SUPP_SVC_REINVITE_COLLISION                      = 2302;
+
+    //DPD Procedure received no response or send failed
+    int IWLAN_DPD_FAILURE                                = 2400;
+
+    // Establishment of the ePDG Tunnel Failed
+    int EPDG_TUNNEL_ESTABLISH_FAILURE                    = 2500;
+
+    // Re-keying of the ePDG Tunnel Failed; may not always result in teardown
+    int EPDG_TUNNEL_REKEY_FAILURE                        = 2501;
+
+    // Connection to the packet gateway is lost
+    int EPDG_TUNNEL_LOST_CONNECTION                      = 2502;
+
+    /**
+     * The maximum number of calls allowed has been reached.  Used in a multi-endpoint scenario
+     * where the number of calls across all connected devices has reached the maximum.
+     */
+    int MAXIMUM_NUMBER_OF_CALLS_REACHED                  = 2503;
+
+    /**
+     * Similar to {@link #CODE_LOCAL_CALL_DECLINE}, except indicates that a remote device has
+     * declined the call.  Used in a multi-endpoint scenario where a remote device declined an
+     * incoming call.
+     */
+    int REMOTE_CALL_DECLINE                              = 2504;
+
+    /**
+     * Indicates the call was disconnected due to the user reaching their data limit.
+     */
+    int DATA_LIMIT_REACHED                               = 2505;
+
+    /**
+     * Indicates the call was disconnected due to the user disabling cellular data.
+     */
+    int DATA_DISABLED                                    = 2506;
+
+    /**
+     * Indicates a call was disconnected due to loss of wifi signal.
+     */
+    int WIFI_LOST                                        = 2507;
+
+    /**
+     * Indicates a call was disconnected because retry over volte is needed.
+     */
+    int EMC_REDIAL_ON_IMS                                = 3001;
+
+    /**
+     * Indicates a call was disconnected because retry over vowifi is needed.
+     */
+    int EMC_REDIAL_ON_VOWIFI                             = 3002;
+
+    /* OEM specific error codes. To be used by OEMs when they don't want to
+       reveal error code which would be replaced by ERROR_UNSPECIFIED */
+    int OEM_CAUSE_1                                      = 0xf001;
+    int OEM_CAUSE_2                                      = 0xf002;
+    int OEM_CAUSE_3                                      = 0xf003;
+    int OEM_CAUSE_4                                      = 0xf004;
+    int OEM_CAUSE_5                                      = 0xf005;
+    int OEM_CAUSE_6                                      = 0xf006;
+    int OEM_CAUSE_7                                      = 0xf007;
+    int OEM_CAUSE_8                                      = 0xf008;
+    int OEM_CAUSE_9                                      = 0xf009;
+    int OEM_CAUSE_10                                     = 0xf00a;
+    int OEM_CAUSE_11                                     = 0xf00b;
+    int OEM_CAUSE_12                                     = 0xf00c;
+    int OEM_CAUSE_13                                     = 0xf00d;
+    int OEM_CAUSE_14                                     = 0xf00e;
+    int OEM_CAUSE_15                                     = 0xf00f;
+
     int ERROR_UNSPECIFIED = 0xffff;
 
 }
diff --git a/src/java/com/android/internal/telephony/CallForwardInfo.java b/src/java/com/android/internal/telephony/CallForwardInfo.java
index ea3ba27..2a8319b 100644
--- a/src/java/com/android/internal/telephony/CallForwardInfo.java
+++ b/src/java/com/android/internal/telephony/CallForwardInfo.java
@@ -16,8 +16,9 @@
 
 package com.android.internal.telephony;
 
-import android.annotation.UnsupportedAppUsage;
-import android.telecom.Log;
+import android.compat.annotation.UnsupportedAppUsage;
+
+import com.android.telephony.Rlog;
 
 /**
  * See also RIL_CallForwardInfo in include/telephony/ril.h
@@ -28,6 +29,10 @@
     private static final String TAG = "CallForwardInfo";
 
     @UnsupportedAppUsage
+    public CallForwardInfo() {
+    }
+
+    @UnsupportedAppUsage
     public int             status;      /*1 = active, 0 = not active */
     @UnsupportedAppUsage
     public int             reason;      /* from TS 27.007 7.11 "reason" */
@@ -45,6 +50,6 @@
         return "[CallForwardInfo: status=" + (status == 0 ? " not active " : " active ")
                 + ", reason= " + reason
                 + ", serviceClass= " + serviceClass + ", timeSec= " + timeSeconds + " seconds"
-                + ", number=" + Log.pii(number) + "]";
+                + ", number=" + Rlog.pii(TAG, number) + "]";
     }
 }
diff --git a/src/java/com/android/internal/telephony/CallManager.java b/src/java/com/android/internal/telephony/CallManager.java
index 5112376..151a4bf 100644
--- a/src/java/com/android/internal/telephony/CallManager.java
+++ b/src/java/com/android/internal/telephony/CallManager.java
@@ -16,7 +16,7 @@
 
 package com.android.internal.telephony;
 
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
 import android.content.Context;
 import android.os.AsyncResult;
 import android.os.Handler;
@@ -25,19 +25,16 @@
 import android.os.RegistrantList;
 import android.telephony.PhoneNumberUtils;
 import android.telephony.PhoneStateListener;
-import android.telephony.Rlog;
 import android.telephony.ServiceState;
-import android.telephony.TelephonyManager;
 
 import com.android.internal.telephony.sip.SipPhone;
+import com.android.telephony.Rlog;
 
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.List;
 
-
-
 /**
  * @hide
  *
diff --git a/src/java/com/android/internal/telephony/CallStateException.java b/src/java/com/android/internal/telephony/CallStateException.java
index 1356a2b..3fdc444 100644
--- a/src/java/com/android/internal/telephony/CallStateException.java
+++ b/src/java/com/android/internal/telephony/CallStateException.java
@@ -16,7 +16,7 @@
 
 package com.android.internal.telephony;
 
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
 
 /**
  * {@hide}
diff --git a/src/java/com/android/internal/telephony/CallTracker.java b/src/java/com/android/internal/telephony/CallTracker.java
index 7174186..5975fde 100644
--- a/src/java/com/android/internal/telephony/CallTracker.java
+++ b/src/java/com/android/internal/telephony/CallTracker.java
@@ -16,13 +16,12 @@
 
 package com.android.internal.telephony;
 
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
 import android.content.Context;
 import android.os.AsyncResult;
 import android.os.Handler;
 import android.os.Message;
 import android.os.PersistableBundle;
-import android.os.SystemProperties;
 import android.telephony.CarrierConfigManager;
 import android.text.TextUtils;
 
@@ -75,6 +74,10 @@
     protected static final int EVENT_THREE_WAY_DIAL_L2_RESULT_CDMA = 16;
     protected static final int EVENT_THREE_WAY_DIAL_BLANK_FLASH    = 20;
 
+    @UnsupportedAppUsage
+    public CallTracker() {
+    }
+
     protected void pollCallsWhenSafe() {
         mNeedsPoll = true;
 
@@ -163,52 +166,6 @@
         return mPendingOperations == 0;
     }
 
-    /**
-     * Routine called from dial to check if the number is a test Emergency number
-     * and if so remap the number. This allows a short emergency number to be remapped
-     * to a regular number for testing how the frameworks handles emergency numbers
-     * without actually calling an emergency number.
-     *
-     * This is not a full test and is not a substitute for testing real emergency
-     * numbers but can be useful.
-     *
-     * To use this feature set a system property ril.test.emergencynumber to a pair of
-     * numbers separated by a colon. If the first number matches the number parameter
-     * this routine returns the second number. Example:
-     *
-     * ril.test.emergencynumber=112:1-123-123-45678
-     *
-     * To test Dial 112 take call then hang up on MO device to enter ECM
-     * see RIL#processSolicited RIL_REQUEST_HANGUP_FOREGROUND_RESUME_BACKGROUND
-     *
-     * @param dialString to test if it should be remapped
-     * @return the same number or the remapped number.
-     */
-    protected String checkForTestEmergencyNumber(String dialString) {
-        String testEn = SystemProperties.get("ril.test.emergencynumber");
-        if (DBG_POLL) {
-            log("checkForTestEmergencyNumber: dialString=" + dialString +
-                " testEn=" + testEn);
-        }
-        if (!TextUtils.isEmpty(testEn)) {
-            String values[] = testEn.split(":");
-            log("checkForTestEmergencyNumber: values.length=" + values.length);
-            if (values.length == 2) {
-                if (values[0].equals(
-                        android.telephony.PhoneNumberUtils.stripSeparators(dialString))) {
-                    // mCi will be null for ImsPhoneCallTracker.
-                    if (mCi != null) {
-                        mCi.testingEmergencyCall();
-                    }
-                    log("checkForTestEmergencyNumber: remap " +
-                            dialString + " to " + values[1]);
-                    dialString = values[1];
-                }
-            }
-        }
-        return dialString;
-    }
-
     protected String convertNumberIfNecessary(Phone phone, String dialNumber) {
         if (dialNumber == null) {
             return dialNumber;
@@ -293,6 +250,18 @@
         return ret;
     }
 
+    /**
+     * Get the ringing connections which during SRVCC handover.
+     */
+    public Connection getRingingHandoverConnection() {
+        for (Connection hoConn : mHandoverConnections) {
+            if (hoConn.getCall().isRinging()) {
+                return hoConn;
+            }
+        }
+        return null;
+    }
+
     //***** Overridden from Handler
     @Override
     public abstract void handleMessage (Message msg);
diff --git a/src/java/com/android/internal/telephony/CarrierActionAgent.java b/src/java/com/android/internal/telephony/CarrierActionAgent.java
index c01c9d4..a87925d 100644
--- a/src/java/com/android/internal/telephony/CarrierActionAgent.java
+++ b/src/java/com/android/internal/telephony/CarrierActionAgent.java
@@ -27,13 +27,13 @@
 import android.os.RegistrantList;
 import android.provider.Settings;
 import android.provider.Telephony;
-import android.telephony.Rlog;
 import android.telephony.TelephonyManager;
 import android.util.LocalLog;
 import android.util.Log;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.IndentingPrintWriter;
+import com.android.telephony.Rlog;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
@@ -89,7 +89,7 @@
             final String action = intent.getAction();
             final String iccState = intent.getStringExtra(IccCardConstants.INTENT_KEY_ICC_STATE);
             if (TelephonyIntents.ACTION_SIM_STATE_CHANGED.equals(action)){
-                if (intent.getBooleanExtra(TelephonyIntents.EXTRA_REBROADCAST_ON_UNLOCK, false)) {
+                if (intent.getBooleanExtra(Intent.EXTRA_REBROADCAST_ON_UNLOCK, false)) {
                     // ignore rebroadcast since carrier apps are direct boot aware.
                     return;
                 }
@@ -227,7 +227,7 @@
         carrierActionSetRadioEnabled(true);
         // notify configured carrier apps for reset
         mPhone.getCarrierSignalAgent().notifyCarrierSignalReceivers(
-                new Intent(TelephonyIntents.ACTION_CARRIER_SIGNAL_RESET));
+                new Intent(TelephonyManager.ACTION_CARRIER_SIGNAL_RESET));
     }
 
     private RegistrantList getRegistrantsFromAction(int action) {
diff --git a/src/java/com/android/internal/telephony/CarrierInfoManager.java b/src/java/com/android/internal/telephony/CarrierInfoManager.java
index f645746..ea4d75e 100644
--- a/src/java/com/android/internal/telephony/CarrierInfoManager.java
+++ b/src/java/com/android/internal/telephony/CarrierInfoManager.java
@@ -25,6 +25,7 @@
 import android.os.UserHandle;
 import android.provider.Telephony;
 import android.telephony.ImsiEncryptionInfo;
+import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyManager;
 import android.text.TextUtils;
 import android.util.Log;
@@ -57,18 +58,16 @@
      *         used for encryption.
      */
     public static ImsiEncryptionInfo getCarrierInfoForImsiEncryption(int keyType,
-                                                                     Context context) {
+                                                                     Context context,
+                                                                     String operatorNumeric) {
         String mcc = "";
         String mnc = "";
-        final TelephonyManager telephonyManager =
-                (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
-        String simOperator = telephonyManager.getSimOperator();
-        if (!TextUtils.isEmpty(simOperator)) {
-            mcc = simOperator.substring(0, 3);
-            mnc = simOperator.substring(3);
+        if (!TextUtils.isEmpty(operatorNumeric)) {
+            mcc = operatorNumeric.substring(0, 3);
+            mnc = operatorNumeric.substring(3);
             Log.i(LOG_TAG, "using values for mnc, mcc: " + mnc + "," + mcc);
         } else {
-            Log.e(LOG_TAG, "Invalid networkOperator: " + simOperator);
+            Log.e(LOG_TAG, "Invalid networkOperator: " + operatorNumeric);
             return null;
         }
         Cursor findCursor = null;
@@ -199,7 +198,7 @@
         mLastAccessResetCarrierKey = now;
         deleteCarrierInfoForImsiEncryption(context);
         Intent resetIntent = new Intent(TelephonyIntents.ACTION_CARRIER_CERTIFICATE_DOWNLOAD);
-        resetIntent.putExtra(PhoneConstants.PHONE_KEY, mPhoneId);
+        SubscriptionManager.putPhoneIdAndSubIdExtra(resetIntent, mPhoneId);
         context.sendBroadcastAsUser(resetIntent, UserHandle.ALL);
     }
 
diff --git a/src/java/com/android/internal/telephony/CarrierKeyDownloadManager.java b/src/java/com/android/internal/telephony/CarrierKeyDownloadManager.java
index 71e9dc5..3e94f4f 100644
--- a/src/java/com/android/internal/telephony/CarrierKeyDownloadManager.java
+++ b/src/java/com/android/internal/telephony/CarrierKeyDownloadManager.java
@@ -43,7 +43,6 @@
 import android.util.Pair;
 
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.org.bouncycastle.util.io.pem.PemReader;
 
 import org.json.JSONArray;
 import org.json.JSONException;
@@ -55,7 +54,6 @@
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.InputStreamReader;
-import java.io.Reader;
 import java.security.PublicKey;
 import java.security.cert.CertificateFactory;
 import java.security.cert.X509Certificate;
@@ -72,6 +70,10 @@
 
     private static final String MCC_MNC_PREF_TAG = "CARRIER_KEY_DM_MCC_MNC";
 
+    private static final String CERT_BEGIN_STRING = "-----BEGIN CERTIFICATE-----";
+
+    private static final String CERT_END_STRING = "-----END CERTIFICATE-----";
+
     private static final int DAY_IN_MILLIS = 24 * 3600 * 1000;
 
     // Create a window prior to the key expiration, during which the cert will be
@@ -270,8 +272,7 @@
         Intent intent = new Intent(INTENT_KEY_RENEWAL_ALARM_PREFIX + slotId);
         PendingIntent carrierKeyDownloadIntent = PendingIntent.getBroadcast(mContext, 0, intent,
                 PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);
-        alarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, minExpirationDate,
-                carrierKeyDownloadIntent);
+        alarmManager.set(AlarmManager.RTC_WAKEUP, minExpirationDate, carrierKeyDownloadIntent);
         Log.d(LOG_TAG, "setRenewelAlarm: action=" + intent.getAction() + " time="
                 + new Date(minExpirationDate));
     }
@@ -437,7 +438,6 @@
             Log.e(LOG_TAG, "jsonStr or mcc, mnc: is empty");
             return;
         }
-        PemReader reader = null;
         try {
             String mcc = "";
             String mnc = "";
@@ -449,18 +449,14 @@
             for (int i = 0; i < keys.length(); i++) {
                 JSONObject key = keys.getJSONObject(i);
                 // Support both "public-key" and "certificate" String property.
-                // "certificate" is a more accurate description, however, the 3GPP draft spec
-                // S3-170116, "Privacy Protection for EAP-AKA" section 4.3 mandates the use of
-                // "public-key".
                 String cert = null;
                 if (key.has(JSON_CERTIFICATE)) {
                     cert = key.getString(JSON_CERTIFICATE);
                 } else {
                     cert = key.getString(JSON_CERTIFICATE_ALTERNATE);
                 }
-                // The 3GPP draft spec 3GPP draft spec S3-170116, "Privacy Protection for EAP-AKA"
-                // section 4.3, does not specify any key-type property. To be compatible with these
-                // networks, the logic defaults to WLAN type if not specified.
+                // The key-type property is optional, therefore, the default value is WLAN type if
+                // not specified.
                 int type = TelephonyManager.KEY_TYPE_WLAN;
                 if (key.has(JSON_TYPE)) {
                     String typeString = key.getString(JSON_TYPE);
@@ -471,26 +467,14 @@
                     }
                 }
                 String identifier = key.getString(JSON_IDENTIFIER);
-                ByteArrayInputStream inStream = new ByteArrayInputStream(cert.getBytes());
-                Reader fReader = new BufferedReader(new InputStreamReader(inStream));
-                reader = new PemReader(fReader);
                 Pair<PublicKey, Long> keyInfo =
-                        getKeyInformation(reader.readPemObject().getContent());
-                reader.close();
+                        getKeyInformation(cleanCertString(cert).getBytes());
                 savePublicKey(keyInfo.first, type, identifier, keyInfo.second, mcc, mnc);
             }
         } catch (final JSONException e) {
             Log.e(LOG_TAG, "Json parsing error: " + e.getMessage());
         } catch (final Exception e) {
             Log.e(LOG_TAG, "Exception getting certificate: " + e);
-        } finally {
-            try {
-                if (reader != null) {
-                    reader.close();
-                }
-            } catch (final Exception e) {
-                Log.e(LOG_TAG, "Exception getting certificate: " + e);
-            }
         }
     }
 
@@ -551,6 +535,7 @@
             request.setAllowedOverMetered(mAllowedOverMeteredNetwork);
             request.setVisibleInDownloadsUi(false);
             request.setNotificationVisibility(DownloadManager.Request.VISIBILITY_HIDDEN);
+            request.addRequestHeader("Accept-Encoding", "gzip");
             Long carrierKeyDownloadRequestId = mDownloadManager.enqueue(request);
             SharedPreferences.Editor editor = getDefaultSharedPreferences(mContext).edit();
 
@@ -598,4 +583,16 @@
                 publicKey, new Date(expirationDate));
         mPhone.setCarrierInfoForImsiEncryption(imsiEncryptionInfo);
     }
+
+    /**
+     * Remove potential extraneous text in a certificate string
+     * @param cert certificate string
+     * @return Cleaned up version of the certificate string
+     */
+    @VisibleForTesting
+    public static String cleanCertString(String cert) {
+        return cert.substring(
+                cert.indexOf(CERT_BEGIN_STRING),
+                cert.indexOf(CERT_END_STRING) + CERT_END_STRING.length());
+    }
 }
diff --git a/src/java/com/android/internal/telephony/CarrierPrivilegesTracker.java b/src/java/com/android/internal/telephony/CarrierPrivilegesTracker.java
new file mode 100644
index 0000000..276471a
--- /dev/null
+++ b/src/java/com/android/internal/telephony/CarrierPrivilegesTracker.java
@@ -0,0 +1,551 @@
+/*
+ * Copyright (C) 2020 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;
+
+import static android.telephony.CarrierConfigManager.EXTRA_SLOT_INDEX;
+import static android.telephony.CarrierConfigManager.EXTRA_SUBSCRIPTION_INDEX;
+import static android.telephony.CarrierConfigManager.KEY_CARRIER_CERTIFICATE_STRING_ARRAY;
+import static android.telephony.SubscriptionManager.INVALID_SIM_SLOT_INDEX;
+import static android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID;
+import static android.telephony.TelephonyManager.EXTRA_SIM_STATE;
+import static android.telephony.TelephonyManager.SIM_STATE_ABSENT;
+import static android.telephony.TelephonyManager.SIM_STATE_LOADED;
+import static android.telephony.TelephonyManager.SIM_STATE_NOT_READY;
+import static android.telephony.TelephonyManager.SIM_STATE_UNKNOWN;
+
+import android.annotation.NonNull;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.pm.Signature;
+import android.content.pm.UserInfo;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.os.PersistableBundle;
+import android.os.Registrant;
+import android.os.RegistrantList;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.telephony.CarrierConfigManager;
+import android.telephony.SubscriptionManager;
+import android.telephony.TelephonyManager;
+import android.telephony.UiccAccessRule;
+import android.text.TextUtils;
+import android.util.ArrayMap;
+import android.util.ArraySet;
+import android.util.IntArray;
+import android.util.LocalLog;
+
+import com.android.internal.telephony.uicc.IccUtils;
+import com.android.telephony.Rlog;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.StringJoiner;
+
+/**
+ * CarrierPrivilegesTracker will track the Carrier Privileges for a specific {@link Phone}.
+ * Registered Telephony entities will receive notifications when the UIDs with these privileges
+ * change.
+ */
+public class CarrierPrivilegesTracker extends Handler {
+    private static final String TAG = CarrierPrivilegesTracker.class.getSimpleName();
+
+    private static final boolean VDBG = false;
+
+    private static final String SHA_1 = "SHA-1";
+    private static final String SHA_256 = "SHA-256";
+
+    /**
+     * Action to register a Registrant with this Tracker.
+     * obj: Registrant that will be notified of Carrier Privileged UID changes.
+     */
+    private static final int ACTION_REGISTER_LISTENER = 1;
+
+    /**
+     * Action to unregister a Registrant with this Tracker.
+     * obj: Handler used by the Registrant that will be removed.
+     */
+    private static final int ACTION_UNREGISTER_LISTENER = 2;
+
+    /**
+     * Action for tracking when Carrier Configs are updated.
+     * arg1: Subscription Id for the Carrier Configs update being broadcast
+     * arg2: Slot Index for the Carrier Configs update being broadcast
+     */
+    private static final int ACTION_CARRIER_CONFIG_CERTS_UPDATED = 3;
+
+    /**
+     * Action for tracking when the Phone's SIM state changes.
+     * arg1: slotId that this Action applies to
+     * arg2: simState reported by this Broadcast
+     */
+    private static final int ACTION_SIM_STATE_UPDATED = 4;
+
+    /**
+     * Action for tracking when a package is installed or replaced on the device.
+     * obj: String package name that was installed or replaced on the device.
+     */
+    private static final int ACTION_PACKAGE_ADDED_OR_REPLACED = 5;
+
+    /**
+     * Action for tracking when a package is uninstalled on the device.
+     * obj: String package name that was installed on the device.
+     */
+    private static final int ACTION_PACKAGE_REMOVED = 6;
+
+    /**
+     * Action used to initialize the state of the Tracker.
+     */
+    private static final int ACTION_INITIALIZE_TRACKER = 7;
+
+    private final Context mContext;
+    private final Phone mPhone;
+    private final CarrierConfigManager mCarrierConfigManager;
+    private final PackageManager mPackageManager;
+    private final UserManager mUserManager;
+    private final TelephonyManager mTelephonyManager;
+    private final RegistrantList mRegistrantList;
+    private final LocalLog mLocalLog;
+
+    // Stores certificate hashes for Carrier Config-loaded certs. Certs must be UPPERCASE.
+    private final Set<String> mCarrierConfigCerts;
+
+    // TODO(b/151981841): Use Set<UiccAccessRule> to also check for package names loaded from SIM
+    private final Set<String> mUiccCerts;
+
+    // Map of PackageName -> Certificate hashes for that Package
+    private final Map<String, Set<String>> mInstalledPackageCerts;
+
+    // Map of PackageName -> UIDs for that Package
+    private final Map<String, Set<Integer>> mCachedUids;
+
+    // Privileged UIDs must be kept in sorted order for update-checks.
+    private int[] mPrivilegedUids;
+
+    private final BroadcastReceiver mIntentReceiver =
+            new BroadcastReceiver() {
+                @Override
+                public void onReceive(Context context, Intent intent) {
+                    String action = intent.getAction();
+                    if (action == null) return;
+
+                    switch (action) {
+                        case CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED: {
+                            Bundle extras = intent.getExtras();
+                            int slotIndex = extras.getInt(EXTRA_SLOT_INDEX);
+                            int subId =
+                                    extras.getInt(
+                                            EXTRA_SUBSCRIPTION_INDEX, INVALID_SUBSCRIPTION_ID);
+                            sendMessage(
+                                    obtainMessage(
+                                            ACTION_CARRIER_CONFIG_CERTS_UPDATED,
+                                            subId,
+                                            slotIndex));
+                            break;
+                        }
+                        case TelephonyManager.ACTION_SIM_CARD_STATE_CHANGED: // fall through
+                        case TelephonyManager.ACTION_SIM_APPLICATION_STATE_CHANGED: {
+                            Bundle extras = intent.getExtras();
+                            int simState = extras.getInt(EXTRA_SIM_STATE, SIM_STATE_UNKNOWN);
+                            int slotId =
+                                    extras.getInt(PhoneConstants.PHONE_KEY, INVALID_SIM_SLOT_INDEX);
+
+                            if (simState != SIM_STATE_ABSENT
+                                    && simState != SIM_STATE_NOT_READY
+                                    && simState != SIM_STATE_LOADED) return;
+
+                            sendMessage(obtainMessage(ACTION_SIM_STATE_UPDATED, slotId, simState));
+                            break;
+                        }
+                        case Intent.ACTION_PACKAGE_ADDED: // fall through
+                        case Intent.ACTION_PACKAGE_REPLACED: // fall through
+                        case Intent.ACTION_PACKAGE_REMOVED: {
+                            int what =
+                                    (action.equals(Intent.ACTION_PACKAGE_REMOVED))
+                                            ? ACTION_PACKAGE_REMOVED
+                                            : ACTION_PACKAGE_ADDED_OR_REPLACED;
+                            Uri uri = intent.getData();
+                            String pkgName = (uri != null) ? uri.getSchemeSpecificPart() : null;
+                            if (TextUtils.isEmpty(pkgName)) {
+                                Rlog.e(TAG, "Failed to get package from Intent");
+                                return;
+                            }
+
+                            sendMessage(obtainMessage(what, pkgName));
+                            break;
+                        }
+                    }
+                }
+            };
+
+    public CarrierPrivilegesTracker(
+            @NonNull Looper looper, @NonNull Phone phone, @NonNull Context context) {
+        super(looper);
+        mContext = context;
+        mCarrierConfigManager =
+                (CarrierConfigManager) mContext.getSystemService(Context.CARRIER_CONFIG_SERVICE);
+        mPackageManager = mContext.getPackageManager();
+        mUserManager = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
+        mTelephonyManager = (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE);
+        mPhone = phone;
+        mLocalLog = new LocalLog(100);
+
+        IntentFilter certFilter = new IntentFilter();
+        certFilter.addAction(CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED);
+        certFilter.addAction(TelephonyManager.ACTION_SIM_CARD_STATE_CHANGED);
+        certFilter.addAction(TelephonyManager.ACTION_SIM_APPLICATION_STATE_CHANGED);
+        mContext.registerReceiver(mIntentReceiver, certFilter);
+
+        IntentFilter packageFilter = new IntentFilter();
+        packageFilter.addAction(Intent.ACTION_PACKAGE_ADDED);
+        packageFilter.addAction(Intent.ACTION_PACKAGE_REPLACED);
+        packageFilter.addAction(Intent.ACTION_PACKAGE_REMOVED);
+
+        // For package-related broadcasts, specify the data scheme for "package" to receive the
+        // package name along with the broadcast
+        packageFilter.addDataScheme("package");
+        mContext.registerReceiver(mIntentReceiver, packageFilter);
+
+        mRegistrantList = new RegistrantList();
+        mCarrierConfigCerts = new ArraySet<>();
+        mUiccCerts = new ArraySet<>();
+        mInstalledPackageCerts = new ArrayMap<>();
+        mCachedUids = new ArrayMap<>();
+        mPrivilegedUids = new int[0];
+
+        sendMessage(obtainMessage(ACTION_INITIALIZE_TRACKER));
+    }
+
+    @Override
+    public void handleMessage(Message msg) {
+        switch (msg.what) {
+            case ACTION_REGISTER_LISTENER: {
+                handleRegisterListener((Registrant) msg.obj);
+                break;
+            }
+            case ACTION_UNREGISTER_LISTENER: {
+                handleUnregisterListener((Handler) msg.obj);
+                break;
+            }
+            case ACTION_CARRIER_CONFIG_CERTS_UPDATED: {
+                int subId = msg.arg1;
+                int slotIndex = msg.arg2;
+                handleCarrierConfigUpdated(subId, slotIndex);
+                break;
+            }
+            case ACTION_SIM_STATE_UPDATED: {
+                handleSimStateChanged(msg.arg1, msg.arg2);
+                break;
+            }
+            case ACTION_PACKAGE_ADDED_OR_REPLACED: {
+                String pkgName = (String) msg.obj;
+                handlePackageAddedOrReplaced(pkgName);
+                break;
+            }
+            case ACTION_PACKAGE_REMOVED: {
+                String pkgName = (String) msg.obj;
+                handlePackageRemoved(pkgName);
+                break;
+            }
+            case ACTION_INITIALIZE_TRACKER: {
+                handleInitializeTracker();
+                break;
+            }
+            default: {
+                Rlog.e(TAG, "Received unknown msg type: " + msg.what);
+                break;
+            }
+        }
+    }
+
+    private void handleRegisterListener(Registrant registrant) {
+        mRegistrantList.add(registrant);
+        registrant.notifyResult(mPrivilegedUids);
+    }
+
+    private void handleUnregisterListener(Handler handler) {
+        mRegistrantList.remove(handler);
+    }
+
+    private void handleCarrierConfigUpdated(int subId, int slotIndex) {
+        if (slotIndex != mPhone.getPhoneId()) return;
+
+        Set<String> updatedCarrierConfigCerts = Collections.EMPTY_SET;
+
+        // Carrier Config broadcasts with INVALID_SUBSCRIPTION_ID when the SIM is removed. This is
+        // an expected event. When this happens, clear the certificates from the previous configs.
+        // The certs will be cleared in maybeUpdateCertsAndNotifyRegistrants() below.
+        if (subId != SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
+            updatedCarrierConfigCerts = getCarrierConfigCerts(subId);
+        }
+
+        mLocalLog.log("CarrierConfigUpdated:"
+                + " subId=" + subId
+                + " slotIndex=" + slotIndex
+                + " updated CarrierConfig certs=" + updatedCarrierConfigCerts);
+        maybeUpdateCertsAndNotifyRegistrants(mCarrierConfigCerts, updatedCarrierConfigCerts);
+    }
+
+    private Set<String> getCarrierConfigCerts(int subId) {
+        PersistableBundle carrierConfigs = mCarrierConfigManager.getConfigForSubId(subId);
+        if (!mCarrierConfigManager.isConfigForIdentifiedCarrier(carrierConfigs)) {
+            return Collections.EMPTY_SET;
+        }
+
+        Set<String> updatedCarrierConfigCerts = new ArraySet<>();
+        String[] carrierConfigCerts =
+                carrierConfigs.getStringArray(KEY_CARRIER_CERTIFICATE_STRING_ARRAY);
+
+        if (carrierConfigCerts != null) {
+            for (String cert : carrierConfigCerts) {
+                updatedCarrierConfigCerts.add(cert.toUpperCase());
+            }
+        }
+        return updatedCarrierConfigCerts;
+    }
+
+    private void handleSimStateChanged(int slotId, int simState) {
+        if (slotId != mPhone.getPhoneId()) return;
+
+        Set<String> updatedUiccCerts = Collections.EMPTY_SET;
+
+        // Only include the UICC certs if the SIM is fully loaded
+        if (simState == SIM_STATE_LOADED) {
+            updatedUiccCerts = getSimCerts();
+        }
+
+        mLocalLog.log("SIM State Changed:"
+                + " slotId=" + slotId
+                + " simState=" + simState
+                + " updated SIM-loaded certs=" + updatedUiccCerts);
+        maybeUpdateCertsAndNotifyRegistrants(mUiccCerts, updatedUiccCerts);
+    }
+
+    private Set<String> getSimCerts() {
+        Set<String> updatedUiccCerts = Collections.EMPTY_SET;
+        TelephonyManager telMan = mTelephonyManager.createForSubscriptionId(mPhone.getSubId());
+
+        if (telMan.hasIccCard(mPhone.getPhoneId())) {
+            updatedUiccCerts = new ArraySet<>();
+            List<String> uiccCerts = telMan.getCertsFromCarrierPrivilegeAccessRules();
+            if (uiccCerts != null) {
+                for (String cert : uiccCerts) {
+                    updatedUiccCerts.add(cert.toUpperCase());
+                }
+            }
+        }
+        return updatedUiccCerts;
+    }
+
+    private void handlePackageAddedOrReplaced(String pkgName) {
+        PackageInfo pkg;
+        try {
+            pkg = mPackageManager.getPackageInfo(pkgName, PackageManager.GET_SIGNING_CERTIFICATES);
+        } catch (NameNotFoundException e) {
+            Rlog.e(TAG, "Error getting installed package: " + pkgName, e);
+            return;
+        }
+
+        updateCertsForPackage(pkg);
+        mCachedUids.put(pkg.packageName, getUidsForPackage(pkg.packageName));
+        mLocalLog.log("Package added/replaced:"
+                + " pkg=" + Rlog.pii(TAG, pkgName)
+                + " cert hashes=" + mInstalledPackageCerts.get(pkgName));
+
+        maybeUpdatePrivilegedUidsAndNotifyRegistrants();
+    }
+
+    private void updateCertsForPackage(PackageInfo pkg) {
+        Set<String> certs = new ArraySet<>();
+        List<Signature> signatures = UiccAccessRule.getSignatures(pkg);
+        for (Signature signature : signatures) {
+            byte[] sha1 = UiccAccessRule.getCertHash(signature, SHA_1);
+            certs.add(IccUtils.bytesToHexString(sha1).toUpperCase());
+
+            byte[] sha256 = UiccAccessRule.getCertHash(signature, SHA_256);
+            certs.add(IccUtils.bytesToHexString(sha256).toUpperCase());
+        }
+
+        mInstalledPackageCerts.put(pkg.packageName, certs);
+    }
+
+    private void handlePackageRemoved(String pkgName) {
+        if (mInstalledPackageCerts.remove(pkgName) == null) {
+            Rlog.e(TAG, "Unknown package was uninstalled: " + pkgName);
+            return;
+        }
+        mCachedUids.remove(pkgName);
+
+        mLocalLog.log("Package removed: pkg=" + Rlog.pii(TAG, pkgName));
+
+        maybeUpdatePrivilegedUidsAndNotifyRegistrants();
+    }
+
+    private void handleInitializeTracker() {
+        // Cache CarrierConfig Certs
+        mCarrierConfigCerts.addAll(getCarrierConfigCerts(mPhone.getSubId()));
+
+        // Cache SIM certs
+        mUiccCerts.addAll(getSimCerts());
+
+        // Cache all installed packages and their certs
+        int flags =
+                PackageManager.MATCH_DISABLED_COMPONENTS
+                        | PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS
+                        | PackageManager.GET_SIGNING_CERTIFICATES;
+        List<PackageInfo> installedPackages =
+                mPackageManager.getInstalledPackagesAsUser(
+                        flags, UserHandle.SYSTEM.getIdentifier());
+        for (PackageInfo pkg : installedPackages) {
+            updateCertsForPackage(pkg);
+        }
+
+        // Okay because no registrants exist yet
+        maybeUpdatePrivilegedUidsAndNotifyRegistrants();
+
+        String msg = "Initializing state:"
+                + " CarrierConfig certs=" + mCarrierConfigCerts
+                + " SIM-loaded certs=" + mUiccCerts;
+        if (VDBG) {
+            msg += " installed pkgs=" + getObfuscatedPackages();
+        }
+        mLocalLog.log(msg);
+    }
+
+    private String getObfuscatedPackages() {
+        StringJoiner obfuscatedPkgs = new StringJoiner(",", "{", "}");
+        for (Map.Entry<String, Set<String>> pkg : mInstalledPackageCerts.entrySet()) {
+            obfuscatedPkgs.add("pkg(" + Rlog.pii(TAG, pkg.getKey()) + ")=" + pkg.getValue());
+        }
+        return obfuscatedPkgs.toString();
+    }
+
+    private void maybeUpdateCertsAndNotifyRegistrants(
+            Set<String> currentCerts, Set<String> updatedCerts) {
+        if (currentCerts.equals(updatedCerts)) return;
+
+        currentCerts.clear();
+        currentCerts.addAll(updatedCerts);
+
+        maybeUpdatePrivilegedUidsAndNotifyRegistrants();
+    }
+
+    private void maybeUpdatePrivilegedUidsAndNotifyRegistrants() {
+        int[] currentPrivilegedUids = getCurrentPrivilegedUidsForAllUsers();
+
+        // Sort UIDs for the equality check
+        Arrays.sort(currentPrivilegedUids);
+        if (Arrays.equals(mPrivilegedUids, currentPrivilegedUids)) return;
+
+        mPrivilegedUids = currentPrivilegedUids;
+        mRegistrantList.notifyResult(mPrivilegedUids);
+
+        mLocalLog.log("Privileged UIDs changed. New UIDs=" + Arrays.toString(mPrivilegedUids));
+    }
+
+    private int[] getCurrentPrivilegedUidsForAllUsers() {
+        Set<Integer> privilegedUids = new ArraySet<>();
+        for (Map.Entry<String, Set<String>> e : mInstalledPackageCerts.entrySet()) {
+            if (isPackagePrivileged(e.getValue())) {
+                privilegedUids.addAll(getUidsForPackage(e.getKey()));
+            }
+        }
+
+        IntArray result = new IntArray(privilegedUids.size());
+        for (int uid : privilegedUids) {
+            result.add(uid);
+        }
+        return result.toArray();
+    }
+
+    /**
+     * Returns true iff there is an overlap between the provided certificate hashes and the
+     * certificate hashes stored in mCarrierConfigCerts and mUiccCerts.
+     */
+    private boolean isPackagePrivileged(Set<String> certs) {
+        return !Collections.disjoint(mCarrierConfigCerts, certs)
+                || !Collections.disjoint(mUiccCerts, certs);
+    }
+
+    private Set<Integer> getUidsForPackage(String pkgName) {
+        if (mCachedUids.containsKey(pkgName)) {
+            return mCachedUids.get(pkgName);
+        }
+
+        Set<Integer> uids = new ArraySet<>();
+        List<UserInfo> users = mUserManager.getUsers();
+        for (UserInfo user : users) {
+            int userId = user.getUserHandle().getIdentifier();
+            try {
+                uids.add(mPackageManager.getPackageUidAsUser(pkgName, userId));
+            } catch (NameNotFoundException exception) {
+                // Didn't find package. Continue looking at other packages
+                Rlog.e(TAG, "Unable to find uid for package " + pkgName + " and user " + userId);
+            }
+        }
+        mCachedUids.put(pkgName, uids);
+        return uids;
+    }
+
+    /**
+     * Dump the local log buffer and other internal state of CarrierPrivilegesTracker.
+     */
+    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+        pw.println("Dump of CarrierPrivilegesTracker");
+        pw.println("CarrierPrivilegesTracker - Log Begin ----");
+        mLocalLog.dump(fd, pw, args);
+        pw.println("CarrierPrivilegesTracker - Log End ----");
+        pw.println("CarrierPrivilegesTracker - Privileged UIDs: "
+                + Arrays.toString(mPrivilegedUids));
+        pw.println("CarrierPrivilegesTracker - SIM-loaded Certs: " + mUiccCerts);
+        pw.println("CarrierPrivilegesTracker - CarrierPrivileged Certs: " + mCarrierConfigCerts);
+        if (VDBG) {
+            pw.println("CarrierPrivilegesTracker - Obfuscated Pkgs + Certs: "
+                    + getObfuscatedPackages());
+        }
+    }
+
+    /**
+     * Registers the given Registrant with this tracker.
+     *
+     * <p>After being registered, the Registrant will be notified with the current Carrier
+     * Privileged UIDs for this Phone.
+     */
+    public void registerCarrierPrivilegesListener(Handler h, int what, Object obj) {
+        sendMessage(obtainMessage(ACTION_REGISTER_LISTENER, new Registrant(h, what, obj)));
+    }
+
+    /**
+     * Unregisters the given listener with this tracker.
+     */
+    public void unregisterCarrierPrivilegesListener(Handler handler) {
+        sendMessage(obtainMessage(ACTION_UNREGISTER_LISTENER, handler));
+    }
+}
diff --git a/src/java/com/android/internal/telephony/CarrierResolver.java b/src/java/com/android/internal/telephony/CarrierResolver.java
index 959522f..d5b630a 100644
--- a/src/java/com/android/internal/telephony/CarrierResolver.java
+++ b/src/java/com/android/internal/telephony/CarrierResolver.java
@@ -16,8 +16,10 @@
 package com.android.internal.telephony;
 
 import static android.provider.Telephony.CarrierId;
+import static android.provider.Telephony.Carriers.CONTENT_URI;
 
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.content.ContentValues;
 import android.content.Context;
 import android.content.Intent;
@@ -29,7 +31,6 @@
 import android.provider.Telephony;
 import android.service.carrier.CarrierIdentifier;
 import android.telephony.PhoneStateListener;
-import android.telephony.Rlog;
 import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyManager;
 import android.text.TextUtils;
@@ -41,6 +42,7 @@
 import com.android.internal.telephony.uicc.IccRecords;
 import com.android.internal.telephony.uicc.UiccController;
 import com.android.internal.util.IndentingPrintWriter;
+import com.android.telephony.Rlog;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
@@ -100,7 +102,7 @@
     private final ContentObserver mContentObserver = new ContentObserver(this) {
         @Override
         public void onChange(boolean selfChange, Uri uri) {
-            if (CONTENT_URL_PREFER_APN.equals(uri.getLastPathSegment())) {
+            if (Telephony.Carriers.CONTENT_URI.equals(uri)) {
                 logd("onChange URI: " + uri);
                 sendEmptyMessage(PREFER_APN_UPDATE_EVENT);
             } else if (CarrierId.All.CONTENT_URI.equals(uri)) {
@@ -992,6 +994,44 @@
 
     // static helper function to get carrier id from mccmnc
     public static int getCarrierIdFromMccMnc(@NonNull Context context, String mccmnc) {
+        try (Cursor cursor = getCursorForMccMnc(context, mccmnc)) {
+            if (cursor == null || !cursor.moveToNext()) return TelephonyManager.UNKNOWN_CARRIER_ID;
+            if (VDBG) {
+                logd("[getCarrierIdFromMccMnc]- " + cursor.getCount()
+                        + " Records(s) in DB" + " mccmnc: " + mccmnc);
+            }
+            return cursor.getInt(cursor.getColumnIndex(CarrierId.CARRIER_ID));
+        } catch (Exception ex) {
+            loge("[getCarrierIdFromMccMnc]- ex: " + ex);
+        }
+        return TelephonyManager.UNKNOWN_CARRIER_ID;
+    }
+
+    /**
+     * Static helper function to get carrier name from mccmnc
+     * @param context Context
+     * @param mccmnc PLMN
+     * @return Carrier name string given mccmnc/PLMN
+     *
+     * @hide
+     */
+    @Nullable
+    public static String getCarrierNameFromMccMnc(@NonNull Context context, String mccmnc) {
+        try (Cursor cursor = getCursorForMccMnc(context, mccmnc)) {
+            if (cursor == null || !cursor.moveToNext()) return null;
+            if (VDBG) {
+                logd("[getCarrierNameFromMccMnc]- " + cursor.getCount()
+                        + " Records(s) in DB" + " mccmnc: " + mccmnc);
+            }
+            return cursor.getString(cursor.getColumnIndex(CarrierId.CARRIER_NAME));
+        } catch (Exception ex) {
+            loge("[getCarrierNameFromMccMnc]- ex: " + ex);
+        }
+        return null;
+    }
+
+    @Nullable
+    private static Cursor getCursorForMccMnc(@NonNull Context context, String mccmnc) {
         try {
             Cursor cursor = context.getContentResolver().query(
                     CarrierId.All.CONTENT_URI,
@@ -1007,25 +1047,11 @@
                             + CarrierId.All.APN + " is NULL",
                     /* selectionArgs */ new String[]{mccmnc},
                     null);
-            try {
-                if (cursor != null) {
-                    if (VDBG) {
-                        logd("[getCarrierIdFromMccMnc]- " + cursor.getCount()
-                                + " Records(s) in DB" + " mccmnc: " + mccmnc);
-                    }
-                    while (cursor.moveToNext()) {
-                        return cursor.getInt(cursor.getColumnIndex(CarrierId.CARRIER_ID));
-                    }
-                }
-            } finally {
-                if (cursor != null) {
-                    cursor.close();
-                }
-            }
+            return cursor;
         } catch (Exception ex) {
-            loge("[getCarrierIdFromMccMnc]- ex: " + ex);
+            loge("[getCursorForMccMnc]- ex: " + ex);
+            return null;
         }
-        return TelephonyManager.UNKNOWN_CARRIER_ID;
     }
 
     private static boolean equals(String a, String b, boolean ignoreCase) {
diff --git a/src/java/com/android/internal/telephony/CarrierServiceBindHelper.java b/src/java/com/android/internal/telephony/CarrierServiceBindHelper.java
index c085a5f..b57a9a6 100644
--- a/src/java/com/android/internal/telephony/CarrierServiceBindHelper.java
+++ b/src/java/com/android/internal/telephony/CarrierServiceBindHelper.java
@@ -16,13 +16,14 @@
 
 package com.android.internal.telephony;
 
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
 import android.content.BroadcastReceiver;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.content.ServiceConnection;
+import android.content.pm.ComponentInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.ResolveInfo;
 import android.os.Bundle;
@@ -33,12 +34,15 @@
 import android.os.SystemClock;
 import android.os.UserHandle;
 import android.service.carrier.CarrierService;
+import android.telephony.PackageChangeReceiver;
 import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyManager;
 import android.text.TextUtils;
 import android.util.Log;
+import android.util.SparseArray;
 
-import com.android.internal.content.PackageMonitor;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.telephony.util.TelephonyUtils;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
@@ -59,9 +63,11 @@
 
     @UnsupportedAppUsage
     private Context mContext;
-    private AppBinding[] mBindings;
-    private String[] mLastSimState;
-    private final PackageMonitor mPackageMonitor = new CarrierServicePackageMonitor();
+    @VisibleForTesting
+    public SparseArray<AppBinding> mBindings = new SparseArray();
+    @VisibleForTesting
+    public SparseArray<String> mLastSimState = new SparseArray<>();
+    private final PackageChangeReceiver mPackageMonitor = new CarrierServicePackageMonitor();
 
     // whether we have successfully bound to the service
     private boolean mServiceBound = false;
@@ -75,33 +81,45 @@
             if (Intent.ACTION_USER_UNLOCKED.equals(action)) {
                 // On user unlock, new components might become available, so reevaluate all
                 // bindings.
-                for (int phoneId = 0; phoneId < mBindings.length; phoneId++) {
-                    mBindings[phoneId].rebind();
+                for (int phoneId = 0; phoneId < mBindings.size(); phoneId++) {
+                    mBindings.get(phoneId).rebind();
                 }
             }
         }
     };
 
     private static final int EVENT_REBIND = 0;
-    private static final int EVENT_PERFORM_IMMEDIATE_UNBIND = 1;
+    @VisibleForTesting
+    public static final int EVENT_PERFORM_IMMEDIATE_UNBIND = 1;
+    @VisibleForTesting
+    public static final int EVENT_MULTI_SIM_CONFIG_CHANGED = 2;
 
     @UnsupportedAppUsage
-    private Handler mHandler = new Handler() {
+    @VisibleForTesting
+    public Handler mHandler = new Handler() {
         @Override
         public void handleMessage(Message msg) {
+            int phoneId;
             AppBinding binding;
             log("mHandler: " + msg.what);
 
             switch (msg.what) {
                 case EVENT_REBIND:
-                    binding = (AppBinding) msg.obj;
+                    phoneId = (int) msg.obj;
+                    binding = mBindings.get(phoneId);
+                    if (binding == null) return;
                     log("Rebinding if necessary for phoneId: " + binding.getPhoneId());
                     binding.rebind();
                     break;
                 case EVENT_PERFORM_IMMEDIATE_UNBIND:
-                    binding = (AppBinding) msg.obj;
+                    phoneId = (int) msg.obj;
+                    binding = mBindings.get(phoneId);
+                    if (binding == null) return;
                     binding.performImmediateUnbind();
                     break;
+                case EVENT_MULTI_SIM_CONFIG_CHANGED:
+                    updateBindingsAndSimStates();
+                    break;
             }
         }
     };
@@ -109,19 +127,42 @@
     public CarrierServiceBindHelper(Context context) {
         mContext = context;
 
-        int numPhones = TelephonyManager.from(context).getPhoneCount();
-        mBindings = new AppBinding[numPhones];
-        mLastSimState = new String[numPhones];
+        updateBindingsAndSimStates();
 
-        for (int phoneId = 0; phoneId < numPhones; phoneId++) {
-            mBindings[phoneId] = new AppBinding(phoneId);
-        }
+        PhoneConfigurationManager.registerForMultiSimConfigChange(
+                mHandler, EVENT_MULTI_SIM_CONFIG_CHANGED, null);
 
         mPackageMonitor.register(
-                context, mHandler.getLooper(), UserHandle.ALL, false /* externalStorage */);
-        mContext.registerReceiverAsUser(mUserUnlockedReceiver, UserHandle.SYSTEM,
+                context, mHandler.getLooper(), UserHandle.ALL);
+        try {
+            Context contextAsUser = mContext.createPackageContextAsUser(mContext.getPackageName(),
+                0, UserHandle.SYSTEM);
+            contextAsUser.registerReceiver(mUserUnlockedReceiver,
                 new IntentFilter(Intent.ACTION_USER_UNLOCKED), null /* broadcastPermission */,
                 mHandler);
+        } catch (PackageManager.NameNotFoundException e) {
+            loge("Package name not found: " + e.getMessage());
+        }
+    }
+
+    // Create or dispose mBindings and mLastSimState objects.
+    private void updateBindingsAndSimStates() {
+        int prevLen = mBindings.size();
+        int newLen = ((TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE))
+                .getActiveModemCount();
+
+        // If prevLen < newLen, allocate AppBinding and simState objects.
+        for (int phoneId = prevLen; phoneId < newLen; phoneId++) {
+            mBindings.put(phoneId, new AppBinding(phoneId));
+            mLastSimState.put(phoneId, new String());
+        }
+
+        // If prevLen > newLen, dispose AppBinding and simState objects.
+        for (int phoneId = newLen; phoneId < prevLen; phoneId++) {
+            mBindings.get(phoneId).unbind(true);
+            mBindings.delete(phoneId);
+            mLastSimState.delete(phoneId);
+        }
     }
 
     void updateForPhoneId(int phoneId, String simState) {
@@ -129,14 +170,14 @@
         if (!SubscriptionManager.isValidPhoneId(phoneId)) {
             return;
         }
-        if (TextUtils.isEmpty(simState) || phoneId >= mLastSimState.length) return;
-        if (simState.equals(mLastSimState[phoneId])) {
+        if (TextUtils.isEmpty(simState) || phoneId >= mLastSimState.size()) return;
+        if (simState.equals(mLastSimState.get(phoneId))) {
             // ignore consecutive duplicated events
             return;
         } else {
-            mLastSimState[phoneId] = simState;
+            mLastSimState.put(phoneId, simState);
         }
-        mHandler.sendMessage(mHandler.obtainMessage(EVENT_REBIND, mBindings[phoneId]));
+        mHandler.sendMessage(mHandler.obtainMessage(EVENT_REBIND, phoneId));
     }
 
     private class AppBinding {
@@ -200,8 +241,9 @@
             String candidateServiceClass = null;
             if (carrierResolveInfo != null) {
                 metadata = carrierResolveInfo.serviceInfo.metaData;
-                candidateServiceClass =
-                        carrierResolveInfo.getComponentInfo().getComponentName().getClassName();
+                ComponentInfo componentInfo = TelephonyUtils.getComponentInfo(carrierResolveInfo);
+                candidateServiceClass = new ComponentName(componentInfo.packageName,
+                    componentInfo.name).getClassName();
             }
 
             // Only bind if the service wants it
@@ -235,9 +277,14 @@
 
             String error;
             try {
-                if (mContext.bindServiceAsUser(carrierService, connection,
-                        Context.BIND_AUTO_CREATE |  Context.BIND_FOREGROUND_SERVICE,
-                        mHandler, Process.myUserHandle())) {
+                if (mContext.createContextAsUser(Process.myUserHandle(), 0)
+                        .bindService(carrierService,
+                                Context.BIND_AUTO_CREATE
+                                | Context.BIND_FOREGROUND_SERVICE
+                                | Context.BIND_INCLUDE_CAPABILITIES,
+                                (r) -> mHandler.post(r),
+                                connection)) {
+                    log("service bound");
                     mServiceBound = true;
                     return;
                 }
@@ -278,7 +325,7 @@
                 mUnbindScheduledUptimeMillis = currentUptimeMillis + UNBIND_DELAY_MILLIS;
                 log("Scheduling unbind in " + UNBIND_DELAY_MILLIS + " millis");
                 mHandler.sendMessageAtTime(
-                        mHandler.obtainMessage(EVENT_PERFORM_IMMEDIATE_UNBIND, this),
+                        mHandler.obtainMessage(EVENT_PERFORM_IMMEDIATE_UNBIND, phoneId),
                         mUnbindScheduledUptimeMillis);
             }
         }
@@ -296,7 +343,12 @@
             if (mServiceBound) {
                 log("Unbinding from carrier app");
                 mServiceBound = false;
-                mContext.unbindService(connection);
+                try {
+                    mContext.unbindService(connection);
+                } catch (IllegalArgumentException e) {
+                    //TODO(b/151328766): Figure out why we unbind without binding
+                    loge("Tried to unbind without binding e=" + e);
+                }
             } else {
                 log("Not bound, skipping unbindService call");
             }
@@ -337,24 +389,36 @@
         }
 
         @Override
+        public void onBindingDied(ComponentName name) {
+            log("Binding from carrier app died: " + name.flattenToString());
+            connected = false;
+        }
+
+        @Override
+        public void onNullBinding(ComponentName name) {
+            log("Null binding from carrier app: " + name.flattenToString());
+            connected = false;
+        }
+
+        @Override
         public String toString() {
             return "CarrierServiceConnection[connected=" + connected + "]";
         }
     }
 
-    private class CarrierServicePackageMonitor extends PackageMonitor {
+    private class CarrierServicePackageMonitor extends PackageChangeReceiver {
         @Override
-        public void onPackageAdded(String packageName, int reason) {
+        public void onPackageAdded(String packageName) {
             evaluateBinding(packageName, true /* forceUnbind */);
         }
 
         @Override
-        public void onPackageRemoved(String packageName, int reason) {
+        public void onPackageRemoved(String packageName) {
             evaluateBinding(packageName, true /* forceUnbind */);
         }
 
         @Override
-        public void onPackageUpdateFinished(String packageName, int uid) {
+        public void onPackageUpdateFinished(String packageName) {
             evaluateBinding(packageName, true /* forceUnbind */);
         }
 
@@ -364,17 +428,17 @@
         }
 
         @Override
-        public boolean onHandleForceStop(Intent intent, String[] packages, int uid, boolean doit) {
+        public void onHandleForceStop(String[] packages, boolean doit) {
             if (doit) {
                 for (String packageName : packages) {
                     evaluateBinding(packageName, true /* forceUnbind */);
                 }
             }
-            return super.onHandleForceStop(intent, packages, uid, doit);
         }
 
         private void evaluateBinding(String carrierPackageName, boolean forceUnbind) {
-            for (AppBinding appBinding : mBindings) {
+            for (int i = 0; i < mBindings.size(); i++) {
+                AppBinding appBinding = mBindings.get(i);
                 String appBindingPackage = appBinding.getPackage();
                 boolean isBindingForPackage = carrierPackageName.equals(appBindingPackage);
                 // Only log if this package was a carrier package to avoid log spam in the common
@@ -398,10 +462,12 @@
         Log.d(LOG_TAG, message);
     }
 
+    private static void loge(String message) { Log.e(LOG_TAG, message); }
+
     public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
         pw.println("CarrierServiceBindHelper:");
-        for (AppBinding binding : mBindings) {
-            binding.dump(fd, pw, args);
+        for (int i = 0; i < mBindings.size(); i++) {
+            mBindings.get(i).dump(fd, pw, args);
         }
     }
 }
diff --git a/src/java/com/android/internal/telephony/CarrierServiceStateTracker.java b/src/java/com/android/internal/telephony/CarrierServiceStateTracker.java
index ad3a858..bfa89e0 100644
--- a/src/java/com/android/internal/telephony/CarrierServiceStateTracker.java
+++ b/src/java/com/android/internal/telephony/CarrierServiceStateTracker.java
@@ -23,25 +23,26 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
+import android.content.res.Resources;
 import android.database.ContentObserver;
 import android.os.Handler;
 import android.os.Message;
 import android.os.PersistableBundle;
 import android.provider.Settings;
 import android.telephony.CarrierConfigManager;
-import android.telephony.Rlog;
 import android.telephony.ServiceState;
 import android.telephony.SubscriptionManager;
 import android.telephony.SubscriptionManager.OnSubscriptionsChangedListener;
+import android.telephony.TelephonyManager;
+import android.telephony.TelephonyManager.NetworkTypeBitMask;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.telephony.util.NotificationChannelController;
+import com.android.telephony.Rlog;
 
 import java.util.HashMap;
 import java.util.Map;
 
-
-
 /**
  * This contains Carrier specific logic based on the states/events
  * managed in ServiceStateTracker.
@@ -64,6 +65,12 @@
     public static final int NOTIFICATION_PREF_NETWORK = 1000;
     public static final int NOTIFICATION_EMERGENCY_NETWORK = 1001;
 
+    @VisibleForTesting
+    public static final String EMERGENCY_NOTIFICATION_TAG = "EmergencyNetworkNotification";
+
+    @VisibleForTesting
+    public static final String PREF_NETWORK_NOTIFICATION_TAG = "PrefNetworkNotification";
+
     public CarrierServiceStateTracker(Phone phone, ServiceStateTracker sst) {
         this.mPhone = phone;
         this.mSST = sst;
@@ -159,15 +166,8 @@
         if (mSST.mSS == null) {
             return true; //something has gone wrong, return true and not show the notification.
         }
-        return (mSST.mSS.getVoiceRegState() == ServiceState.STATE_IN_SERVICE
-                || mSST.mSS.getDataRegState() == ServiceState.STATE_IN_SERVICE);
-    }
-
-    private boolean isPhoneVoiceRegistered() {
-        if (mSST.mSS == null) {
-            return true; //something has gone wrong, return true and not show the notification.
-        }
-        return (mSST.mSS.getVoiceRegState() == ServiceState.STATE_IN_SERVICE);
+        return (mSST.mSS.getState() == ServiceState.STATE_IN_SERVICE
+                || mSST.mSS.getDataRegistrationState() == ServiceState.STATE_IN_SERVICE);
     }
 
     private boolean isPhoneRegisteredForWifiCalling() {
@@ -207,7 +207,51 @@
             Rlog.e(LOG_TAG, "Unable to get PREFERRED_NETWORK_MODE.");
             return true;
         }
-        return (preferredNetworkSetting == RILConstants.NETWORK_MODE_LTE_CDMA_EVDO_GSM_WCDMA);
+
+        if (isNrSupported()) {
+            return (preferredNetworkSetting
+                    == RILConstants.NETWORK_MODE_NR_LTE_CDMA_EVDO_GSM_WCDMA);
+        } else {
+            return (preferredNetworkSetting == RILConstants.NETWORK_MODE_LTE_CDMA_EVDO_GSM_WCDMA);
+        }
+    }
+
+    private boolean isNrSupported() {
+        Context context = mPhone.getContext();
+        TelephonyManager tm = ((TelephonyManager) context.getSystemService(
+                Context.TELEPHONY_SERVICE)).createForSubscriptionId(mPhone.getSubId());
+
+        boolean isCarrierConfigEnabled = isCarrierConfigEnableNr(context);
+        boolean isRadioAccessFamilySupported = checkSupportedBitmask(
+                tm.getSupportedRadioAccessFamily(), TelephonyManager.NETWORK_TYPE_BITMASK_NR);
+        boolean isNrNetworkTypeAllowed = checkSupportedBitmask(
+                tm.getAllowedNetworkTypes(), TelephonyManager.NETWORK_TYPE_BITMASK_NR);
+
+        Rlog.i(LOG_TAG, "isNrSupported: " + " carrierConfigEnabled: " + isCarrierConfigEnabled
+                + ", AccessFamilySupported: " + isRadioAccessFamilySupported
+                + ", isNrNetworkTypeAllowed: " + isNrNetworkTypeAllowed);
+
+        return (isCarrierConfigEnabled && isRadioAccessFamilySupported && isNrNetworkTypeAllowed);
+    }
+
+    private boolean isCarrierConfigEnableNr(Context context) {
+        CarrierConfigManager carrierConfigManager = (CarrierConfigManager)
+                context.getSystemService(Context.CARRIER_CONFIG_SERVICE);
+        if (carrierConfigManager == null) {
+            Rlog.e(LOG_TAG, "isCarrierConfigEnableNr: CarrierConfigManager is null");
+            return false;
+        }
+        PersistableBundle config = carrierConfigManager.getConfigForSubId(mPhone.getSubId());
+        if (config == null) {
+            Rlog.e(LOG_TAG, "isCarrierConfigEnableNr: Cannot get config " + mPhone.getSubId());
+            return false;
+        }
+        return config.getBoolean(CarrierConfigManager.KEY_NR_ENABLED_BOOL);
+    }
+
+    private boolean checkSupportedBitmask(@NetworkTypeBitMask long supportedBitmask,
+            @NetworkTypeBitMask long targetBitmask) {
+        return (targetBitmask & supportedBitmask) == targetBitmask;
     }
 
     private void handleConfigChanges() {
@@ -238,7 +282,7 @@
             Rlog.i(LOG_TAG, "starting timer for notifications." + notificationType.getTypeId());
             sendMessageDelayed(notificationMsg, getDelay(notificationType));
         } else {
-            cancelNotification(notificationType.getTypeId());
+            cancelNotification(notificationType);
             Rlog.i(LOG_TAG, "canceling notifications: " + notificationType.getTypeId());
         }
     }
@@ -307,17 +351,18 @@
                 .setSmallIcon(com.android.internal.R.drawable.stat_sys_warning)
                 .setColor(context.getResources().getColor(
                        com.android.internal.R.color.system_notification_accent_color));
-
-        getNotificationManager(context).notify(notificationType.getTypeId(), builder.build());
+        getNotificationManager(context).notify(notificationType.getNotificationTag(),
+                notificationType.getNotificationId(), builder.build());
     }
 
     /**
      * Cancel notifications if a registration is pending or has been sent.
      **/
-    public void cancelNotification(int notificationId) {
+    public void cancelNotification(NotificationType notificationType) {
         Context context = mPhone.getContext();
-        removeMessages(notificationId);
-        getNotificationManager(context).cancel(notificationId);
+        removeMessages(notificationType.getTypeId());
+        getNotificationManager(context).cancel(
+                notificationType.getNotificationTag(), notificationType.getNotificationId());
     }
 
     /**
@@ -353,6 +398,16 @@
         int getTypeId();
 
         /**
+         * returns notification id.
+         **/
+        int getNotificationId();
+
+        /**
+         * returns notification tag.
+         **/
+        String getNotificationTag();
+
+        /**
          * returns the notification builder, for the notification to be displayed.
          **/
         Notification.Builder getNotificationBuilder();
@@ -392,6 +447,14 @@
             return mTypeId;
         }
 
+        public int getNotificationId() {
+            return mPhone.getSubId();
+        }
+
+        public String getNotificationTag() {
+            return PREF_NETWORK_NOTIFICATION_TAG;
+        }
+
         /**
          * Contains logic on sending notifications.
          */
@@ -415,22 +478,23 @@
             notificationIntent.putExtra("expandable", true);
             PendingIntent settingsIntent = PendingIntent.getActivity(context, 0, notificationIntent,
                     PendingIntent.FLAG_ONE_SHOT | PendingIntent.FLAG_IMMUTABLE);
-            CharSequence title = context.getText(
+            Resources res = SubscriptionManager.getResourcesForSubId(context, mPhone.getSubId());
+            CharSequence title = res.getText(
                     com.android.internal.R.string.NetworkPreferenceSwitchTitle);
-            CharSequence details = context.getText(
+            CharSequence details = res.getText(
                     com.android.internal.R.string.NetworkPreferenceSwitchSummary);
             return new Notification.Builder(context)
                     .setContentTitle(title)
                     .setStyle(new Notification.BigTextStyle().bigText(details))
                     .setContentText(details)
-                    .setChannel(NotificationChannelController.CHANNEL_ID_ALERT)
+                    .setChannelId(NotificationChannelController.CHANNEL_ID_ALERT)
                     .setContentIntent(settingsIntent);
         }
     }
 
     /**
-     * Class that defines the emergency notification, which is shown when the user is out of cell
-     * connectivity, but has wifi enabled.
+     * Class that defines the emergency notification, which is shown when Wi-Fi Calling is
+     * available.
      */
     public class EmergencyNetworkNotification implements NotificationType {
 
@@ -462,15 +526,22 @@
             return mTypeId;
         }
 
+        public int getNotificationId() {
+            return mPhone.getSubId();
+        }
+
+        public String getNotificationTag() {
+            return EMERGENCY_NOTIFICATION_TAG;
+        }
+
         /**
          * Contains logic on sending notifications,
          */
         public boolean sendMessage() {
             Rlog.i(LOG_TAG, "EmergencyNetworkNotification: sendMessage() w/values: "
-                    + "," + isPhoneVoiceRegistered() + "," + mDelay + ","
-                    + isPhoneRegisteredForWifiCalling() + "," + mSST.isRadioOn());
-            if (mDelay == UNINITIALIZED_DELAY_VALUE || isPhoneVoiceRegistered()
-                    || !isPhoneRegisteredForWifiCalling()) {
+                    + "," + mDelay + "," + isPhoneRegisteredForWifiCalling() + ","
+                    + mSST.isRadioOn());
+            if (mDelay == UNINITIALIZED_DELAY_VALUE || !isPhoneRegisteredForWifiCalling()) {
                 return false;
             }
             return true;
@@ -481,15 +552,17 @@
          */
         public Notification.Builder getNotificationBuilder() {
             Context context = mPhone.getContext();
-            CharSequence title = context.getText(
+            Resources res = SubscriptionManager.getResourcesForSubId(context, mPhone.getSubId());
+            CharSequence title = res.getText(
                     com.android.internal.R.string.EmergencyCallWarningTitle);
-            CharSequence details = context.getText(
+            CharSequence details = res.getText(
                     com.android.internal.R.string.EmergencyCallWarningSummary);
             return new Notification.Builder(context)
                     .setContentTitle(title)
                     .setStyle(new Notification.BigTextStyle().bigText(details))
                     .setContentText(details)
-                    .setChannel(NotificationChannelController.CHANNEL_ID_WFC);
+                    .setFlag(Notification.FLAG_NO_CLEAR, true)
+                    .setChannelId(NotificationChannelController.CHANNEL_ID_WFC);
         }
     }
 }
diff --git a/src/java/com/android/internal/telephony/CarrierServicesSmsFilter.java b/src/java/com/android/internal/telephony/CarrierServicesSmsFilter.java
index 7761680..c8404b4 100644
--- a/src/java/com/android/internal/telephony/CarrierServicesSmsFilter.java
+++ b/src/java/com/android/internal/telephony/CarrierServicesSmsFilter.java
@@ -23,18 +23,16 @@
 import android.os.Binder;
 import android.os.Handler;
 import android.os.Message;
-import android.os.RemoteException;
 import android.service.carrier.CarrierMessagingService;
-import android.service.carrier.ICarrierMessagingCallback;
-import android.service.carrier.ICarrierMessagingService;
+import android.service.carrier.CarrierMessagingServiceWrapper;
+import android.service.carrier.CarrierMessagingServiceWrapper.CarrierMessagingCallbackWrapper;
 import android.service.carrier.MessagePdu;
-import android.telephony.CarrierMessagingServiceManager;
-import android.telephony.Rlog;
 import android.util.LocalLog;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.telephony.uicc.UiccCard;
 import com.android.internal.telephony.uicc.UiccController;
+import com.android.telephony.Rlog;
 
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -64,6 +62,7 @@
     private final String mLogTag;
     private final CallbackTimeoutHandler mCallbackTimeoutHandler;
     private final LocalLog mLocalLog;
+    private final long mMessageId;
     private FilterAggregator mFilterAggregator;
 
     @VisibleForTesting
@@ -75,7 +74,8 @@
             String pduFormat,
             CarrierServicesSmsFilterCallbackInterface carrierServicesSmsFilterCallback,
             String logTag,
-            LocalLog localLog) {
+            LocalLog localLog,
+            long msgId) {
         mContext = context;
         mPhone = phone;
         mPdus = pdus;
@@ -85,6 +85,7 @@
         mLogTag = logTag;
         mCallbackTimeoutHandler = new CallbackTimeoutHandler();
         mLocalLog = localLog;
+        mMessageId = msgId;
     }
 
     /**
@@ -104,7 +105,8 @@
         }
 
         if (mFilterAggregator != null) {
-            String errMsg = "Cannot reuse the same CarrierServiceSmsFilter object for filtering.";
+            String errMsg = "filter: Cannot reuse the same CarrierServiceSmsFilter object for "
+                    + "filtering";
             loge(errMsg);
             throw new RuntimeException(errMsg);
         }
@@ -133,10 +135,11 @@
                     mContext.getPackageManager(),
                     new Intent(CarrierMessagingService.SERVICE_INTERFACE));
         } else {
-            Rlog.e(mLogTag, "UiccCard not initialized.");
+            loge("getCarrierAppPackageForFiltering: UiccCard not initialized");
         }
         if (carrierPackages != null && carrierPackages.size() == 1) {
-            log("Found carrier package.");
+            log("getCarrierAppPackageForFiltering: Found carrier package: "
+                    + carrierPackages.get(0));
             return Optional.of(carrierPackages.get(0));
         }
 
@@ -146,21 +149,22 @@
                 getSystemAppForIntent(new Intent(CarrierMessagingService.SERVICE_INTERFACE));
 
         if (systemPackages != null && systemPackages.size() == 1) {
-            log("Found system package.");
+            log("getCarrierAppPackageForFiltering: Found system package: " + systemPackages.get(0));
             return Optional.of(systemPackages.get(0));
         }
-        logv("Unable to find carrier package: " + carrierPackages
-                + ", nor systemPackages: " + systemPackages);
+        logv("getCarrierAppPackageForFiltering: Unable to find carrierPackages: " + carrierPackages
+                + " or systemPackages: " + systemPackages);
         return Optional.empty();
     }
 
     private void filterWithPackage(String packageName, FilterAggregator filterAggregator) {
-        CarrierSmsFilter smsFilter = new CarrierSmsFilter(mPdus, mDestPort, mPduFormat);
+        CarrierSmsFilter smsFilter = new CarrierSmsFilter(mPdus, mDestPort, mPduFormat,
+                packageName);
         CarrierSmsFilterCallback smsFilterCallback =
-                new CarrierSmsFilterCallback(filterAggregator, smsFilter);
+                new CarrierSmsFilterCallback(filterAggregator, smsFilter, packageName);
         filterAggregator.addToCallbacks(smsFilterCallback);
 
-        smsFilter.filterSms(packageName, smsFilterCallback);
+        smsFilter.filterSms(smsFilterCallback);
     }
 
     private List<String> getSystemAppForIntent(Intent intent) {
@@ -171,7 +175,7 @@
 
         for (ResolveInfo info : receivers) {
             if (info.serviceInfo == null) {
-                loge("Can't get service information from " + info);
+                loge("getSystemAppForIntent: Can't get service information from " + info);
                 continue;
             }
             String packageName = info.serviceInfo.packageName;
@@ -185,15 +189,15 @@
     }
 
     private void log(String message) {
-        Rlog.d(mLogTag, message);
+        Rlog.d(mLogTag, message + ", id: " + mMessageId);
     }
 
     private void loge(String message) {
-        Rlog.e(mLogTag, message);
+        Rlog.e(mLogTag, message + ", id: " + mMessageId);
     }
 
     private void logv(String message) {
-        Rlog.e(mLogTag, message);
+        Rlog.v(mLogTag, message + ", id: " + mMessageId);
     }
 
     /**
@@ -209,30 +213,33 @@
      * instructed to do so by the carrier messaging service. A new instance must be used for every
      * message.
      */
-    private final class CarrierSmsFilter extends CarrierMessagingServiceManager {
+    private final class CarrierSmsFilter extends CarrierMessagingServiceWrapper {
         private final byte[][] mPdus;
         private final int mDestPort;
         private final String mSmsFormat;
         // Instantiated in filterSms.
         private volatile CarrierSmsFilterCallback mSmsFilterCallback;
+        private final String mPackageName;
 
-        CarrierSmsFilter(byte[][] pdus, int destPort, String smsFormat) {
+        CarrierSmsFilter(byte[][] pdus, int destPort, String smsFormat, String packageName) {
             mPdus = pdus;
             mDestPort = destPort;
             mSmsFormat = smsFormat;
+            mPackageName = packageName;
         }
 
         /**
-         * Attempts to bind to a {@link ICarrierMessagingService}. Filtering is initiated
-         * asynchronously once the service is ready using {@link #onServiceReady}.
+         * Attempts to bind to a {@link CarrierMessagingService}. Filtering is initiated
+         * asynchronously once the service is ready using {@link #onServiceReady()}.
          */
-        void filterSms(String carrierPackageName, CarrierSmsFilterCallback smsFilterCallback) {
+        void filterSms(CarrierSmsFilterCallback smsFilterCallback) {
             mSmsFilterCallback = smsFilterCallback;
-            if (!bindToCarrierMessagingService(mContext, carrierPackageName)) {
-                loge("bindService() for carrier messaging service failed");
+            if (!bindToCarrierMessagingService(mContext, mPackageName)) {
+                loge("CarrierSmsFilter::filterSms: bindService() for failed for " + mPackageName);
                 smsFilterCallback.onFilterComplete(CarrierMessagingService.RECEIVE_OPTIONS_DEFAULT);
             } else {
-                logv("bindService() for carrier messaging service succeeded");
+                logv("CarrierSmsFilter::filterSms: bindService() for succeeded for "
+                        + mPackageName);
             }
         }
 
@@ -241,14 +248,13 @@
          * delivered to {@code smsFilterCallback}.
          */
         @Override
-        protected void onServiceReady(ICarrierMessagingService carrierMessagingService) {
+        public void onServiceReady() {
             try {
-                log("onServiceReady: calling filterSms");
-                carrierMessagingService.filterSms(
-                        new MessagePdu(Arrays.asList(mPdus)), mSmsFormat, mDestPort,
+                log("onServiceReady: calling filterSms on " + mPackageName);
+                filterSms(new MessagePdu(Arrays.asList(mPdus)), mSmsFormat, mDestPort,
                         mPhone.getSubId(), mSmsFilterCallback);
-            } catch (RemoteException e) {
-                loge("Exception filtering the SMS: " + e);
+            } catch (RuntimeException e) {
+                loge("Exception filtering the SMS with " + mPackageName + ": " + e);
                 mSmsFilterCallback.onFilterComplete(
                         CarrierMessagingService.RECEIVE_OPTIONS_DEFAULT);
             }
@@ -259,16 +265,18 @@
      * A callback used to notify the platform of the carrier messaging app filtering result. Once
      * the result is ready, the carrier messaging service connection is disposed.
      */
-    private final class CarrierSmsFilterCallback extends ICarrierMessagingCallback.Stub {
+    private final class CarrierSmsFilterCallback extends CarrierMessagingCallbackWrapper {
         private final FilterAggregator mFilterAggregator;
-        private final CarrierMessagingServiceManager mCarrierMessagingServiceManager;
+        private final CarrierMessagingServiceWrapper mCarrierMessagingServiceWrapper;
         private boolean mIsOnFilterCompleteCalled;
+        private final String mPackageName;
 
         CarrierSmsFilterCallback(FilterAggregator filterAggregator,
-                CarrierMessagingServiceManager carrierMessagingServiceManager) {
+                CarrierMessagingServiceWrapper carrierMessagingServiceWrapper, String packageName) {
             mFilterAggregator = filterAggregator;
-            mCarrierMessagingServiceManager = carrierMessagingServiceManager;
+            mCarrierMessagingServiceWrapper = carrierMessagingServiceWrapper;
             mIsOnFilterCompleteCalled = false;
+            mPackageName = packageName;
         }
 
         /**
@@ -276,34 +284,39 @@
          */
         @Override
         public void onFilterComplete(int result) {
-            log("onFilterComplete called with result: " + result);
+            log("CarrierSmsFilterCallback::onFilterComplete: Called from " + mPackageName
+                    + " with result: " + result);
             // in the case that timeout has already passed and triggered, but the initial callback
             // is run afterwards, we should not follow through
             if (!mIsOnFilterCompleteCalled) {
                 mIsOnFilterCompleteCalled = true;
-                mCarrierMessagingServiceManager.disposeConnection(mContext);
+                mCarrierMessagingServiceWrapper.disposeConnection(mContext);
                 mFilterAggregator.onFilterComplete(result);
             }
         }
 
         @Override
         public void onSendSmsComplete(int result, int messageRef) {
-            loge("Unexpected onSendSmsComplete call with result: " + result);
+            loge("onSendSmsComplete: Unexpected call from " + mPackageName
+                    + " with result: " + result);
         }
 
         @Override
         public void onSendMultipartSmsComplete(int result, int[] messageRefs) {
-            loge("Unexpected onSendMultipartSmsComplete call with result: " + result);
+            loge("onSendMultipartSmsComplete: Unexpected call from " + mPackageName
+                    + " with result: " + result);
         }
 
         @Override
         public void onSendMmsComplete(int result, byte[] sendConfPdu) {
-            loge("Unexpected onSendMmsComplete call with result: " + result);
+            loge("onSendMmsComplete: Unexpected call from " + mPackageName
+                    + " with result: " + result);
         }
 
         @Override
         public void onDownloadMmsComplete(int result) {
-            loge("Unexpected onDownloadMmsComplete call with result: " + result);
+            loge("onDownloadMmsComplete: Unexpected call from " + mPackageName
+                    + " with result: " + result);
         }
     }
 
@@ -335,10 +348,12 @@
                     }
                     //all onFilterCompletes called before timeout has triggered
                     //remove the pending message
-                    log("onFilterComplete: called successfully with result = " + result);
+                    log("FilterAggregator::onFilterComplete: called successfully with result = "
+                            + result);
                     mCallbackTimeoutHandler.removeMessages(EVENT_ON_FILTER_COMPLETE_NOT_CALLED);
                 } else {
-                    log("onFilterComplete: waiting for pending filters " + mNumPendingFilters);
+                    log("FilterAggregator::onFilterComplete: waiting for pending filters "
+                            + mNumPendingFilters);
                 }
             }
         }
@@ -360,7 +375,7 @@
         @Override
         public void handleMessage(Message msg) {
             if (DBG) {
-                log("CallbackTimeoutHandler handleMessage(" + msg.what + ")");
+                log("CallbackTimeoutHandler: handleMessage(" + msg.what + ")");
             }
 
             switch(msg.what) {
diff --git a/src/java/com/android/internal/telephony/CarrierSignalAgent.java b/src/java/com/android/internal/telephony/CarrierSignalAgent.java
index ad753db..ed7eb47 100644
--- a/src/java/com/android/internal/telephony/CarrierSignalAgent.java
+++ b/src/java/com/android/internal/telephony/CarrierSignalAgent.java
@@ -15,6 +15,9 @@
  */
 package com.android.internal.telephony;
 
+import static android.telephony.CarrierConfigManager.KEY_CARRIER_APP_NO_WAKE_SIGNAL_CONFIG_STRING_ARRAY;
+import static android.telephony.CarrierConfigManager.KEY_CARRIER_APP_WAKE_SIGNAL_CONFIG_STRING_ARRAY;
+
 import android.content.ActivityNotFoundException;
 import android.content.BroadcastReceiver;
 import android.content.ComponentName;
@@ -30,14 +33,15 @@
 import android.os.PersistableBundle;
 import android.os.UserHandle;
 import android.telephony.CarrierConfigManager;
-import android.telephony.Rlog;
 import android.telephony.SubscriptionManager;
+import android.telephony.TelephonyManager;
 import android.text.TextUtils;
 import android.util.LocalLog;
 import android.util.Log;
 
-import com.android.internal.util.ArrayUtils;
+import com.android.internal.telephony.util.ArrayUtils;
 import com.android.internal.util.IndentingPrintWriter;
+import com.android.telephony.Rlog;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
@@ -47,9 +51,6 @@
 import java.util.Map;
 import java.util.Set;
 
-import static android.telephony.CarrierConfigManager.KEY_CARRIER_APP_WAKE_SIGNAL_CONFIG_STRING_ARRAY;
-import static android.telephony.CarrierConfigManager.KEY_CARRIER_APP_NO_WAKE_SIGNAL_CONFIG_STRING_ARRAY;
-
 /**
  * This class act as an CarrierSignalling Agent.
  * it load registered carrier signalling receivers from carrier config, cache the result to avoid
@@ -98,11 +99,11 @@
      * This is a list of supported signals from CarrierSignalAgent
      */
     private final Set<String> mCarrierSignalList = new HashSet<>(Arrays.asList(
-            TelephonyIntents.ACTION_CARRIER_SIGNAL_PCO_VALUE,
-            TelephonyIntents.ACTION_CARRIER_SIGNAL_REDIRECTED,
-            TelephonyIntents.ACTION_CARRIER_SIGNAL_REQUEST_NETWORK_FAILED,
-            TelephonyIntents.ACTION_CARRIER_SIGNAL_RESET,
-            TelephonyIntents.ACTION_CARRIER_SIGNAL_DEFAULT_NETWORK_AVAILABLE));
+            TelephonyManager.ACTION_CARRIER_SIGNAL_PCO_VALUE,
+            TelephonyManager.ACTION_CARRIER_SIGNAL_REDIRECTED,
+            TelephonyManager.ACTION_CARRIER_SIGNAL_REQUEST_NETWORK_FAILED,
+            TelephonyManager.ACTION_CARRIER_SIGNAL_RESET,
+            TelephonyManager.ACTION_CARRIER_SIGNAL_DEFAULT_NETWORK_AVAILABLE));
 
     private final LocalLog mErrorLocalLog = new LocalLog(20);
 
@@ -139,8 +140,8 @@
                     Rlog.e(LOG_TAG, "Register default network exception: " + ar.exception);
                     return;
                 }
-                final ConnectivityManager connectivityMgr =  ConnectivityManager
-                        .from(mPhone.getContext());
+                final ConnectivityManager connectivityMgr =  mPhone.getContext()
+                        .getSystemService(ConnectivityManager.class);
                 if ((boolean) ar.result) {
                     mNetworkCallback = new ConnectivityManager.NetworkCallback() {
                         @Override
@@ -148,10 +149,10 @@
                             // an optimization to avoid signaling on every default network switch.
                             if (!mDefaultNetworkAvail) {
                                 if (DBG) log("Default network available: " + network);
-                                Intent intent = new Intent(TelephonyIntents
+                                Intent intent = new Intent(TelephonyManager
                                         .ACTION_CARRIER_SIGNAL_DEFAULT_NETWORK_AVAILABLE);
                                 intent.putExtra(
-                                        TelephonyIntents.EXTRA_DEFAULT_NETWORK_AVAILABLE_KEY, true);
+                                        TelephonyManager.EXTRA_DEFAULT_NETWORK_AVAILABLE, true);
                                 notifyCarrierSignalReceivers(intent);
                                 mDefaultNetworkAvail = true;
                             }
@@ -159,10 +160,10 @@
                         @Override
                         public void onLost(Network network) {
                             if (DBG) log("Default network lost: " + network);
-                            Intent intent = new Intent(TelephonyIntents
+                            Intent intent = new Intent(TelephonyManager
                                     .ACTION_CARRIER_SIGNAL_DEFAULT_NETWORK_AVAILABLE);
                             intent.putExtra(
-                                    TelephonyIntents.EXTRA_DEFAULT_NETWORK_AVAILABLE_KEY, false);
+                                    TelephonyManager.EXTRA_DEFAULT_NETWORK_AVAILABLE, false);
                             notifyCarrierSignalReceivers(intent);
                             mDefaultNetworkAvail = false;
                         }
@@ -315,8 +316,7 @@
                 continue;
             }
 
-            signal.putExtra(SubscriptionManager.EXTRA_SUBSCRIPTION_INDEX, mPhone.getSubId());
-            signal.putExtra(PhoneConstants.SUBSCRIPTION_KEY, mPhone.getSubId());
+            SubscriptionManager.putSubscriptionIdExtra(signal, mPhone.getSubId());
             signal.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
             if (!wakeup) signal.setFlags(Intent.FLAG_EXCLUDE_STOPPED_PACKAGES);
 
diff --git a/src/java/com/android/internal/telephony/CarrierSmsUtils.java b/src/java/com/android/internal/telephony/CarrierSmsUtils.java
index a64aea7..f78d147 100644
--- a/src/java/com/android/internal/telephony/CarrierSmsUtils.java
+++ b/src/java/com/android/internal/telephony/CarrierSmsUtils.java
@@ -24,7 +24,8 @@
 import android.os.Binder;
 import android.os.PersistableBundle;
 import android.telephony.CarrierConfigManager;
-import android.telephony.Rlog;
+
+import com.android.telephony.Rlog;
 
 import java.util.List;
 
diff --git a/src/java/com/android/internal/telephony/CellBroadcastHandler.java b/src/java/com/android/internal/telephony/CellBroadcastHandler.java
deleted file mode 100644
index 50f3361..0000000
--- a/src/java/com/android/internal/telephony/CellBroadcastHandler.java
+++ /dev/null
@@ -1,451 +0,0 @@
-/*
- * Copyright (C) 2013 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;
-
-import static android.content.PermissionChecker.PERMISSION_GRANTED;
-import static android.provider.Settings.Secure.CMAS_ADDITIONAL_BROADCAST_PKG;
-
-import android.Manifest;
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.app.Activity;
-import android.app.AppOpsManager;
-import android.content.ContentValues;
-import android.content.Context;
-import android.content.Intent;
-import android.content.PermissionChecker;
-import android.location.Location;
-import android.location.LocationListener;
-import android.location.LocationManager;
-import android.net.Uri;
-import android.os.Build;
-import android.os.Bundle;
-import android.os.Handler;
-import android.os.Looper;
-import android.os.Message;
-import android.os.UserHandle;
-import android.provider.Settings;
-import android.provider.Telephony;
-import android.provider.Telephony.CellBroadcasts;
-import android.telephony.SmsCbMessage;
-import android.telephony.SubscriptionManager;
-import android.text.format.DateUtils;
-import android.util.LocalLog;
-import android.util.Log;
-
-import com.android.internal.telephony.CbGeoUtils.Geometry;
-import com.android.internal.telephony.CbGeoUtils.LatLng;
-import com.android.internal.telephony.metrics.TelephonyMetrics;
-
-import java.io.FileDescriptor;
-import java.io.PrintWriter;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
-
-/**
- * Dispatch new Cell Broadcasts to receivers. Acquires a private wakelock until the broadcast
- * completes and our result receiver is called.
- */
-public class CellBroadcastHandler extends WakeLockStateMachine {
-    private static final String EXTRA_MESSAGE = "message";
-
-    private final LocalLog mLocalLog = new LocalLog(100);
-
-    protected static final Uri CELL_BROADCAST_URI = Uri.parse("content://cellbroadcasts_fwk");
-
-    /** Uses to request the location update. */
-    public final LocationRequester mLocationRequester;
-
-    private CellBroadcastHandler(Context context, Phone phone) {
-        this("CellBroadcastHandler", context, phone);
-    }
-
-    protected CellBroadcastHandler(String debugTag, Context context, Phone phone) {
-        super(debugTag, context, phone);
-        mLocationRequester = new LocationRequester(
-                context,
-                (LocationManager) mContext.getSystemService(Context.LOCATION_SERVICE),
-                getHandler().getLooper());
-    }
-
-    /**
-     * Create a new CellBroadcastHandler.
-     * @param context the context to use for dispatching Intents
-     * @return the new handler
-     */
-    public static CellBroadcastHandler makeCellBroadcastHandler(Context context, Phone phone) {
-        CellBroadcastHandler handler = new CellBroadcastHandler(context, phone);
-        handler.start();
-        return handler;
-    }
-
-    /**
-     * Handle Cell Broadcast messages from {@code CdmaInboundSmsHandler}.
-     * 3GPP-format Cell Broadcast messages sent from radio are handled in the subclass.
-     *
-     * @param message the message to process
-     * @return true if need to wait for geo-fencing or an ordered broadcast was sent.
-     */
-    @Override
-    protected boolean handleSmsMessage(Message message) {
-        if (message.obj instanceof SmsCbMessage) {
-            handleBroadcastSms((SmsCbMessage) message.obj);
-            return true;
-        } else {
-            loge("handleMessage got object of type: " + message.obj.getClass().getName());
-            return false;
-        }
-    }
-
-    /**
-     * Dispatch a Cell Broadcast message to listeners.
-     * @param message the Cell Broadcast to broadcast
-     */
-    protected void handleBroadcastSms(SmsCbMessage message) {
-        // Log Cellbroadcast msg received event
-        TelephonyMetrics metrics = TelephonyMetrics.getInstance();
-        metrics.writeNewCBSms(mPhone.getPhoneId(), message.getMessageFormat(),
-                message.getMessagePriority(), message.isCmasMessage(), message.isEtwsMessage(),
-                message.getServiceCategory(), message.getSerialNumber(),
-                System.currentTimeMillis());
-
-        // TODO: Database inserting can be time consuming, therefore this should be changed to
-        // asynchronous.
-        ContentValues cv = message.getContentValues();
-        Uri uri = mContext.getContentResolver().insert(CELL_BROADCAST_URI, cv);
-
-        if (message.needGeoFencingCheck()) {
-            if (DBG) {
-                log("Request location update for geo-fencing. serialNumber = "
-                        + message.getSerialNumber());
-            }
-
-            requestLocationUpdate(location -> {
-                if (location == null) {
-                    // Broadcast the message directly if the location is not available.
-                    broadcastMessage(message, uri);
-                } else {
-                    performGeoFencing(message, uri, message.getGeometries(), location);
-                }
-            }, message.getMaximumWaitingTime());
-        } else {
-            if (DBG) {
-                log("Broadcast the message directly because no geo-fencing required, "
-                        + "serialNumber = " + message.getSerialNumber()
-                        + " needGeoFencing = " + message.needGeoFencingCheck());
-            }
-            broadcastMessage(message, uri);
-        }
-    }
-
-    /**
-     * Perform a geo-fencing check for {@code message}. Broadcast the {@code message} if the
-     * {@code location} is inside the {@code broadcastArea}.
-     * @param message the message need to geo-fencing check
-     * @param uri the message's uri
-     * @param broadcastArea the broadcast area of the message
-     * @param location current location
-     */
-    protected void performGeoFencing(SmsCbMessage message, Uri uri, List<Geometry> broadcastArea,
-            LatLng location) {
-
-        if (DBG) {
-            logd("Perform geo-fencing check for message identifier = "
-                    + message.getServiceCategory()
-                    + " serialNumber = " + message.getSerialNumber());
-        }
-
-        for (Geometry geo : broadcastArea) {
-            if (geo.contains(location)) {
-                broadcastMessage(message, uri);
-                return;
-            }
-        }
-
-        if (DBG) {
-            logd("Device location is outside the broadcast area "
-                    + CbGeoUtils.encodeGeometriesToString(broadcastArea));
-        }
-
-        sendMessage(EVENT_BROADCAST_NOT_REQUIRED);
-    }
-
-    /**
-     * Request a single location update.
-     * @param callback a callback will be called when the location is available.
-     * @param maximumWaitTimeSec the maximum wait time of this request. If location is not updated
-     * within the maximum wait time, {@code callback#onLocationUpadte(null)} will be called.
-     */
-    protected void requestLocationUpdate(LocationUpdateCallback callback, int maximumWaitTimeSec) {
-        mLocationRequester.requestLocationUpdate(callback, maximumWaitTimeSec);
-    }
-
-    /**
-     * Broadcast a list of cell broadcast messages.
-     * @param cbMessages a list of cell broadcast message.
-     * @param cbMessageUris the corresponding {@link Uri} of the cell broadcast messages.
-     */
-    protected void broadcastMessage(List<SmsCbMessage> cbMessages, List<Uri> cbMessageUris) {
-        for (int i = 0; i < cbMessages.size(); i++) {
-            broadcastMessage(cbMessages.get(i), cbMessageUris.get(i));
-        }
-    }
-
-    /**
-     * Broadcast the {@code message} to the applications.
-     * @param message a message need to broadcast
-     * @param messageUri message's uri
-     */
-    protected void broadcastMessage(@NonNull SmsCbMessage message, @Nullable Uri messageUri) {
-        String receiverPermission;
-        int appOp;
-        String msg;
-        Intent intent;
-        if (message.isEmergencyMessage()) {
-            msg = "Dispatching emergency SMS CB, SmsCbMessage is: " + message;
-            log(msg);
-            mLocalLog.log(msg);
-            intent = new Intent(Telephony.Sms.Intents.SMS_EMERGENCY_CB_RECEIVED_ACTION);
-            //Emergency alerts need to be delivered with high priority
-            intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
-            receiverPermission = Manifest.permission.RECEIVE_EMERGENCY_BROADCAST;
-            appOp = AppOpsManager.OP_RECEIVE_EMERGECY_SMS;
-
-            intent.putExtra(EXTRA_MESSAGE, message);
-            SubscriptionManager.putPhoneIdAndSubIdExtra(intent, mPhone.getPhoneId());
-
-            if (Build.IS_DEBUGGABLE) {
-                // Send additional broadcast intent to the specified package. This is only for sl4a
-                // automation tests.
-                final String additionalPackage = Settings.Secure.getString(
-                        mContext.getContentResolver(), CMAS_ADDITIONAL_BROADCAST_PKG);
-                if (additionalPackage != null) {
-                    Intent additionalIntent = new Intent(intent);
-                    additionalIntent.setPackage(additionalPackage);
-                    mContext.sendOrderedBroadcastAsUser(additionalIntent, UserHandle.ALL,
-                            receiverPermission, appOp, null, getHandler(), Activity.RESULT_OK,
-                            null, null);
-                }
-            }
-
-            String[] pkgs = mContext.getResources().getStringArray(
-                    com.android.internal.R.array.config_defaultCellBroadcastReceiverPkgs);
-            mReceiverCount.addAndGet(pkgs.length);
-            for (String pkg : pkgs) {
-                // Explicitly send the intent to all the configured cell broadcast receivers.
-                intent.setPackage(pkg);
-                mContext.sendOrderedBroadcastAsUser(intent, UserHandle.ALL, receiverPermission,
-                        appOp, mReceiver, getHandler(), Activity.RESULT_OK, null, null);
-            }
-        } else {
-            msg = "Dispatching SMS CB, SmsCbMessage is: " + message;
-            log(msg);
-            mLocalLog.log(msg);
-            intent = new Intent(Telephony.Sms.Intents.SMS_CB_RECEIVED_ACTION);
-            // Send implicit intent since there are various 3rd party carrier apps listen to
-            // this intent.
-            intent.addFlags(Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
-            receiverPermission = Manifest.permission.RECEIVE_SMS;
-            appOp = AppOpsManager.OP_RECEIVE_SMS;
-
-            intent.putExtra(EXTRA_MESSAGE, message);
-            SubscriptionManager.putPhoneIdAndSubIdExtra(intent, mPhone.getPhoneId());
-
-            mReceiverCount.incrementAndGet();
-            mContext.sendOrderedBroadcastAsUser(intent, UserHandle.ALL, receiverPermission, appOp,
-                    mReceiver, getHandler(), Activity.RESULT_OK, null, null);
-        }
-
-        if (messageUri != null) {
-            ContentValues cv = new ContentValues();
-            cv.put(CellBroadcasts.MESSAGE_BROADCASTED, 1);
-            mContext.getContentResolver().update(CELL_BROADCAST_URI, cv,
-                    CellBroadcasts._ID + "=?", new String[] {messageUri.getLastPathSegment()});
-        }
-    }
-
-    @Override
-    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
-        pw.println("CellBroadcastHandler:");
-        mLocalLog.dump(fd, pw, args);
-        pw.flush();
-    }
-
-    /** The callback interface of a location request. */
-    public interface LocationUpdateCallback {
-        /**
-         * Call when the location update is available.
-         * @param location a location in (latitude, longitude) format, or {@code null} if the
-         * location service is not available.
-         */
-        void onLocationUpdate(@Nullable LatLng location);
-    }
-
-    private static final class LocationRequester {
-        private static final String TAG = CellBroadcastHandler.class.getSimpleName();
-
-        /**
-         * Use as the default maximum wait time if the cell broadcast doesn't specify the value.
-         * Most of the location request should be responded within 30 seconds.
-         */
-        private static final int DEFAULT_MAXIMUM_WAIT_TIME_SEC = 30;
-
-        /**
-         * Trigger this event when the {@link LocationManager} is not responded within the given
-         * time.
-         */
-        private static final int EVENT_LOCATION_REQUEST_TIMEOUT = 1;
-
-        /** Request a single location update. */
-        private static final int EVENT_REQUEST_LOCATION_UPDATE = 2;
-
-        /**
-         * Request location update from network or gps location provider. Network provider will be
-         * used if available, otherwise use the gps provider.
-         */
-        private static final List<String> LOCATION_PROVIDERS = Arrays.asList(
-                LocationManager.NETWORK_PROVIDER, LocationManager.GPS_PROVIDER);
-
-        private final LocationManager mLocationManager;
-        private final Looper mLooper;
-        private final List<LocationUpdateCallback> mCallbacks;
-        private final Context mContext;
-        private Handler mLocationHandler;
-
-        LocationRequester(Context context, LocationManager locationManager, Looper looper) {
-            mLocationManager = locationManager;
-            mLooper = looper;
-            mCallbacks = new ArrayList<>();
-            mContext = context;
-            mLocationHandler = new LocationHandler(looper);
-        }
-
-        /**
-         * Request a single location update. If the location is not available, a callback with
-         * {@code null} location will be called immediately.
-         *
-         * @param callback a callback to the the response when the location is available
-         * @param maximumWaitTimeSec the maximum wait time of this request. If location is not
-         * updated within the maximum wait time, {@code callback#onLocationUpadte(null)} will be
-         * called.
-         */
-        void requestLocationUpdate(@NonNull LocationUpdateCallback callback,
-                int maximumWaitTimeSec) {
-            mLocationHandler.obtainMessage(EVENT_REQUEST_LOCATION_UPDATE, maximumWaitTimeSec,
-                    0 /* arg2 */, callback).sendToTarget();
-        }
-
-        private void onLocationUpdate(@Nullable LatLng location) {
-            for (LocationUpdateCallback callback : mCallbacks) {
-                callback.onLocationUpdate(location);
-            }
-            mCallbacks.clear();
-
-            for (LocationListener listener : mLocationListenerList) {
-                mLocationManager.removeUpdates(listener);
-            }
-            mLocationListenerList.clear();
-        }
-
-        private void requestLocationUpdateInternal(@NonNull LocationUpdateCallback callback,
-                int maximumWaitTimeSec) {
-            if (DBG) Log.d(TAG, "requestLocationUpdate");
-            if (!isLocationServiceAvailable()) {
-                if (DBG) {
-                    Log.d(TAG, "Can't request location update because of no location permission");
-                }
-                callback.onLocationUpdate(null);
-                return;
-            }
-
-            if (!mLocationHandler.hasMessages(EVENT_LOCATION_REQUEST_TIMEOUT)) {
-                if (maximumWaitTimeSec == SmsCbMessage.MAXIMUM_WAIT_TIME_NOT_SET) {
-                    maximumWaitTimeSec = DEFAULT_MAXIMUM_WAIT_TIME_SEC;
-                }
-                mLocationHandler.sendMessageDelayed(
-                        mLocationHandler.obtainMessage(EVENT_LOCATION_REQUEST_TIMEOUT),
-                        maximumWaitTimeSec * DateUtils.SECOND_IN_MILLIS);
-            }
-
-            mCallbacks.add(callback);
-
-            for (String provider : LOCATION_PROVIDERS) {
-                if (mLocationManager.isProviderEnabled(provider)) {
-                    LocationListener listener = new LocationListener() {
-                        @Override
-                        public void onLocationChanged(Location location) {
-                            mLocationListenerList.remove(this);
-                            mLocationHandler.removeMessages(EVENT_LOCATION_REQUEST_TIMEOUT);
-                            onLocationUpdate(new LatLng(location.getLatitude(),
-                                    location.getLongitude()));
-                        }
-
-                        @Override
-                        public void onStatusChanged(String provider, int status, Bundle extras) {}
-
-                        @Override
-                        public void onProviderEnabled(String provider) {}
-
-                        @Override
-                        public void onProviderDisabled(String provider) {}
-                    };
-                    mLocationListenerList.add(listener);
-                    Log.d(TAG, "Request location single update from " + provider);
-                    mLocationManager.requestSingleUpdate(provider, listener, mLooper);
-                }
-            }
-        }
-
-        private boolean isLocationServiceAvailable() {
-            if (!hasPermission(Manifest.permission.ACCESS_FINE_LOCATION)
-                    && !hasPermission(Manifest.permission.ACCESS_COARSE_LOCATION)) return false;
-            for (String provider : LOCATION_PROVIDERS) {
-                if (mLocationManager.isProviderEnabled(provider)) return true;
-            }
-            return false;
-        }
-
-        private boolean hasPermission(String permission) {
-            return PermissionChecker.checkCallingOrSelfPermissionForDataDelivery(mContext,
-                    permission) == PERMISSION_GRANTED;
-        }
-
-        private final List<LocationListener> mLocationListenerList = new ArrayList<>();
-
-        private final class LocationHandler extends Handler {
-            LocationHandler(Looper looper) {
-                super(looper);
-            }
-
-            @Override
-            public void handleMessage(Message msg) {
-                switch (msg.what) {
-                    case EVENT_LOCATION_REQUEST_TIMEOUT:
-                        if (DBG) Log.d(TAG, "location request timeout");
-                        onLocationUpdate(null);
-                        break;
-                    case EVENT_REQUEST_LOCATION_UPDATE:
-                        requestLocationUpdateInternal((LocationUpdateCallback) msg.obj, msg.arg1);
-                        break;
-                    default:
-                        Log.e(TAG, "Unsupported message type " + msg.what);
-                }
-            }
-        }
-    }
-}
diff --git a/src/java/com/android/internal/telephony/CellBroadcastServiceManager.java b/src/java/com/android/internal/telephony/CellBroadcastServiceManager.java
new file mode 100644
index 0000000..a991153
--- /dev/null
+++ b/src/java/com/android/internal/telephony/CellBroadcastServiceManager.java
@@ -0,0 +1,301 @@
+/*
+ * Copyright (C) 2019 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;
+
+import android.Manifest;
+import android.annotation.NonNull;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.os.AsyncResult;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Message;
+import android.os.RemoteCallback;
+import android.os.RemoteException;
+import android.telephony.CellBroadcastService;
+import android.telephony.ICellBroadcastService;
+import android.text.TextUtils;
+import android.util.LocalLog;
+import android.util.Log;
+import android.util.Pair;
+
+import com.android.cellbroadcastservice.CellBroadcastStatsLog;
+import com.android.internal.telephony.cdma.SmsMessage;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.List;
+
+/**
+ * Manages a single binding to the CellBroadcastService from the platform. In mSIM cases callers
+ * should have one CellBroadcastServiceManager per phone, and the CellBroadcastServiceManager
+ * will handle the single binding.
+ */
+public class CellBroadcastServiceManager {
+
+    private static final String TAG = "CellBroadcastServiceManager";
+
+    private String mCellBroadcastServicePackage;
+    private static CellBroadcastServiceConnection sServiceConnection;
+    private Handler mModuleCellBroadcastHandler = null;
+
+    private Phone mPhone;
+    private Context mContext;
+
+    private final LocalLog mLocalLog = new LocalLog(100);
+
+    /** New SMS cell broadcast received as an AsyncResult. */
+    private static final int EVENT_NEW_GSM_SMS_CB = 0;
+    private static final int EVENT_NEW_CDMA_SMS_CB = 1;
+    private static final int EVENT_NEW_CDMA_SCP_MESSAGE = 2;
+    private boolean mEnabled = false;
+
+    public CellBroadcastServiceManager(Context context, Phone phone) {
+        Log.d(TAG, "CellBroadcastServiceManager created for phone " + phone.getPhoneId());
+        mContext = context;
+        mPhone = phone;
+    }
+
+    /**
+     * Send a GSM CB message to the CellBroadcastServiceManager's handler.
+     * @param m the message
+     */
+    public void sendGsmMessageToHandler(Message m) {
+        m.what = EVENT_NEW_GSM_SMS_CB;
+        mModuleCellBroadcastHandler.sendMessage(m);
+    }
+
+    /**
+     * Send a CDMA CB message to the CellBroadcastServiceManager's handler.
+     * @param sms the SmsMessage to forward
+     */
+    public void sendCdmaMessageToHandler(SmsMessage sms) {
+        Message m = Message.obtain();
+        m.what = EVENT_NEW_CDMA_SMS_CB;
+        m.obj = sms;
+        mModuleCellBroadcastHandler.sendMessage(m);
+    }
+
+    /**
+     * Send a CDMA Service Category Program message to the CellBroadcastServiceManager's handler.
+     * @param sms the SCP message
+     */
+    public void sendCdmaScpMessageToHandler(SmsMessage sms, RemoteCallback callback) {
+        Message m = Message.obtain();
+        m.what = EVENT_NEW_CDMA_SCP_MESSAGE;
+        m.obj = Pair.create(sms, callback);
+        mModuleCellBroadcastHandler.sendMessage(m);
+    }
+
+    /**
+     * Enable the CB module. The CellBroadcastService will be bound to and CB messages from the
+     * RIL will be forwarded to the module.
+     */
+    public void enable() {
+        initCellBroadcastServiceModule();
+    }
+
+    /**
+     * Disable the CB module. The manager's handler will no longer receive CB messages from the RIL.
+     */
+    public void disable() {
+        if (mEnabled == false) {
+            return;
+        }
+        mEnabled = false;
+        mPhone.mCi.unSetOnNewGsmBroadcastSms(mModuleCellBroadcastHandler);
+        if (sServiceConnection.mService != null) {
+            mContext.unbindService(sServiceConnection);
+        }
+    }
+
+    /**
+     * The CellBroadcastServiceManager binds to an implementation of the CellBroadcastService
+     * specified in com.android.internal.R.string.cellbroadcast_default_package (typically the
+     * DefaultCellBroadcastService) and forwards cell broadcast messages to the service.
+     */
+    private void initCellBroadcastServiceModule() {
+        mEnabled = true;
+        if (sServiceConnection == null) {
+            sServiceConnection = new CellBroadcastServiceConnection();
+        }
+        mCellBroadcastServicePackage = getCellBroadcastServicePackage();
+        if (mCellBroadcastServicePackage != null) {
+            mModuleCellBroadcastHandler = new Handler() {
+                @Override
+                public void handleMessage(@NonNull Message msg) {
+                    if (!mEnabled) {
+                        Log.d(TAG, "CB module is disabled.");
+                        return;
+                    }
+                    if (sServiceConnection.mService == null) {
+                        final String errorMessage = "sServiceConnection.mService is null, ignoring message.";
+                        Log.d(TAG, errorMessage);
+                        CellBroadcastStatsLog.write(CellBroadcastStatsLog.CB_MESSAGE_ERROR,
+                                CellBroadcastStatsLog.CELL_BROADCAST_MESSAGE_ERROR__TYPE__NO_CONNECTION_TO_CB_SERVICE,
+                                errorMessage);
+                        return;
+                    }
+                    try {
+                        ICellBroadcastService cellBroadcastService =
+                                ICellBroadcastService.Stub.asInterface(
+                                        sServiceConnection.mService);
+                        if (msg.what == EVENT_NEW_GSM_SMS_CB) {
+                            mLocalLog.log("GSM SMS CB for phone " + mPhone.getPhoneId());
+                            CellBroadcastStatsLog.write(CellBroadcastStatsLog.CB_MESSAGE_REPORTED,
+                                    CellBroadcastStatsLog.CELL_BROADCAST_MESSAGE_REPORTED__TYPE__GSM,
+                                    CellBroadcastStatsLog.CELL_BROADCAST_MESSAGE_REPORTED__SOURCE__FRAMEWORK);
+                            cellBroadcastService.handleGsmCellBroadcastSms(mPhone.getPhoneId(),
+                                    (byte[]) ((AsyncResult) msg.obj).result);
+                        } else if (msg.what == EVENT_NEW_CDMA_SMS_CB) {
+                            mLocalLog.log("CDMA SMS CB for phone " + mPhone.getPhoneId());
+                            SmsMessage sms = (SmsMessage) msg.obj;
+                            CellBroadcastStatsLog.write(CellBroadcastStatsLog.CB_MESSAGE_REPORTED,
+                                    CellBroadcastStatsLog.CELL_BROADCAST_MESSAGE_REPORTED__TYPE__CDMA,
+                                    CellBroadcastStatsLog.CELL_BROADCAST_MESSAGE_REPORTED__SOURCE__FRAMEWORK);
+                            cellBroadcastService.handleCdmaCellBroadcastSms(mPhone.getPhoneId(),
+                                    sms.getEnvelopeBearerData(), sms.getEnvelopeServiceCategory());
+                        } else if (msg.what == EVENT_NEW_CDMA_SCP_MESSAGE) {
+                            mLocalLog.log("CDMA SCP message for phone " + mPhone.getPhoneId());
+                            CellBroadcastStatsLog.write(CellBroadcastStatsLog.CB_MESSAGE_REPORTED,
+                                    CellBroadcastStatsLog.CELL_BROADCAST_MESSAGE_REPORTED__TYPE__CDMA_SPC,
+                                    CellBroadcastStatsLog.CELL_BROADCAST_MESSAGE_REPORTED__SOURCE__FRAMEWORK);
+                            Pair<SmsMessage, RemoteCallback> smsAndCallback =
+                                    (Pair<SmsMessage, RemoteCallback>) msg.obj;
+                            SmsMessage sms = smsAndCallback.first;
+                            RemoteCallback callback = smsAndCallback.second;
+                            cellBroadcastService.handleCdmaScpMessage(mPhone.getPhoneId(),
+                                    sms.getSmsCbProgramData(),
+                                    sms.getOriginatingAddress(),
+                                    callback);
+                        }
+                    } catch (RemoteException e) {
+                        final String errorMessage = "Failed to connect to default app: "
+                                + mCellBroadcastServicePackage + " err: " + e.toString();
+                        Log.e(TAG, errorMessage);
+                        mLocalLog.log(errorMessage);
+                        CellBroadcastStatsLog.write(CellBroadcastStatsLog.CB_MESSAGE_ERROR,
+                                CellBroadcastStatsLog.CELL_BROADCAST_MESSAGE_ERROR__TYPE__NO_CONNECTION_TO_CB_SERVICE,
+                                errorMessage);
+                        mContext.unbindService(sServiceConnection);
+                        sServiceConnection = null;
+                    }
+                }
+            };
+
+            Intent intent = new Intent(CellBroadcastService.CELL_BROADCAST_SERVICE_INTERFACE);
+            intent.setPackage(mCellBroadcastServicePackage);
+            if (sServiceConnection.mService == null) {
+                boolean serviceWasBound = mContext.bindService(intent, sServiceConnection,
+                        Context.BIND_AUTO_CREATE);
+                Log.d(TAG, "serviceWasBound=" + serviceWasBound);
+                if (!serviceWasBound) {
+                    final String errorMessage = "Unable to bind to service";
+                    Log.e(TAG, errorMessage);
+                    mLocalLog.log(errorMessage);
+                    CellBroadcastStatsLog.write(CellBroadcastStatsLog.CB_MESSAGE_ERROR,
+                            CellBroadcastStatsLog.CELL_BROADCAST_MESSAGE_ERROR__TYPE__NO_CONNECTION_TO_CB_SERVICE,
+                            errorMessage);
+                    return;
+                }
+            } else {
+                Log.d(TAG, "skipping bindService because connection already exists");
+            }
+            mPhone.mCi.setOnNewGsmBroadcastSms(mModuleCellBroadcastHandler, EVENT_NEW_GSM_SMS_CB,
+                    null);
+        } else {
+            final String errorMessage = "Unable to bind service; no cell broadcast service found";
+            Log.e(TAG, errorMessage);
+            mLocalLog.log(errorMessage);
+            CellBroadcastStatsLog.write(CellBroadcastStatsLog.CB_MESSAGE_ERROR,
+                    CellBroadcastStatsLog.CELL_BROADCAST_MESSAGE_ERROR__TYPE__NO_CONNECTION_TO_CB_SERVICE,
+                    errorMessage);
+        }
+    }
+
+    /** Returns the package name of the cell broadcast service, or null if there is none. */
+    private String getCellBroadcastServicePackage() {
+        PackageManager packageManager = mContext.getPackageManager();
+        List<ResolveInfo> cbsPackages = packageManager.queryIntentServices(
+                new Intent(CellBroadcastService.CELL_BROADCAST_SERVICE_INTERFACE),
+                PackageManager.MATCH_SYSTEM_ONLY);
+        if (cbsPackages.size() != 1) {
+            Log.e(TAG, "getCellBroadcastServicePackageName: found " + cbsPackages.size()
+                    + " CBS packages");
+        }
+        for (ResolveInfo info : cbsPackages) {
+            if (info.serviceInfo == null) continue;
+            String packageName = info.serviceInfo.packageName;
+            if (!TextUtils.isEmpty(packageName)) {
+                if (packageManager.checkPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE,
+                        packageName) == PackageManager.PERMISSION_GRANTED) {
+                    Log.d(TAG, "getCellBroadcastServicePackageName: " + packageName);
+                    return packageName;
+                } else {
+                    Log.e(TAG, "getCellBroadcastServicePackageName: " + packageName
+                            + " does not have READ_PRIVILEGED_PHONE_STATE permission");
+                }
+            } else {
+                Log.e(TAG, "getCellBroadcastServicePackageName: found a CBS package but "
+                        + "packageName is null/empty");
+            }
+        }
+        Log.e(TAG, "getCellBroadcastServicePackageName: package name not found");
+        return null;
+    }
+
+    private class CellBroadcastServiceConnection implements ServiceConnection {
+        IBinder mService;
+
+        @Override
+        public void onServiceConnected(ComponentName className, IBinder service) {
+            Log.d(TAG, "connected to CellBroadcastService");
+            this.mService = service;
+        }
+
+        @Override
+        public void onServiceDisconnected(ComponentName arg0) {
+            Log.d(TAG, "mICellBroadcastService has disconnected unexpectedly");
+            this.mService = null;
+        }
+
+        @Override
+        public void onBindingDied(ComponentName name) {
+            Log.d(TAG, "Binding died");
+        }
+
+        @Override
+        public void onNullBinding(ComponentName name) {
+            Log.d(TAG, "Null binding");
+        }
+    }
+
+    /**
+     * Triggered with `adb shell dumpsys isms`
+     */
+    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+        pw.println("CellBroadcastServiceManager:");
+        pw.println(" mEnabled=" + mEnabled);
+        pw.println(" mCellBroadcastServicePackage=" + mCellBroadcastServicePackage);
+        mLocalLog.dump(fd, pw, args);
+        pw.flush();
+    }
+}
diff --git a/src/java/com/android/internal/telephony/CellularNetworkService.java b/src/java/com/android/internal/telephony/CellularNetworkService.java
index b45d63d..3bb3814 100644
--- a/src/java/com/android/internal/telephony/CellularNetworkService.java
+++ b/src/java/com/android/internal/telephony/CellularNetworkService.java
@@ -16,34 +16,33 @@
 
 package com.android.internal.telephony;
 
-import android.hardware.radio.V1_0.CellInfoType;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.hardware.radio.V1_0.RegState;
 import android.hardware.radio.V1_4.DataRegStateResult.VopsInfo.hidl_discriminator;
 import android.os.AsyncResult;
 import android.os.Handler;
-import android.os.HandlerThread;
 import android.os.Looper;
 import android.os.Message;
 import android.telephony.AccessNetworkConstants;
 import android.telephony.AccessNetworkConstants.AccessNetworkType;
 import android.telephony.CellIdentity;
 import android.telephony.CellIdentityCdma;
-import android.telephony.CellIdentityGsm;
-import android.telephony.CellIdentityLte;
-import android.telephony.CellIdentityTdscdma;
-import android.telephony.CellIdentityWcdma;
 import android.telephony.LteVopsSupportInfo;
 import android.telephony.NetworkRegistrationInfo;
 import android.telephony.NetworkService;
 import android.telephony.NetworkServiceCallback;
-import android.telephony.Rlog;
 import android.telephony.ServiceState;
 import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyManager;
+import android.text.TextUtils;
+
+import com.android.telephony.Rlog;
 
 import java.util.ArrayList;
+import java.util.HashMap;
 import java.util.List;
-import java.util.concurrent.ConcurrentHashMap;
+import java.util.Map;
 
 /**
  * Implementation of network services for Cellular. It's a service that handles network requests
@@ -59,14 +58,12 @@
     private static final int GET_PS_REGISTRATION_STATE_DONE = 2;
     private static final int NETWORK_REGISTRATION_STATE_CHANGED = 3;
 
+    // From 24.008 6.1.3.0 and 10.5.6.2 the maximum number of PDP Contexts is 16.
+    private static final int MAX_DATA_CALLS = 16;
+
     private class CellularNetworkServiceProvider extends NetworkServiceProvider {
 
-        private final ConcurrentHashMap<Message, NetworkServiceCallback> mCallbackMap =
-                new ConcurrentHashMap<>();
-
-        private final Looper mLooper;
-
-        private final HandlerThread mHandlerThread;
+        private final Map<Message, NetworkServiceCallback> mCallbackMap = new HashMap<>();
 
         private final Handler mHandler;
 
@@ -77,10 +74,7 @@
 
             mPhone = PhoneFactory.getPhone(getSlotIndex());
 
-            mHandlerThread = new HandlerThread(CellularNetworkService.class.getSimpleName());
-            mHandlerThread.start();
-            mLooper = mHandlerThread.getLooper();
-            mHandler = new Handler(mLooper) {
+            mHandler = new Handler(Looper.myLooper()) {
                 @Override
                 public void handleMessage(Message message) {
                     NetworkServiceCallback callback = mCallbackMap.remove(message);
@@ -208,11 +202,28 @@
             }
         }
 
-        private NetworkRegistrationInfo createRegistrationStateFromVoiceRegState(Object result) {
-            int transportType = AccessNetworkConstants.TRANSPORT_TYPE_WWAN;
-            int domain = NetworkRegistrationInfo.DOMAIN_CS;
+        private @NonNull String getPlmnFromCellIdentity(@Nullable final CellIdentity ci) {
+            if (ci == null || ci instanceof CellIdentityCdma) return "";
 
-            if (result instanceof android.hardware.radio.V1_0.VoiceRegStateResult) {
+            final String mcc = ci.getMccString();
+            final String mnc = ci.getMncString();
+
+            if (TextUtils.isEmpty(mcc) || TextUtils.isEmpty(mnc)) return "";
+
+            return mcc + mnc;
+        }
+
+        private NetworkRegistrationInfo createRegistrationStateFromVoiceRegState(Object result) {
+            final int transportType = AccessNetworkConstants.TRANSPORT_TYPE_WWAN;
+            final int domain = NetworkRegistrationInfo.DOMAIN_CS;
+
+            // 1.5 at the top so that we can do an "early exit" from the method
+            if (result instanceof android.hardware.radio.V1_5.RegStateResult) {
+                return getNetworkRegistrationInfo(
+                        domain,
+                        transportType,
+                        (android.hardware.radio.V1_5.RegStateResult) result);
+            } else if (result instanceof android.hardware.radio.V1_0.VoiceRegStateResult) {
                 android.hardware.radio.V1_0.VoiceRegStateResult voiceRegState =
                         (android.hardware.radio.V1_0.VoiceRegStateResult) result;
                 int regState = getRegStateFromHalRegState(voiceRegState.regState);
@@ -228,12 +239,12 @@
                 int defaultRoamingIndicator = voiceRegState.defaultRoamingIndicator;
                 List<Integer> availableServices = getAvailableServices(
                         regState, domain, emergencyOnly);
-                CellIdentity cellIdentity =
-                        convertHalCellIdentityToCellIdentity(voiceRegState.cellIdentity);
+                CellIdentity cellIdentity = CellIdentity.create(voiceRegState.cellIdentity);
+                final String rplmn = getPlmnFromCellIdentity(cellIdentity);
 
                 return new NetworkRegistrationInfo(domain, transportType, regState,
                         networkType, reasonForDenial, emergencyOnly, availableServices,
-                        cellIdentity, cssSupported, roamingIndicator, systemIsInPrl,
+                        cellIdentity, rplmn, cssSupported, roamingIndicator, systemIsInPrl,
                         defaultRoamingIndicator);
             } else if (result instanceof android.hardware.radio.V1_2.VoiceRegStateResult) {
                 android.hardware.radio.V1_2.VoiceRegStateResult voiceRegState =
@@ -251,12 +262,12 @@
                 int defaultRoamingIndicator = voiceRegState.defaultRoamingIndicator;
                 List<Integer> availableServices = getAvailableServices(
                         regState, domain, emergencyOnly);
-                CellIdentity cellIdentity =
-                        convertHalCellIdentityToCellIdentity(voiceRegState.cellIdentity);
+                CellIdentity cellIdentity = CellIdentity.create(voiceRegState.cellIdentity);
+                final String rplmn = getPlmnFromCellIdentity(cellIdentity);
 
                 return new NetworkRegistrationInfo(domain, transportType, regState,
                         networkType, reasonForDenial, emergencyOnly, availableServices,
-                        cellIdentity, cssSupported, roamingIndicator, systemIsInPrl,
+                        cellIdentity, rplmn, cssSupported, roamingIndicator, systemIsInPrl,
                         defaultRoamingIndicator);
             }
 
@@ -264,9 +275,10 @@
         }
 
         private NetworkRegistrationInfo createRegistrationStateFromDataRegState(Object result) {
-            int domain = NetworkRegistrationInfo.DOMAIN_PS;
+            final int domain = NetworkRegistrationInfo.DOMAIN_PS;
+            final int transportType = AccessNetworkConstants.TRANSPORT_TYPE_WWAN;
+
             int regState = NetworkRegistrationInfo.REGISTRATION_STATE_UNKNOWN;
-            int transportType = AccessNetworkConstants.TRANSPORT_TYPE_WWAN;
             int networkType = TelephonyManager.NETWORK_TYPE_UNKNOWN;
             int reasonForDenial = 0;
             boolean isUsingCarrierAggregation = false;
@@ -281,7 +293,13 @@
                     new LteVopsSupportInfo(LteVopsSupportInfo.LTE_STATUS_NOT_AVAILABLE,
                             LteVopsSupportInfo.LTE_STATUS_NOT_AVAILABLE);
 
-            if (result instanceof android.hardware.radio.V1_0.DataRegStateResult) {
+            // 1.5 at the top so that we can do an "early exit" from the method
+            if (result instanceof android.hardware.radio.V1_5.RegStateResult) {
+                return getNetworkRegistrationInfo(
+                        domain,
+                        transportType,
+                        (android.hardware.radio.V1_5.RegStateResult) result);
+            } else if (result instanceof android.hardware.radio.V1_0.DataRegStateResult) {
                 android.hardware.radio.V1_0.DataRegStateResult dataRegState =
                         (android.hardware.radio.V1_0.DataRegStateResult) result;
                 regState = getRegStateFromHalRegState(dataRegState.regState);
@@ -289,8 +307,7 @@
                 reasonForDenial = dataRegState.reasonDataDenied;
                 emergencyOnly = isEmergencyOnly(dataRegState.regState);
                 maxDataCalls = dataRegState.maxDataCalls;
-
-                cellIdentity = convertHalCellIdentityToCellIdentity(dataRegState.cellIdentity);
+                cellIdentity = CellIdentity.create(dataRegState.cellIdentity);
             } else if (result instanceof android.hardware.radio.V1_2.DataRegStateResult) {
                 android.hardware.radio.V1_2.DataRegStateResult dataRegState =
                         (android.hardware.radio.V1_2.DataRegStateResult) result;
@@ -299,7 +316,7 @@
                 reasonForDenial = dataRegState.reasonDataDenied;
                 emergencyOnly = isEmergencyOnly(dataRegState.regState);
                 maxDataCalls = dataRegState.maxDataCalls;
-                cellIdentity = convertHalCellIdentityToCellIdentity(dataRegState.cellIdentity);
+                cellIdentity = CellIdentity.create(dataRegState.cellIdentity);
             } else if (result instanceof android.hardware.radio.V1_4.DataRegStateResult) {
                 android.hardware.radio.V1_4.DataRegStateResult dataRegState =
                         (android.hardware.radio.V1_4.DataRegStateResult) result;
@@ -309,7 +326,7 @@
                 reasonForDenial = dataRegState.base.reasonDataDenied;
                 emergencyOnly = isEmergencyOnly(dataRegState.base.regState);
                 maxDataCalls = dataRegState.base.maxDataCalls;
-                cellIdentity = convertHalCellIdentityToCellIdentity(dataRegState.base.cellIdentity);
+                cellIdentity = CellIdentity.create(dataRegState.base.cellIdentity);
                 android.hardware.radio.V1_4.NrIndicators nrIndicators = dataRegState.nrIndicators;
 
                 // Check for lteVopsInfo only if its initialized and RAT is EUTRAN
@@ -334,6 +351,7 @@
                 return null;
             }
 
+            String rplmn = getPlmnFromCellIdentity(cellIdentity);
             List<Integer> availableServices = getAvailableServices(
                     regState, domain, emergencyOnly);
 
@@ -343,9 +361,95 @@
             }
 
             return new NetworkRegistrationInfo(domain, transportType, regState, networkType,
-                    reasonForDenial, emergencyOnly, availableServices, cellIdentity, maxDataCalls,
-                    isDcNrRestricted, isNrAvailable, isEndcAvailable, lteVopsSupportInfo,
-                    isUsingCarrierAggregation);
+                    reasonForDenial, emergencyOnly, availableServices, cellIdentity, rplmn,
+                    maxDataCalls, isDcNrRestricted, isNrAvailable, isEndcAvailable,
+                    lteVopsSupportInfo, isUsingCarrierAggregation);
+        }
+
+        private @NonNull NetworkRegistrationInfo getNetworkRegistrationInfo(
+                int domain, int transportType,
+                android.hardware.radio.V1_5.RegStateResult regResult) {
+
+            // Perform common conversions that aren't domain specific
+            final int regState = getRegStateFromHalRegState(regResult.regState);
+            final boolean isEmergencyOnly = isEmergencyOnly(regResult.regState);
+            final List<Integer> availableServices = getAvailableServices(
+                    regState, domain, isEmergencyOnly);
+            final int rejectCause = regResult.reasonForDenial;
+            final CellIdentity cellIdentity = CellIdentity.create(regResult.cellIdentity);
+            final String rplmn = regResult.registeredPlmn;
+            final int reasonForDenial = regResult.reasonForDenial;
+
+            // Network Type fixup for carrier aggregation
+            int networkType = ServiceState.rilRadioTechnologyToNetworkType(regResult.rat);
+            // In earlier versions of the HAL, LTE_CA was allowed to indicate that the device
+            // is on CA; however, that has been superseded by the PHYSICAL_CHANNEL_CONFIG signal.
+            // Because some vendors provide both NETWORK_TYPE_LTE_CA *and* PHYSICAL_CHANNEL_CONFIG,
+            // this tweak is left for compatibility; however, the network type is no longer allowed
+            // to be used to declare that carrier aggregation is in effect, because the other
+            // signal provides a much richer information set, and we want to mitigate confusion in
+            // how CA information is being provided.
+            if (networkType == TelephonyManager.NETWORK_TYPE_LTE_CA) {
+                networkType = TelephonyManager.NETWORK_TYPE_LTE;
+            }
+
+            // Conditional parameters for specific RANs
+            boolean cssSupported = false;
+            int roamingIndicator = 0;
+            int systemIsInPrl = 0;
+            int defaultRoamingIndicator = 0;
+            boolean isEndcAvailable = false;
+            boolean isNrAvailable = false;
+            boolean isDcNrRestricted = false;
+            LteVopsSupportInfo vopsInfo = new LteVopsSupportInfo(
+                    LteVopsSupportInfo.LTE_STATUS_NOT_AVAILABLE,
+                    LteVopsSupportInfo.LTE_STATUS_NOT_AVAILABLE);
+
+            switch (regResult.accessTechnologySpecificInfo.getDiscriminator()) {
+                case android.hardware.radio.V1_5.RegStateResult
+                        .AccessTechnologySpecificInfo.hidl_discriminator.cdmaInfo:
+                    android.hardware.radio.V1_5.RegStateResult
+                            .AccessTechnologySpecificInfo.Cdma2000RegistrationInfo cdmaInfo =
+                                    regResult.accessTechnologySpecificInfo.cdmaInfo();
+                    cssSupported = cdmaInfo.cssSupported;
+                    roamingIndicator = cdmaInfo.roamingIndicator;
+                    systemIsInPrl = cdmaInfo.systemIsInPrl;
+                    defaultRoamingIndicator = cdmaInfo.defaultRoamingIndicator;
+                    break;
+                case android.hardware.radio.V1_5.RegStateResult
+                        .AccessTechnologySpecificInfo.hidl_discriminator.eutranInfo:
+                    android.hardware.radio.V1_5.RegStateResult
+                            .AccessTechnologySpecificInfo.EutranRegistrationInfo eutranInfo =
+                                    regResult.accessTechnologySpecificInfo.eutranInfo();
+
+                    isDcNrRestricted = eutranInfo.nrIndicators.isDcNrRestricted;
+                    isNrAvailable = eutranInfo.nrIndicators.isNrAvailable;
+                    isEndcAvailable = eutranInfo.nrIndicators.isEndcAvailable;
+                    vopsInfo = convertHalLteVopsSupportInfo(
+                            eutranInfo.lteVopsInfo.isVopsSupported,
+                            eutranInfo.lteVopsInfo.isEmcBearerSupported);
+                    break;
+                default:
+                    log("No access tech specific info passes for RegStateResult");
+                    break;
+            }
+
+            // build the result based on the domain for the request
+            switch(domain) {
+                case NetworkRegistrationInfo.DOMAIN_CS:
+                    return new NetworkRegistrationInfo(domain, transportType, regState,
+                            networkType, reasonForDenial, isEmergencyOnly, availableServices,
+                            cellIdentity, rplmn, cssSupported, roamingIndicator, systemIsInPrl,
+                            defaultRoamingIndicator);
+                default:
+                    loge("Unknown domain passed to CellularNetworkService= " + domain);
+                    // fall through
+                case NetworkRegistrationInfo.DOMAIN_PS:
+                    return new NetworkRegistrationInfo(domain, transportType, regState, networkType,
+                            reasonForDenial, isEmergencyOnly, availableServices, cellIdentity,
+                            rplmn, MAX_DATA_CALLS, isDcNrRestricted, isNrAvailable, isEndcAvailable,
+                            vopsInfo, false /* isUsingCarrierAggregation */);
+            }
         }
 
         private LteVopsSupportInfo convertHalLteVopsSupportInfo(
@@ -362,118 +466,6 @@
             return new LteVopsSupportInfo(vops, emergency);
         }
 
-        private CellIdentity convertHalCellIdentityToCellIdentity(
-                android.hardware.radio.V1_0.CellIdentity cellIdentity) {
-            if (cellIdentity == null) {
-                return null;
-            }
-
-            CellIdentity result = null;
-            switch(cellIdentity.cellInfoType) {
-                case CellInfoType.GSM: {
-                    if (cellIdentity.cellIdentityGsm.size() == 1) {
-                        android.hardware.radio.V1_0.CellIdentityGsm cellIdentityGsm =
-                                cellIdentity.cellIdentityGsm.get(0);
-                        result = new CellIdentityGsm(cellIdentityGsm);
-                    }
-                    break;
-                }
-                case CellInfoType.WCDMA: {
-                    if (cellIdentity.cellIdentityWcdma.size() == 1) {
-                        android.hardware.radio.V1_0.CellIdentityWcdma cellIdentityWcdma =
-                                cellIdentity.cellIdentityWcdma.get(0);
-                        result = new CellIdentityWcdma(cellIdentityWcdma);
-                    }
-                    break;
-                }
-                case CellInfoType.TD_SCDMA: {
-                    if (cellIdentity.cellIdentityTdscdma.size() == 1) {
-                        android.hardware.radio.V1_0.CellIdentityTdscdma cellIdentityTdscdma =
-                                cellIdentity.cellIdentityTdscdma.get(0);
-                        result = new  CellIdentityTdscdma(cellIdentityTdscdma);
-                    }
-                    break;
-                }
-                case CellInfoType.LTE: {
-                    if (cellIdentity.cellIdentityLte.size() == 1) {
-                        android.hardware.radio.V1_0.CellIdentityLte cellIdentityLte =
-                                cellIdentity.cellIdentityLte.get(0);
-                        result = new CellIdentityLte(cellIdentityLte);
-                    }
-                    break;
-                }
-                case CellInfoType.CDMA: {
-                    if (cellIdentity.cellIdentityCdma.size() == 1) {
-                        android.hardware.radio.V1_0.CellIdentityCdma cellIdentityCdma =
-                                cellIdentity.cellIdentityCdma.get(0);
-                        result = new CellIdentityCdma(cellIdentityCdma);
-                    }
-                    break;
-                }
-                case CellInfoType.NONE:
-                default:
-                    break;
-            }
-
-            return result;
-        }
-
-        private CellIdentity convertHalCellIdentityToCellIdentity(
-                android.hardware.radio.V1_2.CellIdentity cellIdentity) {
-            if (cellIdentity == null) {
-                return null;
-            }
-
-            CellIdentity result = null;
-            switch(cellIdentity.cellInfoType) {
-                case CellInfoType.GSM: {
-                    if (cellIdentity.cellIdentityGsm.size() == 1) {
-                        android.hardware.radio.V1_2.CellIdentityGsm cellIdentityGsm =
-                                cellIdentity.cellIdentityGsm.get(0);
-                        result = new CellIdentityGsm(cellIdentityGsm);
-                    }
-                    break;
-                }
-                case CellInfoType.WCDMA: {
-                    if (cellIdentity.cellIdentityWcdma.size() == 1) {
-                        android.hardware.radio.V1_2.CellIdentityWcdma cellIdentityWcdma =
-                                cellIdentity.cellIdentityWcdma.get(0);
-                        result = new CellIdentityWcdma(cellIdentityWcdma);
-                    }
-                    break;
-                }
-                case CellInfoType.TD_SCDMA: {
-                    if (cellIdentity.cellIdentityTdscdma.size() == 1) {
-                        android.hardware.radio.V1_2.CellIdentityTdscdma cellIdentityTdscdma =
-                                cellIdentity.cellIdentityTdscdma.get(0);
-                        result = new  CellIdentityTdscdma(cellIdentityTdscdma);
-                    }
-                    break;
-                }
-                case CellInfoType.LTE: {
-                    if (cellIdentity.cellIdentityLte.size() == 1) {
-                        android.hardware.radio.V1_2.CellIdentityLte cellIdentityLte =
-                                cellIdentity.cellIdentityLte.get(0);
-                        result = new CellIdentityLte(cellIdentityLte);
-                    }
-                    break;
-                }
-                case CellInfoType.CDMA: {
-                    if (cellIdentity.cellIdentityCdma.size() == 1) {
-                        android.hardware.radio.V1_2.CellIdentityCdma cellIdentityCdma =
-                                cellIdentity.cellIdentityCdma.get(0);
-                        result = new CellIdentityCdma(cellIdentityCdma);
-                    }
-                    break;
-                }
-                case CellInfoType.NONE:
-                default:
-                    break;
-            }
-
-            return result;
-        }
-
         @Override
         public void requestNetworkRegistrationInfo(int domain, NetworkServiceCallback callback) {
             if (DBG) log("requestNetworkRegistrationInfo for domain " + domain);
@@ -497,7 +489,6 @@
         @Override
         public void close() {
             mCallbackMap.clear();
-            mHandlerThread.quit();
             mPhone.mCi.unregisterForNetworkStateChanged(mHandler);
         }
     }
diff --git a/src/java/com/android/internal/telephony/CellularNetworkValidator.java b/src/java/com/android/internal/telephony/CellularNetworkValidator.java
index 7f84cfe..cee9cea 100644
--- a/src/java/com/android/internal/telephony/CellularNetworkValidator.java
+++ b/src/java/com/android/internal/telephony/CellularNetworkValidator.java
@@ -25,6 +25,7 @@
 import android.net.Network;
 import android.net.NetworkCapabilities;
 import android.net.NetworkRequest;
+import android.net.TelephonyNetworkSpecifier;
 import android.os.Handler;
 import android.os.PersistableBundle;
 import android.telephony.CarrierConfigManager;
@@ -54,7 +55,7 @@
     private static final String LOG_TAG = "NetworkValidator";
     // If true, upon validated network cache hit, we report validationDone only when
     // network becomes available. Otherwise, we report validationDone immediately.
-    private static boolean sWaitForNetworkAvailableWhenCacheHit = false;
+    private static boolean sWaitForNetworkAvailableWhenCacheHit = true;
 
     // States of validator. Only one validation can happen at once.
     // IDLE: no validation going on.
@@ -84,8 +85,6 @@
     public Handler mHandler = new Handler();
     @VisibleForTesting
     public ConnectivityNetworkCallback mNetworkCallback;
-    @VisibleForTesting
-    public Runnable mTimeoutCallback;
     private final ValidatedNetworkCache mValidatedNetworkCache = new ValidatedNetworkCache();
 
     private class ValidatedNetworkCache {
@@ -115,10 +114,8 @@
             long mValidationTimeStamp;
         }
 
-        boolean isRecentlyValidated(int subId) {
+        synchronized boolean isRecentlyValidated(int subId) {
             long cacheTtl = getValidationCacheTtl(subId);
-            if (cacheTtl == 0) return false;
-
             String networkIdentity = getValidationNetworkIdentity(subId);
             if (networkIdentity == null || !mValidatedNetworkMap.containsKey(networkIdentity)) {
                 return false;
@@ -129,9 +126,7 @@
             return recentlyValidated;
         }
 
-        void storeLastValidationResult(int subId, boolean validated) {
-            if (getValidationCacheTtl(subId) == 0) return;
-
+        synchronized void storeLastValidationResult(int subId, boolean validated) {
             String networkIdentity = getValidationNetworkIdentity(subId);
             logd("storeLastValidationResult for subId " + subId
                     + (validated ? " validated." : " not validated."));
@@ -165,11 +160,10 @@
 
         private String getValidationNetworkIdentity(int subId) {
             if (!SubscriptionManager.isUsableSubscriptionId(subId)) return null;
-            Phone phone = PhoneFactory.getPhone(SubscriptionController.getInstance()
-                    .getPhoneId(subId));
-            if (phone == null) return null;
-
-            if (phone.getServiceState() == null) return null;
+            SubscriptionController subController = SubscriptionController.getInstance();
+            if (subController == null) return null;
+            Phone phone = PhoneFactory.getPhone(subController.getPhoneId(subId));
+            if (phone == null || phone.getServiceState() == null) return null;
 
             NetworkRegistrationInfo regInfo = phone.getServiceState().getNetworkRegistrationInfo(
                     DOMAIN_PS, TRANSPORT_TYPE_WWAN);
@@ -209,7 +203,11 @@
         /**
          * Validation failed, passed or timed out.
          */
-        void onValidationResult(boolean validated, int subId);
+        void onValidationDone(boolean validated, int subId);
+        /**
+         * Called when a corresponding network becomes available.
+         */
+        void onNetworkAvailable(Network network, int subId);
     }
 
     /**
@@ -257,7 +255,7 @@
 
         if (!SubscriptionController.getInstance().isActiveSubId(subId)) {
             logd("Failed to start validation. Inactive subId " + subId);
-            callback.onValidationResult(false, subId);
+            callback.onValidationDone(false, subId);
             return;
         }
 
@@ -267,7 +265,7 @@
 
         if (!sWaitForNetworkAvailableWhenCacheHit && mValidatedNetworkCache
                 .isRecentlyValidated(subId)) {
-            callback.onValidationResult(true, subId);
+            callback.onValidationDone(true, subId);
             return;
         }
 
@@ -284,23 +282,14 @@
         mNetworkCallback = new ConnectivityNetworkCallback(subId);
 
         mConnectivityManager.requestNetwork(mNetworkRequest, mNetworkCallback, mHandler);
-
-        mTimeoutCallback = () -> {
-            logd("timeout on subId " + subId + " validation.");
-            // Remember latest validated network.
-            mValidatedNetworkCache.storeLastValidationResult(subId, false);
-            reportValidationResult(false, subId);
-        };
-
-        mHandler.postDelayed(mTimeoutCallback, mTimeoutInMs);
+        mHandler.postDelayed(() -> onValidationTimeout(subId), mTimeoutInMs);
     }
 
-    private void removeTimeoutCallback() {
-        // Remove timeout callback.
-        if (mTimeoutCallback != null) {
-            mHandler.removeCallbacks(mTimeoutCallback);
-            mTimeoutCallback = null;
-        }
+    private synchronized void onValidationTimeout(int subId) {
+        logd("timeout on subId " + subId + " validation.");
+        // Remember latest validated network.
+        mValidatedNetworkCache.storeLastValidationResult(subId, false);
+        reportValidationResult(false, subId);
     }
 
     /**
@@ -309,12 +298,13 @@
     public synchronized void stopValidation() {
         if (!isValidating()) {
             logd("No need to stop validation.");
-        } else {
-            mConnectivityManager.unregisterNetworkCallback(mNetworkCallback);
-            mState = STATE_IDLE;
+            return;
         }
-
-        removeTimeoutCallback();
+        if (mNetworkCallback != null) {
+            mConnectivityManager.unregisterNetworkCallback(mNetworkCallback);
+        }
+        mState = STATE_IDLE;
+        mHandler.removeCallbacksAndMessages(null);
         mSubId = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
     }
 
@@ -336,7 +326,8 @@
         return new NetworkRequest.Builder()
                 .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
                 .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR)
-                .setNetworkSpecifier(String.valueOf(mSubId))
+                .setNetworkSpecifier(new TelephonyNetworkSpecifier.Builder()
+                        .setSubscriptionId(mSubId).build())
                 .build();
     }
 
@@ -344,25 +335,30 @@
         // If the validation result is not for current subId, do nothing.
         if (mSubId != subId) return;
 
-        removeTimeoutCallback();
+        mHandler.removeCallbacksAndMessages(null);
 
         // Deal with the result only when state is still VALIDATING. This is to avoid
         // receiving multiple callbacks in queue.
         if (mState == STATE_VALIDATING) {
-            mValidationCallback.onValidationResult(passed, mSubId);
+            mValidationCallback.onValidationDone(passed, mSubId);
+            mState = STATE_VALIDATED;
+            // If validation passed and per request to NOT release after validation, delay cleanup.
             if (!mReleaseAfterValidation && passed) {
-                mState = STATE_VALIDATED;
+                mHandler.postDelayed(()-> stopValidation(), 500);
             } else {
-                mConnectivityManager.unregisterNetworkCallback(mNetworkCallback);
-                mState = STATE_IDLE;
+                stopValidation();
             }
 
             TelephonyMetrics.getInstance().writeNetworkValidate(passed
                     ? TelephonyEvent.NetworkValidationState.NETWORK_VALIDATION_STATE_PASSED
                     : TelephonyEvent.NetworkValidationState.NETWORK_VALIDATION_STATE_FAILED);
         }
+    }
 
-        mSubId = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
+    private synchronized void reportNetworkAvailable(Network network, int subId) {
+        // If the validation result is not for current subId, do nothing.
+        if (mSubId != subId) return;
+        mValidationCallback.onNetworkAvailable(network, subId);
     }
 
     @VisibleForTesting
@@ -378,13 +374,14 @@
         @Override
         public void onAvailable(Network network) {
             logd("network onAvailable " + network);
-            if (ConnectivityNetworkCallback.this.mSubId != CellularNetworkValidator.this.mSubId) {
-                return;
-            }
             TelephonyMetrics.getInstance().writeNetworkValidate(
                     TelephonyEvent.NetworkValidationState.NETWORK_VALIDATION_STATE_AVAILABLE);
+            // If it hits validation cache, we report as validation passed; otherwise we report
+            // network is available.
             if (mValidatedNetworkCache.isRecentlyValidated(mSubId)) {
                 reportValidationResult(true, ConnectivityNetworkCallback.this.mSubId);
+            } else {
+                reportNetworkAvailable(network, ConnectivityNetworkCallback.this.mSubId);
             }
         }
 
diff --git a/src/java/com/android/internal/telephony/ClientWakelockAccountant.java b/src/java/com/android/internal/telephony/ClientWakelockAccountant.java
index c47faab..a78b520 100644
--- a/src/java/com/android/internal/telephony/ClientWakelockAccountant.java
+++ b/src/java/com/android/internal/telephony/ClientWakelockAccountant.java
@@ -17,9 +17,10 @@
 package com.android.internal.telephony;
 
 import android.telephony.ClientRequestStats;
-import android.telephony.Rlog;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.telephony.Rlog;
+
 import java.util.ArrayList;
 
 public class ClientWakelockAccountant {
diff --git a/src/java/com/android/internal/telephony/CommandException.java b/src/java/com/android/internal/telephony/CommandException.java
index 4642d90..ccd9251 100644
--- a/src/java/com/android/internal/telephony/CommandException.java
+++ b/src/java/com/android/internal/telephony/CommandException.java
@@ -16,8 +16,9 @@
 
 package com.android.internal.telephony;
 
-import android.annotation.UnsupportedAppUsage;
-import android.telephony.Rlog;
+import android.compat.annotation.UnsupportedAppUsage;
+
+import com.android.telephony.Rlog;
 
 /**
  * {@hide}
@@ -123,6 +124,7 @@
         OEM_ERROR_23,
         OEM_ERROR_24,
         OEM_ERROR_25,
+        REQUEST_CANCELLED,
     }
 
     @UnsupportedAppUsage
@@ -321,6 +323,8 @@
                 return new CommandException(Error.OEM_ERROR_24);
             case RILConstants.OEM_ERROR_25:
                 return new CommandException(Error.OEM_ERROR_25);
+            case RILConstants.REQUEST_CANCELLED:
+                return new CommandException(Error.REQUEST_CANCELLED);
 
             default:
                 Rlog.e("GSM", "Unrecognized RIL errno " + ril_errno);
diff --git a/src/java/com/android/internal/telephony/CommandsInterface.java b/src/java/com/android/internal/telephony/CommandsInterface.java
index 2350cfa..40ffd58 100644
--- a/src/java/com/android/internal/telephony/CommandsInterface.java
+++ b/src/java/com/android/internal/telephony/CommandsInterface.java
@@ -16,22 +16,27 @@
 
 package com.android.internal.telephony;
 
-import android.annotation.UnsupportedAppUsage;
+import android.annotation.NonNull;
+import android.compat.annotation.UnsupportedAppUsage;
 import android.net.KeepalivePacketData;
 import android.net.LinkProperties;
 import android.os.Handler;
 import android.os.Message;
 import android.os.WorkSource;
+import android.telephony.AccessNetworkConstants.AccessNetworkType;
 import android.telephony.CarrierRestrictionRules;
 import android.telephony.ClientRequestStats;
 import android.telephony.ImsiEncryptionInfo;
 import android.telephony.NetworkScanRequest;
+import android.telephony.RadioAccessSpecifier;
+import android.telephony.SignalThresholdInfo;
 import android.telephony.data.DataProfile;
 import android.telephony.emergency.EmergencyNumber;
 
 import com.android.internal.telephony.cdma.CdmaSmsBroadcastConfigInfo;
 import com.android.internal.telephony.gsm.SmsBroadcastConfigInfo;
 import com.android.internal.telephony.uicc.IccCardStatus;
+import com.android.internal.telephony.uicc.IccCardApplicationStatus.PersoSubState;
 
 import java.util.List;
 
@@ -73,6 +78,7 @@
     static final String CB_FACILITY_BA_MT        = "AC";
     static final String CB_FACILITY_BA_SIM       = "SC";
     static final String CB_FACILITY_BA_FD        = "FD";
+    static final String CB_FACILITY_BIC_ACR      = "AR";
 
 
     // Used for various supp services apis
@@ -480,6 +486,20 @@
     void unSetOnSs(Handler h);
 
     /**
+     * Register for unsolicited NATT Keepalive Status Indications
+     *
+     * @param h Handler for notification message.
+     * @param what User-defined message code.
+     * @param obj User object.
+     */
+    default void setOnRegistrationFailed(Handler h, int what, Object obj) {}
+
+    /**
+     * @param h Handler for notification message.
+     */
+    default void unSetOnRegistrationFailed(Handler h) {}
+
+    /**
      * Sets the handler for Event Notifications for CDMA Display Info.
      * Unlike the register* methods, there's only one notification handler
      *
@@ -654,6 +674,22 @@
      void unregisterForRilConnected(Handler h);
 
     /**
+     * Registers the handler for RIL_UNSOL_SIM_DETACH_FROM_NETWORK_CONFIG_CHANGED events.
+     *
+     * @param h Handler for notification message.
+     * @param what User-defined message code.
+     * @param obj User object.
+     */
+    default void registerUiccApplicationEnablementChanged(Handler h, int what, Object obj) {};
+
+    /**
+     * Unregisters the handler for RIL_UNSOL_SIM_DETACH_FROM_NETWORK_CONFIG_CHANGED events.
+     *
+     * @param h Handler for notification message.
+     */
+    default void unregisterUiccApplicationEnablementChanged(Handler h) {};
+
+    /**
      * Supply the ICC PIN to the ICC card
      *
      *  returned message
@@ -816,6 +852,8 @@
 
     void supplyNetworkDepersonalization(String netpin, Message result);
 
+    void supplySimDepersonalization(PersoSubState persoType, String controlKey, Message result);
+
     /**
      *  returned message
      *  retMsg.obj = AsyncResult ar
@@ -1152,6 +1190,13 @@
     void sendCdmaSms(byte[] pdu, Message response);
 
     /**
+     * Identical to sendCdmaSms, except that more messages are expected to be sent soon
+     * @param pdu is CDMA-SMS in internal pseudo-PDU format
+     * @param response response sent when operation completed
+     */
+    void sendCdmaSMSExpectMore(byte[] pdu, Message response);
+
+    /**
      * send SMS over IMS with 3GPP/GSM SMS format
      * @param smscPDU is smsc address in PDU form GSM BCD format prefixed
      *      by a length byte (as expected by TS 27.005) or NULL for default SMSC
@@ -1210,11 +1255,40 @@
     @UnsupportedAppUsage
     void writeSmsToSim(int status, String smsc, String pdu, Message response);
 
+    /**
+     * Writes an SMS message to RUIM memory (EF_SMS).
+     *
+     * @param status status of message on SIM. One of:
+     *                  SmsManger.STATUS_ON_ICC_READ
+     *                  SmsManger.STATUS_ON_ICC_UNREAD
+     *                  SmsManger.STATUS_ON_ICC_SENT
+     *                  SmsManger.STATUS_ON_ICC_UNSENT
+     * @param pdu message PDU, as byte array
+     * @param response sent when operation completes. response.obj will be an AsyncResult, and will
+     *     indicate any error that may have occurred (eg, out of memory).
+     */
     @UnsupportedAppUsage
-    void writeSmsToRuim(int status, String pdu, Message response);
+    void writeSmsToRuim(int status, byte[] pdu, Message response);
 
     @UnsupportedAppUsage
-    void setRadioPower(boolean on, Message response);
+    default void setRadioPower(boolean on, Message response) {
+        setRadioPower(on, false, false, response);
+    }
+
+    /**
+     * Sets the radio power on/off state (off is sometimes
+     * called "airplane mode").
+     *
+     * @param on true means "on", false means "off".
+     * @param forEmergencyCall true means the purpose of turning radio power on is for emergency
+     *                         call. No effect if power is set false.
+     * @param isSelectedPhoneForEmergencyCall true means this phone / modem is selected to place
+     *                                  emergency call after turning power on. No effect if power
+     *                                  or forEmergency is set false.
+     * @param response sent when operation completes.
+     */
+    default void setRadioPower(boolean on, boolean forEmergencyCall,
+            boolean isSelectedPhoneForEmergencyCall, Message response) {}
 
     @UnsupportedAppUsage
     void acknowledgeLastIncomingGsmSms(boolean success, int cause, Message response);
@@ -1330,8 +1404,15 @@
     @UnsupportedAppUsage
     void setNetworkSelectionModeAutomatic(Message response);
 
+    /**
+     * Ask the radio to connect to the input network with specific RadioAccessNetwork
+     * and change selection mode to manual.
+     * @param operatorNumeric PLMN ID of the network to select.
+     * @param ran radio access network type (see {@link AccessNetworkType}).
+     * @param response callback message.
+     */
     @UnsupportedAppUsage
-    void setNetworkSelectionModeManual(String operatorNumeric, Message response);
+    void setNetworkSelectionModeManual(String operatorNumeric, int ran, Message response);
 
     /**
      * Queries whether the current network selection mode is automatic
@@ -2217,17 +2298,30 @@
     void setUnsolResponseFilter(int filter, Message result);
 
     /**
-     * Send the signal strength reporting criteria to the modem.
+     * Sets the signal strength reporting criteria.
      *
-     * @param hysteresisMs A hysteresis time in milliseconds. A value of 0 disables hysteresis.
-     * @param hysteresisDb An interval in dB defining the required magnitude change between reports.
-     *     A value of 0 disables hysteresis.
-     * @param thresholdsDbm An array of trigger thresholds in dBm. A size of 0 disables thresholds.
-     * @param ran RadioAccessNetwork for which to apply criteria.
+     * The resulting reporting rules are the AND of all the supplied criteria. For each RAN
+     * The hysteresisDb and thresholds apply to only the following measured quantities:
+     * -GERAN    - RSSI
+     * -CDMA2000 - RSSI
+     * -UTRAN    - RSCP
+     * -EUTRAN   - RSRP/RSRQ/RSSNR
+     * -NGRAN    - SSRSRP/SSRSRQ/SSSINR
+     *
+     * Note: Reporting criteria must be individually set for each RAN. For any unset reporting
+     * criteria, the value is implementation-defined.
+     *
+     * Response callback is
+     * IRadioResponse.setSignalStrengthReportingCriteriaResponse_1_5()
+     *
+     * @param signalThresholdInfo Signal threshold info including the threshold values,
+     *                            hysteresisDb, and hysteresisMs. See @1.5::SignalThresholdInfo
+     *                            for details.
+     * @param ran The type of network for which to apply these thresholds.
      * @param result callback message contains the information of SUCCESS/FAILURE
      */
-    void setSignalStrengthReportingCriteria(int hysteresisMs, int hysteresisDb, int[] thresholdsDbm,
-            int ran, Message result);
+    void setSignalStrengthReportingCriteria(SignalThresholdInfo signalThresholdInfo, int ran,
+            Message result);
 
     /**
      * Send the link capacity reporting criteria to the modem
@@ -2352,13 +2446,82 @@
     default void enableModem(boolean enable, Message result) {};
 
     /**
+     * Notify CommandsInterface that whether its corresponding slot is active or not. If not,
+     * it means it has no RIL service or logical modem to connect to.
+     *
+     * @param active whether there's a matching active SIM slot.
+     */
+    default void onSlotActiveStatusChange(boolean active) {}
+
+    /**
      * Query whether logical modem is enabled or disabled
      *
      * @param result a Message to return to the requester
      */
     default void getModemStatus(Message result) {};
 
+    /**
+     * Enable or disable uicc applications on the SIM.
+     *
+     * @param enable enable or disable UiccApplications on the SIM.
+     * @param onCompleteMessage a Message to return to the requester
+     */
+    default void enableUiccApplications(boolean enable, Message onCompleteMessage) {}
+
+    /**
+     * Specify which bands modem's background scan must act on.
+     * If {@code specifiers} is non-empty, the scan will be restricted to the bands specified.
+     * Otherwise, it scans all bands.
+     *
+     * For example, CBRS is only on LTE band 48. By specifying this band,
+     * modem saves more power.
+     *
+     * @param specifiers which bands to scan.
+     * @param onComplete a message to send when complete.
+     */
+    default void setSystemSelectionChannels(@NonNull List<RadioAccessSpecifier> specifiers,
+            Message onComplete) {}
+
+    /**
+     * Whether uicc applications are enabled or not.
+     *
+     * @param onCompleteMessage a Message to return to the requester
+     */
+    default void areUiccApplicationsEnabled(Message onCompleteMessage) {}
+
+    /**
+     * Whether {@link #enableUiccApplications} is supported, based on IRadio version.
+     */
+    default boolean canToggleUiccApplicationsEnablement() {
+        return false;
+    }
+
     default List<ClientRequestStats> getClientRequestStats() {
         return null;
     }
+
+    /**
+     * Registers the handler for RIL_UNSOL_BARRING_INFO_CHANGED events.
+     *
+     * @param h Handler for notification message.
+     * @param what User-defined message code.
+     * @param obj User object.
+     */
+    default void registerForBarringInfoChanged(Handler h, int what, Object obj) {};
+
+    /**
+     * Unregisters the handler for RIL_UNSOL_BARRING_INFO_CHANGED events.
+     *
+     * @param h Handler for notification message.
+     */
+    default void unregisterForBarringInfoChanged(Handler h) {};
+
+    /**
+     * Get all the barring info for the current camped cell applicable to the current user.
+     *
+     * AsyncResult.result is the object of {@link android.telephony.BarringInfo}.
+     *
+     * @param result Message will be sent back to handler and result.obj will be the AsycResult.
+     */
+    default void getBarringInfo(Message result) {};
 }
diff --git a/src/java/com/android/internal/telephony/Connection.java b/src/java/com/android/internal/telephony/Connection.java
old mode 100755
new mode 100644
index d4d606d..f1baa1b
--- a/src/java/com/android/internal/telephony/Connection.java
+++ b/src/java/com/android/internal/telephony/Connection.java
@@ -16,18 +16,21 @@
 
 package com.android.internal.telephony;
 
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
 import android.net.Uri;
 import android.os.Bundle;
 import android.os.SystemClock;
-import android.telecom.ConferenceParticipant;
 import android.telephony.DisconnectCause;
-import android.telephony.Rlog;
 import android.telephony.ServiceState;
+import android.telephony.ServiceState.RilRadioTechnology;
 import android.telephony.emergency.EmergencyNumber;
 import android.util.Log;
 
+import com.android.ims.internal.ConferenceParticipant;
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.telephony.emergency.EmergencyNumberTracker;
+import com.android.internal.telephony.util.TelephonyUtils;
+import com.android.telephony.Rlog;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -40,6 +43,8 @@
 public abstract class Connection {
     private static final String TAG = "Connection";
 
+    public static final String ADHOC_CONFERENCE_ADDRESS = "tel:conf-factory";
+
     public interface PostDialListener {
         void onPostDialWait();
         void onPostDialChar(char c);
@@ -93,7 +98,7 @@
     public interface Listener {
         public void onVideoStateChanged(int videoState);
         public void onConnectionCapabilitiesChanged(int capability);
-        public void onCallRadioTechChanged(@ServiceState.RilRadioTechnology int vrat);
+        public void onCallRadioTechChanged(@RilRadioTechnology int vrat);
         public void onVideoProviderChanged(
                 android.telecom.Connection.VideoProvider videoProvider);
         public void onAudioQualityChanged(int audioQuality);
@@ -124,7 +129,7 @@
         @Override
         public void onConnectionCapabilitiesChanged(int capability) {}
         @Override
-        public void onCallRadioTechChanged(@ServiceState.RilRadioTechnology int vrat) {}
+        public void onCallRadioTechChanged(@RilRadioTechnology int vrat) {}
         @Override
         public void onVideoProviderChanged(
                 android.telecom.Connection.VideoProvider videoProvider) {}
@@ -180,8 +185,14 @@
     protected int mCnapNamePresentation  = PhoneConstants.PRESENTATION_ALLOWED;
     @UnsupportedAppUsage
     protected String mAddress;     // MAY BE NULL!!!
+    // The VERSTAT number verification status; defaults to not verified.
+    protected @android.telecom.Connection.VerificationStatus int mNumberVerificationStatus =
+            android.telecom.Connection.VERIFICATION_STATUS_NOT_VERIFIED;
+
     @UnsupportedAppUsage
     protected String mDialString;          // outgoing calls only
+    protected String[] mParticipantsToDial;// outgoing calls only
+    protected boolean mIsAdhocConference;
     @UnsupportedAppUsage
     protected int mNumberPresentation = PhoneConstants.PRESENTATION_ALLOWED;
     @UnsupportedAppUsage
@@ -215,6 +226,9 @@
     protected int mCause = DisconnectCause.NOT_DISCONNECTED;
     protected PostDialState mPostDialState = PostDialState.NOT_STARTED;
 
+    // Store the current audio code
+    protected int mAudioCodec;
+
     @UnsupportedAppUsage
     private static String LOG_TAG = "Connection";
 
@@ -226,7 +240,7 @@
      *
      * This is used to propagate the call radio technology to upper layer.
      */
-    private @ServiceState.RilRadioTechnology int mCallRadioTech =
+    private @RilRadioTechnology int mCallRadioTech =
             ServiceState.RIL_RADIO_TECHNOLOGY_UNKNOWN;
     private boolean mAudioModeIsVoip;
     private int mAudioQuality;
@@ -237,6 +251,7 @@
     private int mPhoneType;
     private boolean mAnsweringDisconnectsActiveCall;
     private boolean mAllowAddCallDuringVideoCall;
+    private boolean mAllowHoldingVideoCall;
 
     private boolean mIsEmergencyCall;
 
@@ -307,6 +322,20 @@
     }
 
     /**
+     * Gets the participants address (e.g. phone number) associated with connection.
+     *
+     * @return address or null if unavailable
+     */
+    public String[] getParticipantsToDial() {
+        return mParticipantsToDial;
+    }
+
+    // return whether connection is AdhocConference or not
+    public boolean isAdhocConference() {
+        return mIsAdhocConference;
+    }
+
+    /**
      * Gets CNAP name associated with connection.
      * @return cnap name or null if unavailable
      */
@@ -323,6 +352,15 @@
     }
 
     /**
+     * Get the number, as set by {@link #restoreDialedNumberAfterConversion(String)}.
+     * @return The converted number.
+     */
+    @VisibleForTesting
+    public String getConvertedNumber() {
+        return mConvertedNumber;
+    }
+
+    /**
      * Gets CNAP presentation associated with connection.
      * @return cnap name or null if unavailable
      */
@@ -631,6 +669,17 @@
     public abstract void deflect(String number) throws CallStateException;
 
     /**
+     * Transfer individual Connection
+     */
+    public abstract void transfer(String number, boolean isConfirmationRequired)
+            throws CallStateException;
+
+    /**
+     * Transfer individual Connection for consultative transfer
+     */
+    public abstract void consultativeTransfer(Connection other) throws CallStateException;
+
+    /**
      * Hangup individual Connection
      */
     @UnsupportedAppUsage
@@ -892,7 +941,7 @@
      * @return the RIL Voice Radio Technology used for current connection,
      *         see {@code RIL_RADIO_TECHNOLOGY_*} in {@link android.telephony.ServiceState}.
      */
-    public @ServiceState.RilRadioTechnology int getCallRadioTech() {
+    public @RilRadioTechnology int getCallRadioTech() {
         return mCallRadioTech;
     }
 
@@ -970,7 +1019,7 @@
      * @param vrat the RIL voice radio technology for current connection,
      *             see {@code RIL_RADIO_TECHNOLOGY_*} in {@link android.telephony.ServiceState}.
      */
-    public void setCallRadioTech(@ServiceState.RilRadioTechnology int vrat) {
+    public void setCallRadioTech(@RilRadioTechnology int vrat) {
         if (mCallRadioTech == vrat) {
             return;
         }
@@ -1014,7 +1063,7 @@
             int previousCount = mExtras.size();
             // Prevent vendors from passing in extras other than primitive types and android API
             // parcelables.
-            mExtras = mExtras.filterValues();
+            mExtras = TelephonyUtils.filterValues(mExtras);
             int filteredCount = mExtras.size();
             if (filteredCount != previousCount) {
                 Rlog.i(TAG, "setConnectionExtras: filtering " + (previousCount - filteredCount)
@@ -1065,6 +1114,14 @@
         mAllowAddCallDuringVideoCall = allowAddCallDuringVideoCall;
     }
 
+    public boolean shouldAllowHoldingVideoCall() {
+        return mAllowHoldingVideoCall;
+    }
+
+    public void setAllowHoldingVideoCall(boolean allowHoldingVideoCall) {
+        mAllowHoldingVideoCall = allowHoldingVideoCall;
+    }
+
     /**
      * Sets whether the connection is the result of an external call which was pulled to the local
      * device.
@@ -1120,7 +1177,19 @@
         }
     }
 
-    public void setConverted(String oriNumber) {
+    /**
+     * {@link CallTracker#convertNumberIfNecessary(Phone, String)} can be used to convert a dialed
+     * number to another number based on carrier config.  This is used where a carrier wishes to
+     * redirect certain short codes such as *55 to another number (e.g. a 1-800 service number).
+     * The {@link CallTracker} sub-classes call
+     * {@link CallTracker#convertNumberIfNecessary(Phone, String)} to retrieve the newly converted
+     * number and instantiate the {@link Connection} instance using the converted number so that the
+     * system will dial out the substitution number instead of the originally dialed one.  This gem
+     * of a method is called after the dialing process to restore the originally dialed number and
+     * keep track of the fact that a converted number was used to place the call.
+     * @param oriNumber The original number prior to conversion.
+     */
+    public void restoreDialedNumberAfterConversion(String oriNumber) {
         mNumberConverted = true;
         mConvertedNumber = mAddress;
         mAddress = oriNumber;
@@ -1332,4 +1401,28 @@
                 .append(" post dial state: " + getPostDialState());
         return str.toString();
     }
+
+    /**
+     * Get current audio codec.
+     * @return current audio codec.
+     */
+    public int getAudioCodec() {
+        return mAudioCodec;
+    }
+
+    /**
+     * @return The number verification status; only applicable for IMS calls.
+     */
+    public @android.telecom.Connection.VerificationStatus int getNumberVerificationStatus() {
+        return mNumberVerificationStatus;
+    }
+
+    /**
+     * Sets the number verification status.
+     * @param verificationStatus The new verification status
+     */
+    public void setNumberVerificationStatus(
+            @android.telecom.Connection.VerificationStatus int verificationStatus) {
+        mNumberVerificationStatus = verificationStatus;
+    }
 }
diff --git a/src/java/com/android/internal/telephony/DebugService.java b/src/java/com/android/internal/telephony/DebugService.java
index 0002f14..3a08243 100644
--- a/src/java/com/android/internal/telephony/DebugService.java
+++ b/src/java/com/android/internal/telephony/DebugService.java
@@ -16,10 +16,10 @@
 
 package com.android.internal.telephony;
 
-import android.telephony.Rlog;
 import android.text.TextUtils;
 
 import com.android.internal.telephony.metrics.TelephonyMetrics;
+import com.android.telephony.Rlog;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
diff --git a/src/java/com/android/internal/telephony/DefaultPhoneNotifier.java b/src/java/com/android/internal/telephony/DefaultPhoneNotifier.java
index a887f9d..0048535 100644
--- a/src/java/com/android/internal/telephony/DefaultPhoneNotifier.java
+++ b/src/java/com/android/internal/telephony/DefaultPhoneNotifier.java
@@ -17,41 +17,44 @@
 package com.android.internal.telephony;
 
 import android.annotation.NonNull;
-import android.annotation.UnsupportedAppUsage;
-import android.net.LinkProperties;
-import android.net.NetworkCapabilities;
-import android.os.Bundle;
-import android.os.RemoteException;
-import android.os.ServiceManager;
+import android.content.Context;
+import android.telephony.Annotation.DataFailureCause;
+import android.telephony.Annotation.RadioPowerState;
+import android.telephony.Annotation.SrvccState;
+import android.telephony.BarringInfo;
 import android.telephony.CallQuality;
+import android.telephony.CellIdentity;
 import android.telephony.CellInfo;
-import android.telephony.CellLocation;
-import android.telephony.DataFailCause;
 import android.telephony.PhoneCapability;
-import android.telephony.PhysicalChannelConfig;
 import android.telephony.PreciseCallState;
-import android.telephony.Rlog;
+import android.telephony.PreciseDataConnectionState;
 import android.telephony.ServiceState;
-import android.telephony.SubscriptionManager;
+import android.telephony.TelephonyDisplayInfo;
 import android.telephony.TelephonyManager;
+import android.telephony.TelephonyRegistryManager;
 import android.telephony.data.ApnSetting;
+import android.telephony.emergency.EmergencyNumber;
 import android.telephony.ims.ImsReasonInfo;
 
+import com.android.internal.telephony.PhoneInternalInterface.DataActivityState;
+import com.android.telephony.Rlog;
+
 import java.util.List;
 
 /**
  * broadcast intents
  */
 public class DefaultPhoneNotifier implements PhoneNotifier {
+
     private static final String LOG_TAG = "DefaultPhoneNotifier";
     private static final boolean DBG = false; // STOPSHIP if true
 
-    @UnsupportedAppUsage
-    protected ITelephonyRegistry mRegistry;
+    private TelephonyRegistryManager mTelephonyRegistryMgr;
 
-    public DefaultPhoneNotifier() {
-        mRegistry = ITelephonyRegistry.Stub.asInterface(ServiceManager.getService(
-                    "telephony.registry"));
+
+    public DefaultPhoneNotifier(Context context) {
+        mTelephonyRegistryMgr = (TelephonyRegistryManager) context.getSystemService(
+            Context.TELEPHONY_REGISTRY_SERVICE);
     }
 
     @Override
@@ -63,15 +66,8 @@
         if (ringingCall != null && ringingCall.getEarliestConnection() != null) {
             incomingNumber = ringingCall.getEarliestConnection().getAddress();
         }
-        try {
-            if (mRegistry != null) {
-                  mRegistry.notifyCallStateForPhoneId(phoneId, subId,
-                        PhoneConstantConversions.convertCallState(
-                            sender.getState()), incomingNumber);
-            }
-        } catch (RemoteException ex) {
-            // system process is dead
-        }
+        mTelephonyRegistryMgr.notifyCallStateChanged(subId, phoneId,
+            PhoneConstantConversions.convertCallState(sender.getState()), incomingNumber);
     }
 
     @Override
@@ -80,19 +76,13 @@
         int phoneId = sender.getPhoneId();
         int subId = sender.getSubId();
 
-        Rlog.d(LOG_TAG, "nofityServiceState: mRegistry=" + mRegistry + " ss=" + ss
-                + " sender=" + sender + " phondId=" + phoneId + " subId=" + subId);
+        Rlog.d(LOG_TAG, "notifyServiceState: mRegistryMgr=" + mTelephonyRegistryMgr + " ss="
+                + ss + " sender=" + sender + " phondId=" + phoneId + " subId=" + subId);
         if (ss == null) {
             ss = new ServiceState();
             ss.setStateOutOfService();
         }
-        try {
-            if (mRegistry != null) {
-                mRegistry.notifyServiceStateForPhoneId(phoneId, subId, ss);
-            }
-        } catch (RemoteException ex) {
-            // system process is dead
-        }
+        mTelephonyRegistryMgr.notifyServiceStateChanged(subId, phoneId, ss);
     }
 
     @Override
@@ -101,158 +91,60 @@
         int subId = sender.getSubId();
         if (DBG) {
             // too chatty to log constantly
-            Rlog.d(LOG_TAG, "notifySignalStrength: mRegistry=" + mRegistry
-                    + " ss=" + sender.getSignalStrength() + " sender=" + sender);
+            Rlog.d(LOG_TAG, "notifySignalStrength: mRegistryMgr=" + mTelephonyRegistryMgr
+                + " ss=" + sender.getSignalStrength() + " sender=" + sender);
         }
-        try {
-            if (mRegistry != null) {
-                mRegistry.notifySignalStrengthForPhoneId(phoneId, subId,
-                        sender.getSignalStrength());
-            }
-        } catch (RemoteException ex) {
-            // system process is dead
-        }
+        mTelephonyRegistryMgr.notifySignalStrengthChanged(subId, phoneId,
+            sender.getSignalStrength());
     }
 
     @Override
     public void notifyMessageWaitingChanged(Phone sender) {
         int phoneId = sender.getPhoneId();
         int subId = sender.getSubId();
-
-        try {
-            if (mRegistry != null) {
-                mRegistry.notifyMessageWaitingChangedForPhoneId(phoneId, subId,
-                        sender.getMessageWaitingIndicator());
-            }
-        } catch (RemoteException ex) {
-            // system process is dead
-        }
+        mTelephonyRegistryMgr.notifyMessageWaitingChanged(subId, phoneId,
+            sender.getMessageWaitingIndicator());
     }
 
     @Override
     public void notifyCallForwardingChanged(Phone sender) {
         int subId = sender.getSubId();
-        try {
-            if (mRegistry != null) {
-                Rlog.d(LOG_TAG, "notifyCallForwardingChanged: subId=" + subId + ", isCFActive="
-                        + sender.getCallForwardingIndicator());
+        Rlog.d(LOG_TAG, "notifyCallForwardingChanged: subId=" + subId + ", isCFActive="
+            + sender.getCallForwardingIndicator());
 
-                mRegistry.notifyCallForwardingChangedForSubscriber(subId,
-                        sender.getCallForwardingIndicator());
-            }
-        } catch (RemoteException ex) {
-            // system process is dead
-        }
+        mTelephonyRegistryMgr.notifyCallForwardingChanged(subId,
+            sender.getCallForwardingIndicator());
     }
 
     @Override
     public void notifyDataActivity(Phone sender) {
         int subId = sender.getSubId();
-        try {
-            if (mRegistry != null) {
-                mRegistry.notifyDataActivityForSubscriber(subId,
-                        convertDataActivityState(sender.getDataActivityState()));
-            }
-        } catch (RemoteException ex) {
-            // system process is dead
-        }
+        mTelephonyRegistryMgr.notifyDataActivityChanged(subId,
+                convertDataActivityState(sender.getDataActivityState()));
     }
 
     @Override
-    public void notifyDataConnection(Phone sender, String apnType,
-                                     PhoneConstants.DataState state) {
-        doNotifyDataConnection(sender, apnType, state);
-    }
+    public void notifyDataConnection(
+            Phone sender, String apnType, PreciseDataConnectionState preciseState) {
 
-    private void doNotifyDataConnection(Phone sender, String apnType,
-                                        PhoneConstants.DataState state) {
         int subId = sender.getSubId();
         int phoneId = sender.getPhoneId();
-        long dds = SubscriptionManager.getDefaultDataSubscriptionId();
-        if (DBG) log("subId = " + subId + ", DDS = " + dds);
+        int apnTypeBitmask = ApnSetting.getApnTypesBitmaskFromString(apnType);
 
-        // TODO
-        // use apnType as the key to which connection we're talking about.
-        // pass apnType back up to fetch particular for this one.
-        TelephonyManager telephony = TelephonyManager.getDefault();
-        LinkProperties linkProperties = null;
-        NetworkCapabilities networkCapabilities = null;
-        boolean roaming = false;
-
-        if (state == PhoneConstants.DataState.CONNECTED) {
-            linkProperties = sender.getLinkProperties(apnType);
-            networkCapabilities = sender.getNetworkCapabilities(apnType);
-        }
-        ServiceState ss = sender.getServiceState();
-        if (ss != null) roaming = ss.getDataRoaming();
-
-        try {
-            if (mRegistry != null) {
-                mRegistry.notifyDataConnectionForSubscriber(phoneId, subId,
-                    PhoneConstantConversions.convertDataState(state),
-                        sender.isDataAllowed(ApnSetting.getApnTypesBitmaskFromString(apnType)),
-                        sender.getActiveApnHost(apnType),
-                        apnType,
-                        linkProperties,
-                        networkCapabilities,
-                        ((telephony != null) ? telephony.getDataNetworkType(subId) :
-                                TelephonyManager.NETWORK_TYPE_UNKNOWN), roaming);
-            }
-        } catch (RemoteException ex) {
-            // system process is dead
-        }
+        mTelephonyRegistryMgr.notifyDataConnectionForSubscriber(
+                phoneId, subId, apnTypeBitmask, preciseState);
     }
 
     @Override
-    public void notifyDataConnectionFailed(Phone sender, String apnType) {
-        try {
-            if (mRegistry != null) {
-                mRegistry.notifyDataConnectionFailedForSubscriber(sender.getPhoneId(),
-                        sender.getSubId(), apnType);
-            }
-        } catch (RemoteException ex) {
-            // system process is dead
-        }
-    }
-
-    @Override
-    public void notifyCellLocation(Phone sender, CellLocation cl) {
+    public void notifyCellLocation(Phone sender, CellIdentity cellIdentity) {
         int subId = sender.getSubId();
-        Bundle data = new Bundle();
-        cl.fillInNotifierBundle(data);
-        try {
-            if (mRegistry != null) {
-                mRegistry.notifyCellLocationForSubscriber(subId, data);
-            }
-        } catch (RemoteException ex) {
-            // system process is dead
-        }
+        mTelephonyRegistryMgr.notifyCellLocation(subId, cellIdentity);
     }
 
     @Override
     public void notifyCellInfo(Phone sender, List<CellInfo> cellInfo) {
         int subId = sender.getSubId();
-        try {
-            if (mRegistry != null) {
-                mRegistry.notifyCellInfoForSubscriber(subId, cellInfo);
-            }
-        } catch (RemoteException ex) {
-
-        }
-    }
-
-    @Override
-    public void notifyPhysicalChannelConfiguration(Phone sender,
-            List<PhysicalChannelConfig> configs) {
-        int subId = sender.getSubId();
-        int phoneId = sender.getPhoneId();
-        try {
-            if (mRegistry != null) {
-                mRegistry.notifyPhysicalChannelConfigurationForSubscriber(phoneId, subId, configs);
-            }
-        } catch (RemoteException ex) {
-            // system process is dead
-        }
+        mTelephonyRegistryMgr.notifyCellInfoChanged(subId, cellInfo);
     }
 
     public void notifyPreciseCallState(Phone sender) {
@@ -260,142 +152,114 @@
         Call foregroundCall = sender.getForegroundCall();
         Call backgroundCall = sender.getBackgroundCall();
         if (ringingCall != null && foregroundCall != null && backgroundCall != null) {
-            try {
-                mRegistry.notifyPreciseCallState(sender.getPhoneId(), sender.getSubId(),
-                        convertPreciseCallState(ringingCall.getState()),
-                        convertPreciseCallState(foregroundCall.getState()),
-                        convertPreciseCallState(backgroundCall.getState()));
-            } catch (RemoteException ex) {
-                // system process is dead
-            }
+            mTelephonyRegistryMgr.notifyPreciseCallState(sender.getSubId(), sender.getPhoneId(),
+                convertPreciseCallState(ringingCall.getState()),
+                convertPreciseCallState(foregroundCall.getState()),
+                convertPreciseCallState(backgroundCall.getState()));
         }
     }
 
     public void notifyDisconnectCause(Phone sender, int cause, int preciseCause) {
-        try {
-            mRegistry.notifyDisconnectCause(sender.getPhoneId(), sender.getSubId(), cause,
-                    preciseCause);
-        } catch (RemoteException ex) {
-            // system process is dead
-        }
+        mTelephonyRegistryMgr.notifyDisconnectCause(sender.getPhoneId(), sender.getSubId(), cause,
+                preciseCause);
     }
 
     @Override
     public void notifyImsDisconnectCause(@NonNull Phone sender, ImsReasonInfo imsReasonInfo) {
-        try {
-            mRegistry.notifyImsDisconnectCause(sender.getSubId(), imsReasonInfo);
-        } catch (RemoteException ex) {
-            // system process is dead
-        }
-    }
-
-    public void notifyPreciseDataConnectionFailed(Phone sender, String apnType,
-            String apn, @DataFailCause.FailCause int failCause) {
-        try {
-            mRegistry.notifyPreciseDataConnectionFailed(sender.getPhoneId(), sender.getSubId(),
-                    apnType, apn, failCause);
-        } catch (RemoteException ex) {
-            // system process is dead
-        }
+        mTelephonyRegistryMgr.notifyImsDisconnectCause(sender.getSubId(), imsReasonInfo);
     }
 
     @Override
-    public void notifySrvccStateChanged(Phone sender, @TelephonyManager.SrvccState int state) {
-        try {
-            mRegistry.notifySrvccStateChanged(sender.getSubId(), state);
-        } catch (RemoteException ex) {
-            // system process is dead
-        }
+    /** Notify the TelephonyRegistry that a data connection has failed with a specified cause */
+    public void notifyDataConnectionFailed(Phone sender, String apnType,
+        String apn, @DataFailureCause int failCause) {
+        mTelephonyRegistryMgr.notifyPreciseDataConnectionFailed(
+                sender.getSubId(), sender.getPhoneId(),
+                ApnSetting.getApnTypesBitmaskFromString(apnType), apn, failCause);
+    }
+
+    @Override
+    public void notifySrvccStateChanged(Phone sender, @SrvccState int state) {
+        mTelephonyRegistryMgr.notifySrvccStateChanged(sender.getSubId(), state);
     }
 
     @Override
     public void notifyDataActivationStateChanged(Phone sender, int activationState) {
-        try {
-            mRegistry.notifySimActivationStateChangedForPhoneId(sender.getPhoneId(),
-                    sender.getSubId(), PhoneConstants.SIM_ACTIVATION_TYPE_DATA, activationState);
-        } catch (RemoteException ex) {
-            // system process is dead
-        }
+        mTelephonyRegistryMgr.notifyDataActivationStateChanged(sender.getSubId(),
+            sender.getPhoneId(), activationState);
     }
 
     @Override
     public void notifyVoiceActivationStateChanged(Phone sender, int activationState) {
-        try {
-            mRegistry.notifySimActivationStateChangedForPhoneId(sender.getPhoneId(),
-                    sender.getSubId(), PhoneConstants.SIM_ACTIVATION_TYPE_VOICE, activationState);
-        } catch (RemoteException ex) {
-            // system process is dead
-        }
+        mTelephonyRegistryMgr.notifyVoiceActivationStateChanged(sender.getSubId(),
+            sender.getPhoneId(),  activationState);
     }
 
     @Override
     public void notifyUserMobileDataStateChanged(Phone sender, boolean state) {
-        try {
-            mRegistry.notifyUserMobileDataStateChangedForPhoneId(
-                    sender.getPhoneId(), sender.getSubId(), state);
-        } catch (RemoteException ex) {
-            // system process is dead
-        }
+        mTelephonyRegistryMgr.notifyUserMobileDataStateChanged(sender.getPhoneId(),
+                sender.getSubId(), state);
     }
 
     @Override
-    public void notifyOemHookRawEventForSubscriber(Phone sender, byte[] rawData) {
-        try {
-            mRegistry.notifyOemHookRawEventForSubscriber(sender.getPhoneId(),
-                    sender.getSubId(), rawData);
-        } catch (RemoteException ex) {
-            // system process is dead
-        }
+    public void notifyDisplayInfoChanged(Phone sender, TelephonyDisplayInfo telephonyDisplayInfo) {
+        mTelephonyRegistryMgr.notifyDisplayInfoChanged(sender.getPhoneId(), sender.getSubId(),
+                telephonyDisplayInfo);
     }
 
     @Override
     public void notifyPhoneCapabilityChanged(PhoneCapability capability) {
-        try {
-            mRegistry.notifyPhoneCapabilityChanged(capability);
-        } catch (RemoteException ex) {
-            // system process is dead
-        }
+        mTelephonyRegistryMgr.notifyPhoneCapabilityChanged(capability);
     }
 
     @Override
-    public void notifyRadioPowerStateChanged(Phone sender,
-                                             @TelephonyManager.RadioPowerState int state) {
-        try {
-            mRegistry.notifyRadioPowerStateChanged(sender.getPhoneId(), sender.getSubId(), state);
-        } catch (RemoteException ex) {
-            // system process is dead
-        }
+    public void notifyRadioPowerStateChanged(Phone sender, @RadioPowerState int state) {
+        mTelephonyRegistryMgr.notifyRadioPowerStateChanged(sender.getSubId(), sender.getPhoneId(),
+            state);
     }
 
     @Override
     public void notifyEmergencyNumberList(Phone sender) {
-        try {
-            if (mRegistry != null) {
-                mRegistry.notifyEmergencyNumberList(sender.getPhoneId(), sender.getSubId());
-            }
-        } catch (RemoteException ex) {
-            // system process is dead
-        }
+        mTelephonyRegistryMgr.notifyEmergencyNumberList(sender.getSubId(), sender.getPhoneId());
+    }
+
+    @Override
+    public void notifyOutgoingEmergencyCall(Phone sender, EmergencyNumber emergencyNumber) {
+        mTelephonyRegistryMgr.notifyOutgoingEmergencyCall(
+                sender.getPhoneId(), sender.getSubId(), emergencyNumber);
+    }
+
+    @Override
+    public void notifyOutgoingEmergencySms(Phone sender, EmergencyNumber emergencyNumber) {
+        mTelephonyRegistryMgr.notifyOutgoingEmergencySms(
+                sender.getPhoneId(), sender.getSubId(), emergencyNumber);
     }
 
     @Override
     public void notifyCallQualityChanged(Phone sender, CallQuality callQuality,
-            int callNetworkType) {
-        try {
-            if (mRegistry != null) {
-                mRegistry.notifyCallQualityChanged(callQuality, sender.getPhoneId(),
-                        sender.getSubId(), callNetworkType);
-            }
-        } catch (RemoteException ex) {
-            // system process is dead
-        }
+        int callNetworkType) {
+        mTelephonyRegistryMgr.notifyCallQualityChanged(sender.getSubId(), sender.getPhoneId(),
+            callQuality, callNetworkType);
+    }
+
+    @Override
+    public void notifyRegistrationFailed(Phone sender, @NonNull CellIdentity cellIdentity,
+            @NonNull String chosenPlmn, int domain, int causeCode, int additionalCauseCode) {
+        mTelephonyRegistryMgr.notifyRegistrationFailed(sender.getPhoneId(), sender.getSubId(),
+                cellIdentity, chosenPlmn, domain, causeCode, additionalCauseCode);
+    }
+
+    @Override
+    public void notifyBarringInfoChanged(Phone sender, BarringInfo barringInfo) {
+        mTelephonyRegistryMgr.notifyBarringInfoChanged(sender.getPhoneId(), sender.getSubId(),
+                barringInfo);
     }
 
     /**
-     * Convert the {@link Phone.DataActivityState} enum into the TelephonyManager.DATA_* constants
-     * for the public API.
+     * Convert the {@link DataActivityState} enum into the TelephonyManager.DATA_* constants for the
+     * public API.
      */
-    public static int convertDataActivityState(Phone.DataActivityState state) {
+    public static int convertDataActivityState(DataActivityState state) {
         switch (state) {
             case DATAIN:
                 return TelephonyManager.DATA_ACTIVITY_IN;
diff --git a/src/java/com/android/internal/telephony/DeviceStateMonitor.java b/src/java/com/android/internal/telephony/DeviceStateMonitor.java
index 2c3d391..e009aad 100644
--- a/src/java/com/android/internal/telephony/DeviceStateMonitor.java
+++ b/src/java/com/android/internal/telephony/DeviceStateMonitor.java
@@ -20,30 +20,36 @@
 import static android.hardware.radio.V1_0.DeviceStateType.LOW_DATA_EXPECTED;
 import static android.hardware.radio.V1_0.DeviceStateType.POWER_SAVE_MODE;
 
+import android.app.UiModeManager;
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
+import android.content.res.Configuration;
 import android.hardware.display.DisplayManager;
-import android.hardware.radio.V1_2.IndicationFilter;
+import android.hardware.radio.V1_5.IndicationFilter;
 import android.net.ConnectivityManager;
 import android.net.Network;
 import android.net.NetworkCapabilities;
 import android.net.NetworkRequest;
+import android.net.TetheringManager;
 import android.os.BatteryManager;
 import android.os.Handler;
 import android.os.Message;
 import android.os.PowerManager;
+import android.os.Registrant;
+import android.os.RegistrantList;
+import android.provider.Settings;
 import android.telephony.AccessNetworkConstants.AccessNetworkType;
 import android.telephony.CarrierConfigManager;
-import android.telephony.Rlog;
-import android.telephony.TelephonyManager;
+import android.telephony.NetworkRegistrationInfo;
+import android.telephony.SignalThresholdInfo;
 import android.util.LocalLog;
-import android.util.SparseIntArray;
 import android.view.Display;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.IndentingPrintWriter;
+import com.android.telephony.Rlog;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
@@ -65,7 +71,7 @@
     protected static final String TAG = DeviceStateMonitor.class.getSimpleName();
 
     static final int EVENT_RIL_CONNECTED                = 0;
-    static final int EVENT_UPDATE_MODE_CHANGED          = 1;
+    static final int EVENT_CAR_MODE_CHANGED             = 1;
     @VisibleForTesting
     static final int EVENT_SCREEN_STATE_CHANGED         = 2;
     static final int EVENT_POWER_SAVE_MODE_CHANGED      = 3;
@@ -75,17 +81,21 @@
     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;
+    static final int EVENT_UPDATE_ALWAYS_REPORT_SIGNAL_STRENGTH = 8;
 
     private static final int WIFI_UNAVAILABLE = 0;
     private static final int WIFI_AVAILABLE = 1;
 
+    private static final int NR_NSA_TRACKING_INDICATIONS_OFF = 0;
+    private static final int NR_NSA_TRACKING_INDICATIONS_EXTENDED = 1;
+    private static final int NR_NSA_TRACKING_INDICATIONS_ALWAYS_ON = 2;
+
     private final Phone mPhone;
 
     private final LocalLog mLocalLog = new LocalLog(100);
 
+    private final RegistrantList mPhysicalChannelConfigRegistrants = new RegistrantList();
+
     private final NetworkRequest mWifiNetworkRequest =
             new NetworkRequest.Builder()
             .addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
@@ -162,6 +172,18 @@
      */
     private boolean mIsWifiConnected;
 
+    /**
+     * Car mode is on. True means the device is currently connected to Android Auto. This should be
+     * handled by mIsScreenOn, but the Android Auto display is private and not accessible by
+     * DeviceStateMonitor from DisplayMonitor.
+     */
+    private boolean mIsCarModeOn;
+
+    /**
+     * True indicates we should always enable the signal strength reporting from radio.
+     */
+    private boolean mIsAlwaysSignalStrengthReportingEnabled;
+
     @VisibleForTesting
     static final int CELL_INFO_INTERVAL_SHORT_MS = 2000;
     @VisibleForTesting
@@ -170,9 +192,6 @@
     /** 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();
-
     /**
      * The unsolicited response filter. See IndicationFilter defined in types.hal for the definition
      * of each bit.
@@ -219,9 +238,9 @@
                     msg = obtainMessage(EVENT_CHARGING_STATE_CHANGED);
                     msg.arg1 = 0;   // not charging
                     break;
-                case ConnectivityManager.ACTION_TETHER_STATE_CHANGED:
+                case TetheringManager.ACTION_TETHER_STATE_CHANGED:
                     ArrayList<String> activeTetherIfaces = intent.getStringArrayListExtra(
-                            ConnectivityManager.EXTRA_ACTIVE_TETHER);
+                            TetheringManager.EXTRA_ACTIVE_TETHER);
 
                     boolean isTetheringOn = activeTetherIfaces != null
                             && activeTetherIfaces.size() > 0;
@@ -229,6 +248,14 @@
                     msg = obtainMessage(EVENT_TETHERING_STATE_CHANGED);
                     msg.arg1 = isTetheringOn ? 1 : 0;
                     break;
+                case UiModeManager.ACTION_ENTER_CAR_MODE_PRIORITIZED:
+                    msg = obtainMessage(EVENT_CAR_MODE_CHANGED);
+                    msg.arg1 = 1; // car mode on
+                    break;
+                case UiModeManager.ACTION_EXIT_CAR_MODE_PRIORITIZED:
+                    msg = obtainMessage(EVENT_CAR_MODE_CHANGED);
+                    msg.arg1 = 0; // car mode off
+                    break;
                 default:
                     log("Unexpected broadcast intent: " + intent, false);
                     return;
@@ -252,18 +279,28 @@
         mIsPowerSaveOn = isPowerSaveModeOn();
         mIsCharging = isDeviceCharging();
         mIsScreenOn = isScreenOn();
+        mIsCarModeOn = isCarModeOn();
         // Assuming tethering is always off after boot up.
         mIsTetheringOn = false;
         mIsLowDataExpected = false;
 
-        log("DeviceStateMonitor mIsPowerSaveOn=" + mIsPowerSaveOn + ",mIsScreenOn="
-                + mIsScreenOn + ",mIsCharging=" + mIsCharging, false);
+        log("DeviceStateMonitor mIsTetheringOn=" + mIsTetheringOn
+                + ", mIsScreenOn=" + mIsScreenOn
+                + ", mIsCharging=" + mIsCharging
+                + ", mIsPowerSaveOn=" + mIsPowerSaveOn
+                + ", mIsLowDataExpected=" + mIsLowDataExpected
+                + ", mIsCarModeOn=" + mIsCarModeOn
+                + ", mIsWifiConnected=" + mIsWifiConnected
+                + ", mIsAlwaysSignalStrengthReportingEnabled="
+                + mIsAlwaysSignalStrengthReportingEnabled, false);
 
         final IntentFilter filter = new IntentFilter();
         filter.addAction(PowerManager.ACTION_POWER_SAVE_MODE_CHANGED);
         filter.addAction(BatteryManager.ACTION_CHARGING);
         filter.addAction(BatteryManager.ACTION_DISCHARGING);
         filter.addAction(ConnectivityManager.ACTION_TETHER_STATE_CHANGED);
+        filter.addAction(UiModeManager.ACTION_ENTER_CAR_MODE_PRIORITIZED);
+        filter.addAction(UiModeManager.ACTION_EXIT_CAR_MODE_PRIORITIZED);
         mPhone.getContext().registerReceiver(mBroadcastReceiver, filter, null, mPhone);
 
         mPhone.mCi.registerForRilConnected(this, EVENT_RIL_CONNECTED, null);
@@ -303,128 +340,104 @@
     }
 
     /**
-     * @return True if signal strength update should be turned off.
+     * @return True if signal strength update should be enabled. See details in
+     *         android.hardware.radio@1.2::IndicationFilter::SIGNAL_STRENGTH.
      */
-    private boolean shouldTurnOffSignalStrength() {
-        // We should not turn off signal strength update if one of the following condition is true.
+    private boolean shouldEnableSignalStrengthReports() {
+        // We should enable signal strength update if one of the following condition is true.
         // 1. The device is charging.
         // 2. When the screen is on.
-        // 3. When the update mode is IGNORE_SCREEN_OFF. This mode is used in some corner cases like
-        //    when Bluetooth carkit is connected, we still want to update signal strength even
-        //    when screen is off.
-        if (mIsCharging || mIsScreenOn
-                || mUpdateModes.get(TelephonyManager.INDICATION_FILTER_SIGNAL_STRENGTH)
-                == TelephonyManager.INDICATION_UPDATE_MODE_IGNORE_SCREEN_OFF) {
-            return false;
-        }
-
-        // In all other cases, we turn off signal strength update.
-        return true;
+        // 3. Any of system services is registrating to always listen to signal strength changes
+        return mIsAlwaysSignalStrengthReportingEnabled || mIsCharging || mIsScreenOn;
     }
 
     /**
-     * @return True if full network update should be turned off. Only significant changes will
-     * trigger the network update unsolicited response.
+     * @return True if full network state update should be enabled. When off, only significant
+     *         changes will trigger the network update unsolicited response. See details in
+     *         android.hardware.radio@1.2::IndicationFilter::FULL_NETWORK_STATE.
      */
-    private boolean shouldTurnOffFullNetworkUpdate() {
-        // We should not turn off full network update if one of the following condition is true.
-        // 1. The device is charging.
-        // 2. When the screen is on.
-        // 3. When data tethering is on.
-        // 4. When the update mode is IGNORE_SCREEN_OFF.
-        if (mIsCharging || mIsScreenOn || mIsTetheringOn
-                || mUpdateModes.get(TelephonyManager.INDICATION_FILTER_FULL_NETWORK_STATE)
-                == TelephonyManager.INDICATION_UPDATE_MODE_IGNORE_SCREEN_OFF) {
-            return false;
-        }
-
-        // In all other cases, we turn off full network state update.
-        return true;
+    private boolean shouldEnableFullNetworkStateReports() {
+        return shouldEnableNrTrackingIndications();
     }
 
     /**
-     * @return True if data dormancy status update should be turned off.
+     * @return True if data call dormancy changed update should be enabled. See details in
+     *         android.hardware.radio@1.2::IndicationFilter::DATA_CALL_DORMANCY_CHANGED.
      */
-    private boolean shouldTurnOffDormancyUpdate() {
-        // We should not turn off data dormancy update if one of the following condition is true.
-        // 1. The device is charging.
-        // 2. When the screen is on.
-        // 3. When data tethering is on.
-        // 4. When the update mode is IGNORE_SCREEN_OFF.
-        if (mIsCharging || mIsScreenOn || mIsTetheringOn
-                || mUpdateModes.get(TelephonyManager.INDICATION_FILTER_DATA_CALL_DORMANCY_CHANGED)
-                == TelephonyManager.INDICATION_UPDATE_MODE_IGNORE_SCREEN_OFF) {
-            return false;
-        }
-
-        // In all other cases, we turn off data dormancy update.
-        return true;
+    private boolean shouldEnableDataCallDormancyChangedReports() {
+        return shouldEnableNrTrackingIndications();
     }
 
     /**
-     * @return True if link capacity estimate update should be turned off.
+     * @return True if link capacity estimate update should be enabled. See details in
+     *         android.hardware.radio@1.2::IndicationFilter::LINK_CAPACITY_ESTIMATE.
      */
-    private boolean shouldTurnOffLinkCapacityEstimate() {
-        // We should not turn off link capacity update if one of the following condition is true.
-        // 1. The device is charging.
-        // 2. When the screen is on.
-        // 3. When data tethering is on.
-        // 4. When the update mode is IGNORE_SCREEN_OFF.
-        if (mIsCharging || mIsScreenOn || mIsTetheringOn
-                || mUpdateModes.get(TelephonyManager.INDICATION_FILTER_LINK_CAPACITY_ESTIMATE)
-                == TelephonyManager.INDICATION_UPDATE_MODE_IGNORE_SCREEN_OFF) {
-            return false;
-        }
-
-        // In all other cases, we turn off link capacity update.
-        return true;
+    private boolean shouldEnableLinkCapacityEstimateReports() {
+        return shouldEnableHighPowerConsumptionIndications();
     }
 
     /**
-     * @return True if physical channel config update should be turned off.
+     * @return True if physical channel config update should be enabled. See details in
+     *         android.hardware.radio@1.2::IndicationFilter::PHYSICAL_CHANNEL_CONFIG.
      */
-    private boolean shouldTurnOffPhysicalChannelConfig() {
-        // We should not turn off physical channel update if one of the following condition is true.
-        // 1. The device is charging.
-        // 2. When the screen is on.
-        // 3. When data tethering is on.
-        // 4. When the update mode is IGNORE_SCREEN_OFF.
-        if (mIsCharging || mIsScreenOn || mIsTetheringOn
-                || mUpdateModes.get(TelephonyManager.INDICATION_FILTER_PHYSICAL_CHANNEL_CONFIG)
-                == TelephonyManager.INDICATION_UPDATE_MODE_IGNORE_SCREEN_OFF) {
-            return false;
-        }
-
-        // In all other cases, we turn off physical channel config update.
-        return true;
+    private boolean shouldEnablePhysicalChannelConfigReports() {
+        return shouldEnableNrTrackingIndications();
     }
 
     /**
-     * Set indication update mode
+     * @return True if barring info update should be enabled. See details in
+     *         android.hardware.radio@1.5::IndicationFilter::BARRING_INFO.
+     */
+    private boolean shouldEnableBarringInfoReports() {
+        return shouldEnableHighPowerConsumptionIndications();
+    }
+
+    /**
+     * A common policy to determine if we should enable the necessary indications update,
+     * for power consumption's sake.
      *
-     * @param filters Indication filters. Should be a bitmask of INDICATION_FILTER_XXX.
-     * @param mode The voice activation state
+     * @return True if the response update should be enabled.
      */
-    public void setIndicationUpdateMode(int filters, int mode) {
-        sendMessage(obtainMessage(EVENT_UPDATE_MODE_CHANGED, filters, mode));
+    private boolean shouldEnableHighPowerConsumptionIndications() {
+        // We should enable indications reports if one of the following condition is true.
+        // 1. The device is charging.
+        // 2. When the screen is on.
+        // 3. When the tethering is on.
+        // 4. When car mode (Android Auto) is on.
+        return mIsCharging || mIsScreenOn || mIsTetheringOn || mIsCarModeOn;
     }
 
-    private void onSetIndicationUpdateMode(int filters, int mode) {
-        if ((filters & TelephonyManager.INDICATION_FILTER_SIGNAL_STRENGTH) != 0) {
-            mUpdateModes.put(TelephonyManager.INDICATION_FILTER_SIGNAL_STRENGTH, mode);
+    /**
+     * For 5G NSA devices, a policy to determine if we should enable NR tracking indications.
+     *
+     * @return True if the response update should be enabled.
+     */
+    private boolean shouldEnableNrTrackingIndications() {
+        int trackingMode = Settings.Global.getInt(mPhone.getContext().getContentResolver(),
+                Settings.Global.NR_NSA_TRACKING_SCREEN_OFF_MODE, NR_NSA_TRACKING_INDICATIONS_OFF);
+        switch (trackingMode) {
+            case NR_NSA_TRACKING_INDICATIONS_ALWAYS_ON:
+                return true;
+            case NR_NSA_TRACKING_INDICATIONS_EXTENDED:
+                if (mPhone.getServiceState().getNrState()
+                        == NetworkRegistrationInfo.NR_STATE_CONNECTED) {
+                    return true;
+                }
+                // fallthrough
+            case NR_NSA_TRACKING_INDICATIONS_OFF:
+                return shouldEnableHighPowerConsumptionIndications();
+            default:
+                return shouldEnableHighPowerConsumptionIndications();
         }
-        if ((filters & TelephonyManager.INDICATION_FILTER_FULL_NETWORK_STATE) != 0) {
-            mUpdateModes.put(TelephonyManager.INDICATION_FILTER_FULL_NETWORK_STATE, mode);
-        }
-        if ((filters & TelephonyManager.INDICATION_FILTER_DATA_CALL_DORMANCY_CHANGED) != 0) {
-            mUpdateModes.put(TelephonyManager.INDICATION_FILTER_DATA_CALL_DORMANCY_CHANGED, mode);
-        }
-        if ((filters & TelephonyManager.INDICATION_FILTER_LINK_CAPACITY_ESTIMATE) != 0) {
-            mUpdateModes.put(TelephonyManager.INDICATION_FILTER_LINK_CAPACITY_ESTIMATE, mode);
-        }
-        if ((filters & TelephonyManager.INDICATION_FILTER_PHYSICAL_CHANNEL_CONFIG) != 0) {
-            mUpdateModes.put(TelephonyManager.INDICATION_FILTER_PHYSICAL_CHANNEL_CONFIG, mode);
-        }
+    }
+
+    /**
+     * Set if Telephony need always report signal strength.
+     *
+     * @param isEnable
+     */
+    public void setAlwaysReportSignalStrength(boolean isEnable) {
+        sendMessage(obtainMessage(EVENT_UPDATE_ALWAYS_REPORT_SIGNAL_STRENGTH, isEnable ? 1 : 0));
     }
 
     /**
@@ -440,13 +453,12 @@
             case EVENT_RADIO_AVAILABLE:
                 onReset();
                 break;
-            case EVENT_UPDATE_MODE_CHANGED:
-                onSetIndicationUpdateMode(msg.arg1, msg.arg2);
-                break;
             case EVENT_SCREEN_STATE_CHANGED:
             case EVENT_POWER_SAVE_MODE_CHANGED:
             case EVENT_CHARGING_STATE_CHANGED:
             case EVENT_TETHERING_STATE_CHANGED:
+            case EVENT_UPDATE_ALWAYS_REPORT_SIGNAL_STRENGTH:
+            case EVENT_CAR_MODE_CHANGED:
                 onUpdateDeviceState(msg.what, msg.arg1 != 0);
                 break;
             case EVENT_WIFI_CONNECTION_CHANGED:
@@ -464,6 +476,7 @@
      * @param state True if enabled/on, otherwise disabled/off.
      */
     private void onUpdateDeviceState(int eventType, boolean state) {
+        final boolean shouldEnableBarringInfoReportsOld = shouldEnableBarringInfoReports();
         switch (eventType) {
             case EVENT_SCREEN_STATE_CHANGED:
                 if (mIsScreenOn == state) return;
@@ -486,7 +499,14 @@
             case EVENT_WIFI_CONNECTION_CHANGED:
                 if (mIsWifiConnected == state) return;
                 mIsWifiConnected = state;
-
+                break;
+            case EVENT_UPDATE_ALWAYS_REPORT_SIGNAL_STRENGTH:
+                if (mIsAlwaysSignalStrengthReportingEnabled == state) return;
+                mIsAlwaysSignalStrengthReportingEnabled = state;
+                break;
+            case EVENT_CAR_MODE_CHANGED:
+                if (mIsCarModeOn == state) return;
+                mIsCarModeOn = state;
                 break;
             default:
                 return;
@@ -504,28 +524,49 @@
             sendDeviceState(LOW_DATA_EXPECTED, mIsLowDataExpected);
         }
 
-        int newFilter = 0;
-        if (!shouldTurnOffSignalStrength()) {
+        // Registration Failure is always reported.
+        int newFilter = IndicationFilter.REGISTRATION_FAILURE;
+
+        if (shouldEnableSignalStrengthReports()) {
             newFilter |= IndicationFilter.SIGNAL_STRENGTH;
         }
 
-        if (!shouldTurnOffFullNetworkUpdate()) {
+        if (shouldEnableFullNetworkStateReports()) {
             newFilter |= IndicationFilter.FULL_NETWORK_STATE;
         }
 
-        if (!shouldTurnOffDormancyUpdate()) {
+        if (shouldEnableDataCallDormancyChangedReports()) {
             newFilter |= IndicationFilter.DATA_CALL_DORMANCY_CHANGED;
         }
 
-        if (!shouldTurnOffLinkCapacityEstimate()) {
+        if (shouldEnableLinkCapacityEstimateReports()) {
             newFilter |= IndicationFilter.LINK_CAPACITY_ESTIMATE;
         }
 
-        if (!shouldTurnOffPhysicalChannelConfig()) {
+        if (shouldEnablePhysicalChannelConfigReports()) {
             newFilter |= IndicationFilter.PHYSICAL_CHANNEL_CONFIG;
         }
 
+        final boolean shouldEnableBarringInfoReports = shouldEnableBarringInfoReports();
+        if (shouldEnableBarringInfoReports) {
+            newFilter |= IndicationFilter.BARRING_INFO;
+        }
+
+        // notify PhysicalChannelConfig registrants if state changes
+        if ((newFilter & IndicationFilter.PHYSICAL_CHANNEL_CONFIG)
+                != (mUnsolicitedResponseFilter & IndicationFilter.PHYSICAL_CHANNEL_CONFIG)) {
+            mPhysicalChannelConfigRegistrants.notifyResult(
+                    (newFilter & IndicationFilter.PHYSICAL_CHANNEL_CONFIG) != 0);
+        }
+
         setUnsolResponseFilter(newFilter, false);
+
+        // Pull barring info AFTER setting filter, the order matters
+        if (shouldEnableBarringInfoReports && !shouldEnableBarringInfoReportsOld) {
+            if (DBG) log("Manually pull barring info...", true);
+            // use a null message since we don't care of receiving response
+            mPhone.mCi.getBarringInfo(null);
+        }
     }
 
     /**
@@ -587,14 +628,28 @@
     }
 
     private void setSignalStrengthReportingCriteria() {
-        mPhone.setSignalStrengthReportingCriteria(
-                AccessNetworkThresholds.GERAN, AccessNetworkType.GERAN);
-        mPhone.setSignalStrengthReportingCriteria(
-                AccessNetworkThresholds.UTRAN, AccessNetworkType.UTRAN);
-        mPhone.setSignalStrengthReportingCriteria(
-                AccessNetworkThresholds.EUTRAN, AccessNetworkType.EUTRAN);
-        mPhone.setSignalStrengthReportingCriteria(
-                AccessNetworkThresholds.CDMA2000, AccessNetworkType.CDMA2000);
+        mPhone.setSignalStrengthReportingCriteria(SignalThresholdInfo.SIGNAL_RSSI,
+                AccessNetworkThresholds.GERAN, AccessNetworkType.GERAN, true);
+        mPhone.setSignalStrengthReportingCriteria(SignalThresholdInfo.SIGNAL_RSCP,
+                AccessNetworkThresholds.UTRAN, AccessNetworkType.UTRAN, true);
+        mPhone.setSignalStrengthReportingCriteria(SignalThresholdInfo.SIGNAL_RSRP,
+                AccessNetworkThresholds.EUTRAN_RSRP, AccessNetworkType.EUTRAN, true);
+        mPhone.setSignalStrengthReportingCriteria(SignalThresholdInfo.SIGNAL_RSSI,
+                AccessNetworkThresholds.CDMA2000, AccessNetworkType.CDMA2000, true);
+        if (mPhone.getHalVersion().greaterOrEqual(RIL.RADIO_HAL_VERSION_1_5)) {
+            mPhone.setSignalStrengthReportingCriteria(SignalThresholdInfo.SIGNAL_RSRQ,
+                    AccessNetworkThresholds.EUTRAN_RSRQ, AccessNetworkType.EUTRAN, false);
+            mPhone.setSignalStrengthReportingCriteria(SignalThresholdInfo.SIGNAL_RSSNR,
+                    AccessNetworkThresholds.EUTRAN_RSSNR, AccessNetworkType.EUTRAN, true);
+
+            // Defaultly we only need SSRSRP for NGRAN signal criteria reporting
+            mPhone.setSignalStrengthReportingCriteria(SignalThresholdInfo.SIGNAL_SSRSRP,
+                    AccessNetworkThresholds.NGRAN_RSRSRP, AccessNetworkType.NGRAN, true);
+            mPhone.setSignalStrengthReportingCriteria(SignalThresholdInfo.SIGNAL_SSRSRQ,
+                    AccessNetworkThresholds.NGRAN_RSRSRQ, AccessNetworkType.NGRAN, false);
+            mPhone.setSignalStrengthReportingCriteria(SignalThresholdInfo.SIGNAL_SSSINR,
+                    AccessNetworkThresholds.NGRAN_SSSINR, AccessNetworkType.NGRAN, false);
+        }
     }
 
     private void setLinkCapacityReportingCriteria() {
@@ -606,6 +661,10 @@
                 LINK_CAPACITY_UPLINK_THRESHOLDS, AccessNetworkType.EUTRAN);
         mPhone.setLinkCapacityReportingCriteria(LINK_CAPACITY_DOWNLINK_THRESHOLDS,
                 LINK_CAPACITY_UPLINK_THRESHOLDS, AccessNetworkType.CDMA2000);
+        if (mPhone.getHalVersion().greaterOrEqual(RIL.RADIO_HAL_VERSION_1_5)) {
+            mPhone.setLinkCapacityReportingCriteria(LINK_CAPACITY_DOWNLINK_THRESHOLDS,
+                    LINK_CAPACITY_UPLINK_THRESHOLDS, AccessNetworkType.NGRAN);
+        }
     }
 
     private void setCellInfoMinInterval(int rate) {
@@ -619,7 +678,9 @@
     private boolean isPowerSaveModeOn() {
         final PowerManager pm = (PowerManager) mPhone.getContext().getSystemService(
                 Context.POWER_SERVICE);
-        return pm.isPowerSaveMode();
+        boolean retval = pm.isPowerSaveMode();
+        log("isPowerSaveModeOn=" + retval, true);
+        return retval;
     }
 
     /**
@@ -631,12 +692,14 @@
     private boolean isDeviceCharging() {
         final BatteryManager bm = (BatteryManager) mPhone.getContext().getSystemService(
                 Context.BATTERY_SERVICE);
-        return bm.isCharging();
+        boolean retval = bm.isCharging();
+        log("isDeviceCharging=" + retval, true);
+        return retval;
     }
 
     /**
-     * @return True if one the device's screen (e.g. main screen, wifi display, HDMI display, or
-     *         Android auto, etc...) is on.
+     * @return True if one the device's screen (e.g. main screen, wifi display, HDMI display etc...)
+     * is on.
      */
     private boolean isScreenOn() {
         // Note that we don't listen to Intent.SCREEN_ON and Intent.SCREEN_OFF because they are no
@@ -651,7 +714,7 @@
                 // Anything other than STATE_ON is treated as screen off, such as STATE_DOZE,
                 // STATE_DOZE_SUSPEND, etc...
                 if (display.getState() == Display.STATE_ON) {
-                    log("Screen " + Display.typeToString(display.getType()) + " on", true);
+                    log("Screen on for display=" + display, true);
                     return true;
                 }
             }
@@ -664,6 +727,40 @@
     }
 
     /**
+     * @return True if car mode (Android Auto) is on.
+     */
+    private boolean isCarModeOn() {
+        final UiModeManager umm = (UiModeManager) mPhone.getContext().getSystemService(
+                Context.UI_MODE_SERVICE);
+        if (umm == null) return false;
+        boolean retval = umm.getCurrentModeType() == Configuration.UI_MODE_TYPE_CAR;
+        log("isCarModeOn=" + retval, true);
+        return retval;
+    }
+
+    /**
+     * Register for PhysicalChannelConfig notifications changed. On change, msg.obj will be an
+     * AsyncResult with a boolean result. AsyncResult.result is true if notifications are enabled
+     * and false if they are disabled.
+     *
+     * @param h Handler to notify
+     * @param what msg.what when the message is delivered
+     * @param obj AsyncResult.userObj when the message is delivered
+     */
+    public void registerForPhysicalChannelConfigNotifChanged(Handler h, int what, Object obj) {
+        Registrant r = new Registrant(h, what, obj);
+        mPhysicalChannelConfigRegistrants.add(r);
+    }
+
+    /**
+     * Unregister for PhysicalChannelConfig notifications changed.
+     * @param h Handler to notify
+     */
+    public void unregisterForPhysicalChannelConfigNotifChanged(Handler h) {
+        mPhysicalChannelConfigRegistrants.remove(h);
+    }
+
+    /**
      * @param msg Debug message
      * @param logIntoLocalLog True if log into the local log
      */
@@ -689,8 +786,11 @@
         ipw.println("mIsCharging=" + mIsCharging);
         ipw.println("mIsPowerSaveOn=" + mIsPowerSaveOn);
         ipw.println("mIsLowDataExpected=" + mIsLowDataExpected);
+        ipw.println("mIsCarModeOn=" + mIsCarModeOn);
         ipw.println("mUnsolicitedResponseFilter=" + mUnsolicitedResponseFilter);
         ipw.println("mIsWifiConnected=" + mIsWifiConnected);
+        ipw.println("mIsAlwaysSignalStrengthReportingEnabled="
+                + mIsAlwaysSignalStrengthReportingEnabled);
         ipw.println("Local logs:");
         ipw.increaseIndent();
         mLocalLog.dump(fd, ipw, args);
@@ -730,11 +830,11 @@
         };
 
         /**
-         * List of default dBm thresholds for EUTRAN {@link AccessNetworkType}.
+         * List of default dBm RSRP thresholds for EUTRAN {@link AccessNetworkType}.
          *
          * These thresholds are taken from the LTE RSRP defaults in {@link CarrierConfigManager}.
          */
-        public static final int[] EUTRAN = new int[] {
+        public static final int[] EUTRAN_RSRP = new int[] {
             -128, /* SIGNAL_STRENGTH_POOR */
             -118, /* SIGNAL_STRENGTH_MODERATE */
             -108, /* SIGNAL_STRENGTH_GOOD */
@@ -742,6 +842,30 @@
         };
 
         /**
+         * List of default dB RSRQ thresholds for EUTRAN {@link AccessNetworkType}.
+         *
+         * These thresholds are taken from the LTE RSRQ defaults in {@link CarrierConfigManager}.
+         */
+        public static final int[] EUTRAN_RSRQ = new int[] {
+            -20,  /* SIGNAL_STRENGTH_POOR */
+            -17,  /* SIGNAL_STRENGTH_MODERATE */
+            -14,  /* SIGNAL_STRENGTH_GOOD */
+            -11   /* SIGNAL_STRENGTH_GREAT */
+        };
+
+        /**
+         * List of default dB RSSNR thresholds for EUTRAN {@link AccessNetworkType}.
+         *
+         * These thresholds are taken from the LTE RSSNR defaults in {@link CarrierConfigManager}.
+         */
+        public static final int[] EUTRAN_RSSNR = new int[] {
+            -3,  /* SIGNAL_STRENGTH_POOR */
+            1,   /* SIGNAL_STRENGTH_MODERATE */
+            5,   /* SIGNAL_STRENGTH_GOOD */
+            13   /* SIGNAL_STRENGTH_GREAT */
+        };
+
+        /**
          * List of dBm thresholds for CDMA2000 {@link AccessNetworkType}.
          *
          * These correspond to EVDO level thresholds.
@@ -752,6 +876,36 @@
             -75,
             -65
         };
+
+        /**
+         * List of dB thresholds for NGRAN {@link AccessNetworkType} RSRSRP
+         */
+        public static final int[] NGRAN_RSRSRP = new int[] {
+            -110, /* SIGNAL_STRENGTH_POOR */
+            -90, /* SIGNAL_STRENGTH_MODERATE */
+            -80, /* SIGNAL_STRENGTH_GOOD */
+            -65,  /* SIGNAL_STRENGTH_GREAT */
+        };
+
+        /**
+         * List of dB thresholds for NGRAN {@link AccessNetworkType} RSRSRP
+         */
+        public static final int[] NGRAN_RSRSRQ = new int[] {
+            -16, /* SIGNAL_STRENGTH_POOR */
+            -12, /* SIGNAL_STRENGTH_MODERATE */
+            -9, /* SIGNAL_STRENGTH_GOOD */
+            -6  /* SIGNAL_STRENGTH_GREAT */
+        };
+
+        /**
+         * List of dB thresholds for NGRAN {@link AccessNetworkType} SSSINR
+         */
+        public static final int[] NGRAN_SSSINR = new int[] {
+            -5, /* SIGNAL_STRENGTH_POOR */
+            5, /* SIGNAL_STRENGTH_MODERATE */
+            15, /* SIGNAL_STRENGTH_GOOD */
+            30  /* SIGNAL_STRENGTH_GREAT */
+        };
     }
 
     /**
@@ -770,10 +924,13 @@
             10000,  // file downloading
             20000,  // 4K video streaming
             50000,  // LTE-Advanced speeds
+            75000,
             100000,
             200000, // 5G speeds
             500000,
-            1000000
+            1000000,
+            1500000,
+            2000000
     };
 
     /** Uplink reporting thresholds in kbps */
@@ -785,7 +942,9 @@
             10000,  // file uploading
             20000,  // 4K video calling
             50000,
+            75000,
             100000,
-            200000
+            200000,
+            500000
     };
 }
diff --git a/src/java/com/android/internal/telephony/DisplayInfoController.java b/src/java/com/android/internal/telephony/DisplayInfoController.java
new file mode 100644
index 0000000..6870355
--- /dev/null
+++ b/src/java/com/android/internal/telephony/DisplayInfoController.java
@@ -0,0 +1,103 @@
+/*
+ * Copyright (C) 2020 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;
+
+import android.os.Handler;
+import android.os.Registrant;
+import android.os.RegistrantList;
+import android.telephony.TelephonyDisplayInfo;
+
+import com.android.telephony.Rlog;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+
+/**
+ * The DisplayInfoController updates and broadcasts all changes to {@link TelephonyDisplayInfo}.
+ * It manages all the information necessary for display purposes. Clients can register for display
+ * info changes via {@link #registerForTelephonyDisplayInfoChanged} and obtain the current
+ * TelephonyDisplayInfo via {@link #getTelephonyDisplayInfo}.
+ */
+public class DisplayInfoController extends Handler {
+    private static final String TAG = "DisplayInfoController";
+    private final Phone mPhone;
+    private final NetworkTypeController mNetworkTypeController;
+    private final RegistrantList mTelephonyDisplayInfoChangedRegistrants = new RegistrantList();
+    private TelephonyDisplayInfo mTelephonyDisplayInfo;
+
+    public DisplayInfoController(Phone phone) {
+        mPhone = phone;
+        mNetworkTypeController = new NetworkTypeController(phone, this);
+        mNetworkTypeController.sendMessage(NetworkTypeController.EVENT_UPDATE);
+    }
+
+    /**
+     * @return the current TelephonyDisplayInfo
+     */
+    public TelephonyDisplayInfo getTelephonyDisplayInfo() {
+        return mTelephonyDisplayInfo;
+    }
+
+    /**
+     * Update TelephonyDisplayInfo based on network type and override network type, received from
+     * NetworkTypeController.
+     */
+    public void updateTelephonyDisplayInfo() {
+        TelephonyDisplayInfo newDisplayInfo = new TelephonyDisplayInfo(
+                mPhone.getServiceState().getDataNetworkType(),
+                mNetworkTypeController.getOverrideNetworkType());
+        if (!newDisplayInfo.equals(mTelephonyDisplayInfo)) {
+            Rlog.d(TAG, "TelephonyDisplayInfo[" + mPhone.getPhoneId() + "] changed from "
+                    + mTelephonyDisplayInfo + " to " + newDisplayInfo);
+            mTelephonyDisplayInfo = newDisplayInfo;
+            mTelephonyDisplayInfoChangedRegistrants.notifyRegistrants();
+            mPhone.notifyDisplayInfoChanged(mTelephonyDisplayInfo);
+        }
+    }
+
+    /**
+     * Register for TelephonyDisplayInfo changed.
+     * @param h Handler to notify
+     * @param what msg.what when the message is delivered
+     * @param obj msg.obj when the message is delivered
+     */
+    public void registerForTelephonyDisplayInfoChanged(Handler h, int what, Object obj) {
+        Registrant r = new Registrant(h, what, obj);
+        mTelephonyDisplayInfoChangedRegistrants.add(r);
+    }
+
+    /**
+     * Unregister for TelephonyDisplayInfo changed.
+     * @param h Handler to notify
+     */
+    public void unregisterForTelephonyDisplayInfoChanged(Handler h) {
+        mTelephonyDisplayInfoChangedRegistrants.remove(h);
+    }
+
+    /**
+     * Dump the current state.
+     */
+    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+        pw.println("DisplayInfoController:");
+        pw.println(" mPhone=" + mPhone.getPhoneName());
+        pw.println(" mTelephonyDisplayInfo=" + mTelephonyDisplayInfo.toString());
+        pw.flush();
+        pw.println(" ***************************************");
+        mNetworkTypeController.dump(fd, pw, args);
+        pw.flush();
+    }
+}
diff --git a/src/java/com/android/internal/telephony/DriverCall.java b/src/java/com/android/internal/telephony/DriverCall.java
index 8c2c3db..a67de7a 100644
--- a/src/java/com/android/internal/telephony/DriverCall.java
+++ b/src/java/com/android/internal/telephony/DriverCall.java
@@ -16,17 +16,19 @@
 
 package com.android.internal.telephony;
 
-import android.annotation.UnsupportedAppUsage;
-import android.telephony.Rlog;
-import java.lang.Comparable;
+import android.compat.annotation.UnsupportedAppUsage;
 import android.telephony.PhoneNumberUtils;
 
+import com.android.telephony.Rlog;
+
 /**
  * {@hide}
  */
 public class DriverCall implements Comparable<DriverCall> {
     static final String LOG_TAG = "DriverCall";
 
+    @UnsupportedAppUsage(implicitMember =
+            "values()[Lcom/android/internal/telephony/DriverCall$State;")
     public enum State {
         @UnsupportedAppUsage
         ACTIVE,
diff --git a/src/java/com/android/internal/telephony/GsmCdmaCall.java b/src/java/com/android/internal/telephony/GsmCdmaCall.java
index da2b057..93f2e6b 100644
--- a/src/java/com/android/internal/telephony/GsmCdmaCall.java
+++ b/src/java/com/android/internal/telephony/GsmCdmaCall.java
@@ -16,8 +16,7 @@
 
 package com.android.internal.telephony;
 
-import android.annotation.UnsupportedAppUsage;
-import java.util.List;
+import android.compat.annotation.UnsupportedAppUsage;
 
 /**
  * {@hide}
@@ -36,19 +35,13 @@
     /************************** Overridden from Call *************************/
 
     @Override
-    public List<Connection> getConnections() {
-        // FIXME should return Collections.unmodifiableList();
-        return mConnections;
-    }
-
-    @Override
     public Phone getPhone() {
         return mOwner.getPhone();
     }
 
     @Override
     public boolean isMultiparty() {
-        return mConnections.size() > 1;
+        return getConnectionsCount() > 1;
     }
 
     /** Please note: if this is the foreground call and a
@@ -60,6 +53,16 @@
         mOwner.hangup(this);
     }
 
+    /**
+     * Hangup the ringing call with a specified reason; reason is not supported on GSM/CDMA.
+     * @param rejectReason
+     */
+    @Override
+    public void hangup(@android.telecom.Call.RejectReason int rejectReason)
+            throws CallStateException {
+        mOwner.hangup(this);
+    }
+
     @Override
     public String toString() {
         return mState.toString();
@@ -68,14 +71,14 @@
     //***** Called from GsmCdmaConnection
 
     public void attach(Connection conn, DriverCall dc) {
-        mConnections.add(conn);
+        addConnection(conn);
 
         mState = stateFromDCState (dc.state);
     }
 
     @UnsupportedAppUsage
     public void attachFake(Connection conn, State state) {
-        mConnections.add(conn);
+        addConnection(conn);
 
         mState = state;
     }
@@ -89,8 +92,8 @@
 
             boolean hasOnlyDisconnectedConnections = true;
 
-            for (int i = 0, s = mConnections.size(); i < s; i ++) {
-                if (mConnections.get(i).getState() != State.DISCONNECTED) {
+            for (Connection c : getConnections()) {
+                if (c.getState() != State.DISCONNECTED) {
                     hasOnlyDisconnectedConnections = false;
                     break;
                 }
@@ -106,9 +109,9 @@
     }
 
     public void detach(GsmCdmaConnection conn) {
-        mConnections.remove(conn);
+        removeConnection(conn);
 
-        if (mConnections.size() == 0) {
+        if (getConnectionsCount() == 0) {
             mState = State.IDLE;
         }
     }
@@ -132,7 +135,7 @@
      * connections to be added via "conference"
      */
     /*package*/ boolean isFull() {
-        return mConnections.size() == mOwner.getMaxConnectionsPerCall();
+        return getConnectionsCount() == mOwner.getMaxConnectionsPerCall();
     }
 
     //***** Called from GsmCdmaCallTracker
@@ -144,10 +147,8 @@
      * but no response has yet been received so update() has not yet been called
      */
     void onHangupLocal() {
-        for (int i = 0, s = mConnections.size(); i < s; i++) {
-            GsmCdmaConnection cn = (GsmCdmaConnection)mConnections.get(i);
-
-            cn.onHangupLocal();
+        for (Connection conn : getConnections()) {
+            ((GsmCdmaConnection) conn).onHangupLocal();
         }
         mState = State.DISCONNECTING;
     }
diff --git a/src/java/com/android/internal/telephony/GsmCdmaCallTracker.java b/src/java/com/android/internal/telephony/GsmCdmaCallTracker.java
index 0a7acee..4659ea4 100755
--- a/src/java/com/android/internal/telephony/GsmCdmaCallTracker.java
+++ b/src/java/com/android/internal/telephony/GsmCdmaCallTracker.java
@@ -16,7 +16,7 @@
 
 package com.android.internal.telephony;
 
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
@@ -28,14 +28,13 @@
 import android.os.PersistableBundle;
 import android.os.Registrant;
 import android.os.RegistrantList;
-import android.os.SystemProperties;
+import android.sysprop.TelephonyProperties;
 import android.telecom.TelecomManager;
 import android.telephony.CarrierConfigManager;
 import android.telephony.CellLocation;
 import android.telephony.DisconnectCause;
 import android.telephony.PhoneNumberUtils;
-import android.telephony.Rlog;
-import android.telephony.ServiceState;
+import android.telephony.ServiceState.RilRadioTechnology;
 import android.telephony.TelephonyManager;
 import android.telephony.cdma.CdmaCellLocation;
 import android.telephony.gsm.GsmCellLocation;
@@ -45,6 +44,7 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.telephony.cdma.CdmaCallWaitingNotification;
 import com.android.internal.telephony.metrics.TelephonyMetrics;
+import com.android.telephony.Rlog;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
@@ -107,7 +107,6 @@
     private boolean mPendingCallInEcm;
     private boolean mIsInEmergencyCall;
     private int mPendingCallClirMode;
-    private boolean mIsEcmTimerCanceled;
     private int m3WayCallFlashDelay;
 
     /**
@@ -119,7 +118,8 @@
             if (intent.getAction().equals(
                     TelephonyIntents.ACTION_EMERGENCY_CALLBACK_MODE_CHANGED)) {
 
-                boolean isInEcm = intent.getBooleanExtra(PhoneConstants.PHONE_IN_ECM_STATE, false);
+                boolean isInEcm = intent.getBooleanExtra(
+                        TelephonyManager.EXTRA_PHONE_IN_ECM_STATE, false);
                 log("Received ACTION_EMERGENCY_CALLBACK_MODE_CHANGED isInEcm = " + isInEcm);
 
                 // If we exit ECM mode, notify all connections.
@@ -188,7 +188,7 @@
             mPendingCallInEcm = false;
             mIsInEmergencyCall = false;
             mPendingCallClirMode = CommandsInterface.CLIR_DEFAULT;
-            mIsEcmTimerCanceled = false;
+            mPhone.setEcmCanceledForEmergency(false /*isCanceled*/);
             m3WayCallFlashDelay = 0;
             mCi.registerForCallWaitingInfo(this, EVENT_CALL_WAITING_INFO_CDMA, null);
         }
@@ -260,16 +260,13 @@
 
     @UnsupportedAppUsage
     private void fakeHoldForegroundBeforeDial() {
-        List<Connection> connCopy;
-
         // We need to make a copy here, since fakeHoldBeforeDial()
         // modifies the lists, and we don't want to reverse the order
-        connCopy = (List<Connection>) mForegroundCall.mConnections.clone();
+        ArrayList<Connection> connCopy = mForegroundCall.getConnections();
 
-        for (int i = 0, s = connCopy.size() ; i < s ; i++) {
-            GsmCdmaConnection conn = (GsmCdmaConnection)connCopy.get(i);
-
-            conn.fakeHoldBeforeDial();
+        for (Connection conn : connCopy) {
+            GsmCdmaConnection gsmCdmaConn = (GsmCdmaConnection) conn;
+            gsmCdmaConn.fakeHoldBeforeDial();
         }
     }
 
@@ -303,6 +300,7 @@
             // This is a hack to delay DIAL so that it is sent out to RIL only after
             // EVENT_SWITCH_RESULT is received. We've seen failures when adding a new call to
             // multi-way conference calls due to DIAL being sent out before SWITCH is processed
+            // TODO: setup duration metrics won't capture this
             try {
                 Thread.sleep(500);
             } catch (InterruptedException e) {
@@ -321,8 +319,8 @@
             throw new CallStateException("cannot dial in current state");
         }
 
-        mPendingMO = new GsmCdmaConnection(mPhone, checkForTestEmergencyNumber(dialString),
-                this, mForegroundCall, isEmergencyCall);
+        mPendingMO = new GsmCdmaConnection(mPhone, dialString, this, mForegroundCall,
+                isEmergencyCall);
         if (intentExtras != null) {
             Rlog.d(LOG_TAG, "dialGsm - emergency dialer: " + intentExtras.getBoolean(
                     TelecomManager.EXTRA_IS_USER_INTENT_EMERGENCY_CALL));
@@ -331,7 +329,7 @@
         }
         mHangupPendingMO = false;
         mMetrics.writeRilDial(mPhone.getPhoneId(), mPendingMO, clirMode, uusInfo);
-
+        mPhone.getVoiceCallSessionStats().onRilDial(mPendingMO);
 
         if ( mPendingMO.getAddress() == null || mPendingMO.getAddress().length() == 0
                 || mPendingMO.getAddress().indexOf(PhoneNumberUtils.WILD) >= 0) {
@@ -351,7 +349,7 @@
         }
 
         if (mNumberConverted) {
-            mPendingMO.setConverted(origNumber);
+            mPendingMO.restoreDialedNumberAfterConversion(origNumber);
             mNumberConverted = false;
         }
 
@@ -368,12 +366,6 @@
     @UnsupportedAppUsage
     private void handleEcmTimer(int action) {
         mPhone.handleTimerInEmergencyCallbackMode(action);
-        switch(action) {
-            case GsmCdmaPhone.CANCEL_ECM_TIMER: mIsEcmTimerCanceled = true; break;
-            case GsmCdmaPhone.RESTART_ECM_TIMER: mIsEcmTimerCanceled = false; break;
-            default:
-                Rlog.e(LOG_TAG, "handleEcmTimer, unsupported action " + action);
-        }
     }
 
     //CDMA
@@ -414,7 +406,7 @@
         TelephonyManager tm =
                 (TelephonyManager) mPhone.getContext().getSystemService(Context.TELEPHONY_SERVICE);
         String origNumber = dialString;
-        String operatorIsoContry = tm.getNetworkCountryIsoForPhone(mPhone.getPhoneId());
+        String operatorIsoContry = tm.getNetworkCountryIso(mPhone.getPhoneId());
         String simIsoContry = tm.getSimCountryIsoForPhone(mPhone.getPhoneId());
         boolean internationalRoaming = !TextUtils.isEmpty(operatorIsoContry)
                 && !TextUtils.isEmpty(simIsoContry)
@@ -434,7 +426,7 @@
 
         // Cancel Ecm timer if a second emergency call is originating in Ecm mode
         if (isPhoneInEcmMode && isEmergencyCall) {
-            handleEcmTimer(GsmCdmaPhone.CANCEL_ECM_TIMER);
+            mPhone.handleTimerInEmergencyCallbackMode(GsmCdmaPhone.CANCEL_ECM_TIMER);
         }
 
         // The new call must be assigned to the foreground call.
@@ -444,8 +436,8 @@
             return dialThreeWay(dialString, intentExtras);
         }
 
-        mPendingMO = new GsmCdmaConnection(mPhone, checkForTestEmergencyNumber(dialString),
-                this, mForegroundCall, isEmergencyCall);
+        mPendingMO = new GsmCdmaConnection(mPhone, dialString, this, mForegroundCall,
+                isEmergencyCall);
         if (intentExtras != null) {
             Rlog.d(LOG_TAG, "dialGsm - emergency dialer: " + intentExtras.getBoolean(
                     TelecomManager.EXTRA_IS_USER_INTENT_EMERGENCY_CALL));
@@ -484,7 +476,7 @@
         }
 
         if (mNumberConverted) {
-            mPendingMO.setConverted(origNumber);
+            mPendingMO.restoreDialedNumberAfterConversion(origNumber);
             mNumberConverted = false;
         }
 
@@ -501,8 +493,7 @@
             disableDataCallInEmergencyCall(dialString);
 
             // Attach the new connection to foregroundCall
-            mPendingMO = new GsmCdmaConnection(mPhone,
-                    checkForTestEmergencyNumber(dialString), this, mForegroundCall,
+            mPendingMO = new GsmCdmaConnection(mPhone, dialString, this, mForegroundCall,
                     mIsInEmergencyCall);
             if (intentExtras != null) {
                 Rlog.d(LOG_TAG, "dialThreeWay - emergency dialer " + intentExtras.getBoolean(
@@ -561,6 +552,7 @@
             Rlog.i("phone", "acceptCall: incoming...");
             // Always unmute when answering a new call
             setMute(false);
+            mPhone.getVoiceCallSessionStats().onRilAcceptCall(mRingingCall.getConnections());
             mCi.acceptCall(obtainCompleteMessage());
         } else if (mRingingCall.getState() == GsmCdmaCall.State.WAITING) {
             if (isPhoneTypeGsm()) {
@@ -609,7 +601,7 @@
                 mCi.switchWaitingOrHoldingAndActive(
                         obtainCompleteMessage(EVENT_SWITCH_RESULT));
             } else {
-                if (mForegroundCall.getConnections().size() > 1) {
+                if (mForegroundCall.getConnectionsCount() > 1) {
                     flashAndSetGenericTrue();
                 } else {
                     // Send a flash command to CDMA network for putting the other party on hold.
@@ -656,14 +648,13 @@
      * @throws CallStateException
      */
     public void checkForDialIssues(boolean isEmergencyCall) throws CallStateException {
-        String disableCall = SystemProperties.get(
-                TelephonyProperties.PROPERTY_DISABLE_CALL, "false");
+        boolean disableCall = TelephonyProperties.disable_call().orElse(false);
 
         if (mCi.getRadioState() != TelephonyManager.RADIO_POWER_ON) {
             throw new CallStateException(CallStateException.ERROR_POWER_OFF,
                     "Modem not powered");
         }
-        if (disableCall.equals("true")) {
+        if (disableCall) {
             throw new CallStateException(CallStateException.ERROR_CALLING_DISABLED,
                     "Calling disabled via ro.telephony.disable-call property");
         }
@@ -863,8 +854,9 @@
                         mHangupPendingMO = false;
 
                         // Re-start Ecm timer when an uncompleted emergency call ends
-                        if (!isPhoneTypeGsm() && mIsEcmTimerCanceled) {
-                            handleEcmTimer(GsmCdmaPhone.RESTART_ECM_TIMER);
+                        if (!isPhoneTypeGsm() && mPhone.isEcmCanceledForEmergency()) {
+                            mPhone.handleTimerInEmergencyCallbackMode(
+                                    GsmCdmaPhone.RESTART_ECM_TIMER);
                         }
 
                         try {
@@ -940,24 +932,27 @@
                     // we need to clean up the foregroundCall and ringingCall.
                     // Loop through foreground call connections as
                     // it contains the known logical connections.
-                    int count = mForegroundCall.mConnections.size();
-                    for (int n = 0; n < count; n++) {
-                        if (Phone.DEBUG_PHONE) log("adding fgCall cn " + n + " to droppedDuringPoll");
-                        GsmCdmaConnection cn = (GsmCdmaConnection)mForegroundCall.mConnections.get(n);
-                        mDroppedDuringPoll.add(cn);
+                    ArrayList<Connection> connections = mForegroundCall.getConnections();
+                    for (Connection cn : connections) {
+                        if (Phone.DEBUG_PHONE) {
+                            log("adding fgCall cn " + cn + "to droppedDuringPoll");
+                        }
+                        mDroppedDuringPoll.add((GsmCdmaConnection) cn);
                     }
-                    count = mRingingCall.mConnections.size();
+
+                    connections = mRingingCall.getConnections();
                     // Loop through ringing call connections as
                     // it may contain the known logical connections.
-                    for (int n = 0; n < count; n++) {
-                        if (Phone.DEBUG_PHONE) log("adding rgCall cn " + n + " to droppedDuringPoll");
-                        GsmCdmaConnection cn = (GsmCdmaConnection)mRingingCall.mConnections.get(n);
-                        mDroppedDuringPoll.add(cn);
+                    for (Connection cn : connections) {
+                        if (Phone.DEBUG_PHONE) {
+                            log("adding rgCall cn " + cn + "to droppedDuringPoll");
+                        }
+                        mDroppedDuringPoll.add((GsmCdmaConnection) cn);
                     }
 
                     // Re-start Ecm timer when the connected emergency call ends
-                    if (mIsEcmTimerCanceled) {
-                        handleEcmTimer(GsmCdmaPhone.RESTART_ECM_TIMER);
+                    if (mPhone.isEcmCanceledForEmergency()) {
+                        mPhone.handleTimerInEmergencyCallbackMode(GsmCdmaPhone.RESTART_ECM_TIMER);
                     }
                     // If emergency call is not going through while dialing
                     checkAndEnableDataCallAfterEmergencyCallDropped();
@@ -1095,6 +1090,7 @@
         if (locallyDisconnectedConnections.size() > 0) {
             mMetrics.writeRilCallList(mPhone.getPhoneId(), locallyDisconnectedConnections,
                     getNetworkCountryIso());
+            mPhone.getVoiceCallSessionStats().onRilCallListChanged(locallyDisconnectedConnections);
         }
 
         /* Disconnect any pending Handover connections */
@@ -1166,6 +1162,7 @@
             if (conn != null) activeConnections.add(conn);
         }
         mMetrics.writeRilCallList(mPhone.getPhoneId(), activeConnections, getNetworkCountryIso());
+        mPhone.getVoiceCallSessionStats().onRilCallListChanged(activeConnections);
     }
 
     private void handleRadioNotAvailable() {
@@ -1283,7 +1280,7 @@
     //***** Called from GsmCdmaCall
 
     public void hangup(GsmCdmaCall call) throws CallStateException {
-        if (call.getConnections().size() == 0) {
+        if (call.getConnectionsCount() == 0) {
             throw new CallStateException("no connections in call");
         }
 
@@ -1325,18 +1322,20 @@
     }
 
     private void logHangupEvent(GsmCdmaCall call) {
-        int count = call.mConnections.size();
-        for (int i = 0; i < count; i++) {
-            GsmCdmaConnection cn = (GsmCdmaConnection) call.mConnections.get(i);
+        for (Connection conn : call.getConnections()) {
+            GsmCdmaConnection c = (GsmCdmaConnection) conn;
             int call_index;
             try {
-                call_index = cn.getGsmCdmaIndex();
-            } catch (CallStateException ex) {
+                call_index = c.getGsmCdmaIndex();
+            } catch (CallStateException e) {
                 call_index = -1;
             }
-            mMetrics.writeRilHangup(mPhone.getPhoneId(), cn, call_index, getNetworkCountryIso());
+            mMetrics.writeRilHangup(mPhone.getPhoneId(), c, call_index, getNetworkCountryIso());
         }
-        if (VDBG) Rlog.v(LOG_TAG, "logHangupEvent logged " + count + " Connections ");
+        if (VDBG) {
+            Rlog.v(LOG_TAG, "logHangupEvent logged " + call.getConnectionsCount()
+                    + " Connections ");
+        }
     }
 
     public void hangupWaitingOrBackground() {
@@ -1352,29 +1351,26 @@
 
     public void hangupConnectionByIndex(GsmCdmaCall call, int index)
             throws CallStateException {
-        int count = call.mConnections.size();
-        for (int i = 0; i < count; i++) {
-            GsmCdmaConnection cn = (GsmCdmaConnection)call.mConnections.get(i);
-            if (!cn.mDisconnected && cn.getGsmCdmaIndex() == index) {
-                mMetrics.writeRilHangup(mPhone.getPhoneId(), cn, cn.getGsmCdmaIndex(),
+        for (Connection conn : call.getConnections()) {
+            GsmCdmaConnection c = (GsmCdmaConnection) conn;
+            if (!c.mDisconnected && c.getGsmCdmaIndex() == index) {
+                mMetrics.writeRilHangup(mPhone.getPhoneId(), c, c.getGsmCdmaIndex(),
                         getNetworkCountryIso());
                 mCi.hangupConnection(index, obtainCompleteMessage());
                 return;
             }
         }
-
         throw new CallStateException("no GsmCdma index found");
     }
 
     public void hangupAllConnections(GsmCdmaCall call) {
         try {
-            int count = call.mConnections.size();
-            for (int i = 0; i < count; i++) {
-                GsmCdmaConnection cn = (GsmCdmaConnection)call.mConnections.get(i);
-                if (!cn.mDisconnected) {
-                    mMetrics.writeRilHangup(mPhone.getPhoneId(), cn, cn.getGsmCdmaIndex(),
+            for (Connection conn : call.getConnections()) {
+                GsmCdmaConnection c = (GsmCdmaConnection) conn;
+                if (!c.mDisconnected) {
+                    mMetrics.writeRilHangup(mPhone.getPhoneId(), c, c.getGsmCdmaIndex(),
                             getNetworkCountryIso());
-                    mCi.hangupConnection(cn.getGsmCdmaIndex(), obtainCompleteMessage());
+                    mCi.hangupConnection(c.getGsmCdmaIndex(), obtainCompleteMessage());
                 }
             }
         } catch (CallStateException ex) {
@@ -1384,14 +1380,12 @@
 
     public GsmCdmaConnection getConnectionByIndex(GsmCdmaCall call, int index)
             throws CallStateException {
-        int count = call.mConnections.size();
-        for (int i = 0; i < count; i++) {
-            GsmCdmaConnection cn = (GsmCdmaConnection)call.mConnections.get(i);
-            if (!cn.mDisconnected && cn.getGsmCdmaIndex() == index) {
-                return cn;
+        for (Connection conn : call.getConnections()) {
+            GsmCdmaConnection c = (GsmCdmaConnection) conn;
+            if (!c.mDisconnected && c.getGsmCdmaIndex() == index) {
+                return c;
             }
         }
-
         return null;
     }
 
@@ -1468,6 +1462,20 @@
                 if (isPhoneTypeGsm()) {
                     ar = (AsyncResult) msg.obj;
                     if (ar.exception != null) {
+                        if (msg.what == EVENT_SWITCH_RESULT) {
+                            Connection connection = mForegroundCall.getLatestConnection();
+                            if (connection != null) {
+                                if (mBackgroundCall.getState() != GsmCdmaCall.State.HOLDING) {
+                                    connection.onConnectionEvent(
+                                            android.telecom.Connection.EVENT_CALL_HOLD_FAILED,
+                                            null);
+                                } else {
+                                    connection.onConnectionEvent(
+                                            android.telecom.Connection.EVENT_CALL_SWITCH_FAILED,
+                                            null);
+                                }
+                            }
+                        }
                         mPhone.notifySuppServiceFailed(getFailedService(msg.what));
                     }
                     operationComplete();
@@ -1534,7 +1542,7 @@
                     causeCode == CallFailCause.BEARER_NOT_AVAIL ||
                     causeCode == CallFailCause.ERROR_UNSPECIFIED) {
 
-                    CellLocation loc = mPhone.getCellLocation();
+                    CellLocation loc = mPhone.getCellIdentity().asCellLocation();
                     int cid = -1;
                     if (loc != null) {
                         if (loc instanceof GsmCellLocation) {
@@ -1547,6 +1555,22 @@
                             TelephonyManager.getDefault().getNetworkType());
                 }
 
+                if (isEmcRetryCause(causeCode)) {
+                    String dialString = "";
+                    for(Connection conn : mForegroundCall.mConnections) {
+                        GsmCdmaConnection gsmCdmaConnection = (GsmCdmaConnection)conn;
+                        dialString = gsmCdmaConnection.getOrigDialString();
+                        gsmCdmaConnection.getCall().detach(gsmCdmaConnection);
+                        mDroppedDuringPoll.remove(gsmCdmaConnection);
+                    }
+                    mPhone.notifyVolteSilentRedial(dialString, causeCode);
+                    updatePhoneState();
+                    if (mDroppedDuringPoll.isEmpty()) {
+                        log("LAST_CALL_FAIL_CAUSE - no Dropped normal Call");
+                        return;
+                    }
+                }
+
                 for (int i = 0, s = mDroppedDuringPoll.size(); i < s ; i++) {
                     GsmCdmaConnection conn = mDroppedDuringPoll.get(i);
 
@@ -1558,6 +1582,7 @@
                 mPhone.notifyPreciseCallStateChanged();
                 mMetrics.writeRilCallList(mPhone.getPhoneId(), mDroppedDuringPoll,
                         getNetworkCountryIso());
+                mPhone.getVoiceCallSessionStats().onRilCallListChanged(mDroppedDuringPoll);
                 mDroppedDuringPoll.clear();
             break;
 
@@ -1654,7 +1679,7 @@
      * @param vrat the RIL voice radio technology for CS calls,
      *             see {@code RIL_RADIO_TECHNOLOGY_*} in {@link android.telephony.ServiceState}.
      */
-    public void dispatchCsCallRadioTech(@ServiceState.RilRadioTechnology int vrat) {
+    public void dispatchCsCallRadioTech(@RilRadioTechnology int vrat) {
         if (mConnections == null) {
             log("dispatchCsCallRadioTech: mConnections is null");
             return;
@@ -1753,6 +1778,14 @@
         return mPhone;
     }
 
+    private boolean isEmcRetryCause(int causeCode) {
+        if (causeCode == CallFailCause.EMC_REDIAL_ON_IMS ||
+            causeCode == CallFailCause.EMC_REDIAL_ON_VOWIFI) {
+            return true;
+        }
+        return false;
+    }
+
     @UnsupportedAppUsage
     @Override
     protected void log(String msg) {
@@ -1788,7 +1821,6 @@
             pw.println(" mPendingCallInEcm=" + mPendingCallInEcm);
             pw.println(" mIsInEmergencyCall=" + mIsInEmergencyCall);
             pw.println(" mPendingCallClirMode=" + mPendingCallClirMode);
-            pw.println(" mIsEcmTimerCanceled=" + mIsEcmTimerCanceled);
         }
 
     }
diff --git a/src/java/com/android/internal/telephony/GsmCdmaConnection.java b/src/java/com/android/internal/telephony/GsmCdmaConnection.java
index b3dea8e..282d974 100644
--- a/src/java/com/android/internal/telephony/GsmCdmaConnection.java
+++ b/src/java/com/android/internal/telephony/GsmCdmaConnection.java
@@ -15,7 +15,7 @@
  */
 
 package com.android.internal.telephony;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
 import android.content.Context;
 import android.os.AsyncResult;
 import android.os.Handler;
@@ -28,15 +28,16 @@
 import android.telephony.CarrierConfigManager;
 import android.telephony.DisconnectCause;
 import android.telephony.PhoneNumberUtils;
-import android.telephony.Rlog;
 import android.telephony.ServiceState;
 import android.text.TextUtils;
 
 import com.android.internal.telephony.cdma.CdmaCallWaitingNotification;
 import com.android.internal.telephony.cdma.CdmaSubscriptionSourceManager;
+import com.android.internal.telephony.emergency.EmergencyNumberTracker;
 import com.android.internal.telephony.metrics.TelephonyMetrics;
 import com.android.internal.telephony.uicc.IccCardApplicationStatus.AppState;
 import com.android.internal.telephony.uicc.UiccCardApplication;
+import com.android.telephony.Rlog;
 
 /**
  * {@hide}
@@ -79,9 +80,6 @@
     // The cached delay to be used between DTMF tones fetched from carrier config.
     private int mDtmfToneDelay = 0;
 
-    // Store the current audio codec
-    private int mAudioCodec = DriverCall.AUDIO_QUALITY_UNSPECIFIED;
-
     private TelephonyMetrics mMetrics = TelephonyMetrics.getInstance();
 
     //***** Event Constants
@@ -94,7 +92,7 @@
     //***** Constants
     static final int PAUSE_DELAY_MILLIS_GSM = 3 * 1000;
     static final int PAUSE_DELAY_MILLIS_CDMA = 2 * 1000;
-    static final int WAKE_LOCK_TIMEOUT_MILLIS = 60*1000;
+    static final int WAKE_LOCK_TIMEOUT_MILLIS = 60 * 1000;
 
     //***** Inner Classes
 
@@ -383,6 +381,18 @@
     }
 
     @Override
+    public void transfer(String number, boolean isConfirmationRequired) throws CallStateException {
+        // Transfer is not supported.
+        throw new CallStateException("Transfer is not supported for CS");
+    }
+
+    @Override
+    public void consultativeTransfer(Connection other) throws CallStateException {
+        // Transfer is not supported.
+        throw new CallStateException("Transfer is not supported for CS");
+    }
+
+    @Override
     public void separate() throws CallStateException {
         if (!mDisconnected) {
             mOwner.separate(this);
@@ -681,6 +691,7 @@
         if (dc.audioQuality != mAudioCodec) {
             mAudioCodec = dc.audioQuality;
             mMetrics.writeAudioCodecGsmCdma(mOwner.getPhone().getPhoneId(), dc.audioQuality);
+            mOwner.getPhone().getVoiceCallSessionStats().onAudioCodecChanged(this, dc.audioQuality);
         }
 
         // A null cnapName should be the same as ""
@@ -1004,8 +1015,8 @@
      * @param s new PostDialState
      */
     private void setPostDialState(PostDialState s) {
-        if (s == PostDialState.STARTED ||
-                s == PostDialState.PAUSE) {
+        if (s == PostDialState.STARTED
+                || s == PostDialState.PAUSE) {
             synchronized (mPartialWakeLock) {
                 if (mPartialWakeLock.isHeld()) {
                     mHandler.removeMessages(EVENT_WAKE_LOCK_TIMEOUT);
@@ -1205,6 +1216,20 @@
     }
 
     /**
+     * Get the corresponding EmergencyNumberTracker associated with the connection.
+     * @return the EmergencyNumberTracker
+     */
+    public EmergencyNumberTracker getEmergencyNumberTracker() {
+        if (mOwner != null) {
+            Phone phone = mOwner.getPhone();
+            if (phone != null) {
+                return phone.getEmergencyNumberTracker();
+            }
+        }
+        return null;
+    }
+
+    /**
      * @return {@code true} if this call is an OTASP activation call, {@code false} otherwise.
      */
     public boolean isOtaspCall() {
diff --git a/src/java/com/android/internal/telephony/GsmCdmaPhone.java b/src/java/com/android/internal/telephony/GsmCdmaPhone.java
index 46bcb21..771e542 100644
--- a/src/java/com/android/internal/telephony/GsmCdmaPhone.java
+++ b/src/java/com/android/internal/telephony/GsmCdmaPhone.java
@@ -16,6 +16,8 @@
 
 package com.android.internal.telephony;
 
+import static com.android.internal.telephony.CommandException.Error.GENERIC_FAILURE;
+import static com.android.internal.telephony.CommandException.Error.SIM_BUSY;
 import static com.android.internal.telephony.CommandsInterface.CF_ACTION_DISABLE;
 import static com.android.internal.telephony.CommandsInterface.CF_ACTION_ENABLE;
 import static com.android.internal.telephony.CommandsInterface.CF_ACTION_ERASURE;
@@ -30,8 +32,7 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
-import android.annotation.UnsupportedAppUsage;
-import android.app.ActivityManager;
+import android.compat.annotation.UnsupportedAppUsage;
 import android.content.BroadcastReceiver;
 import android.content.ContentValues;
 import android.content.Context;
@@ -55,16 +56,21 @@
 import android.preference.PreferenceManager;
 import android.provider.Settings;
 import android.provider.Telephony;
+import android.sysprop.TelephonyProperties;
 import android.telecom.TelecomManager;
 import android.telecom.VideoProfile;
 import android.telephony.AccessNetworkConstants;
+import android.telephony.BarringInfo;
 import android.telephony.CarrierConfigManager;
-import android.telephony.CellLocation;
+import android.telephony.CellIdentity;
+import android.telephony.DataFailCause;
 import android.telephony.ImsiEncryptionInfo;
 import android.telephony.NetworkScanRequest;
 import android.telephony.PhoneNumberUtils;
-import android.telephony.Rlog;
+import android.telephony.PreciseDataConnectionState;
 import android.telephony.ServiceState;
+import android.telephony.ServiceState.RilRadioTechnology;
+import android.telephony.SignalThresholdInfo;
 import android.telephony.SubscriptionInfo;
 import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyManager;
@@ -84,10 +90,14 @@
 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.imsphone.ImsPhoneMmiCode;
+import com.android.internal.telephony.metrics.VoiceCallSessionStats;
 import com.android.internal.telephony.test.SimulatedRadioControl;
 import com.android.internal.telephony.uicc.IccCardApplicationStatus.AppType;
+import com.android.internal.telephony.uicc.IccCardStatus;
 import com.android.internal.telephony.uicc.IccException;
 import com.android.internal.telephony.uicc.IccRecords;
+import com.android.internal.telephony.uicc.IccUtils;
 import com.android.internal.telephony.uicc.IccVmNotSupportedException;
 import com.android.internal.telephony.uicc.IsimRecords;
 import com.android.internal.telephony.uicc.IsimUiccRecords;
@@ -98,7 +108,8 @@
 import com.android.internal.telephony.uicc.UiccController;
 import com.android.internal.telephony.uicc.UiccProfile;
 import com.android.internal.telephony.uicc.UiccSlot;
-import com.android.internal.util.ArrayUtils;
+import com.android.internal.telephony.util.ArrayUtils;
+import com.android.telephony.Rlog;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
@@ -135,7 +146,7 @@
 
     //CDMA
     // Default Emergency Callback Mode exit timer
-    private static final int DEFAULT_ECM_EXIT_TIMER_VALUE = 300000;
+    private static final long DEFAULT_ECM_EXIT_TIMER_VALUE = 300000;
     private static final String VM_NUMBER_CDMA = "vm_number_key_cdma";
     public static final int RESTART_ECM_TIMER = 0; // restart Ecm timer
     public static final int CANCEL_ECM_TIMER = 1; // cancel Ecm timer
@@ -150,6 +161,13 @@
     private String mMeid;
     // string to define how the carrier specifies its own ota sp number
     private String mCarrierOtaSpNumSchema;
+    private Boolean mUiccApplicationsEnabled = null;
+    // keeps track of when we have triggered an emergency call due to the ril.test.emergencynumber
+    // param being set and we should generate a simulated exit from the modem upon exit of ECbM.
+    private boolean mIsTestingEmergencyCallbackMode = false;
+    @VisibleForTesting
+    public static int ENABLE_UICC_APPS_MAX_RETRIES = 3;
+    private static final int REAPPLY_UICC_APPS_SETTING_RETRY_TIME_GAP_IN_MS = 5000;
 
     // A runnable which is used to automatically exit from Ecm after a period of time.
     private Runnable mExitEcmRunnable = new Runnable() {
@@ -167,6 +185,9 @@
      */
     private SIMRecords mSimRecords;
 
+    // For non-persisted manual network selection
+    private String mManualNetworkSelectionPlmn = "";
+
     //Common
     // Instance Variables
     @UnsupportedAppUsage
@@ -185,6 +206,9 @@
     // mEcmTimerResetRegistrants are informed after Ecm timer is canceled or re-started
     private final RegistrantList mEcmTimerResetRegistrants = new RegistrantList();
 
+    private final RegistrantList mVolteSilentRedialRegistrants = new RegistrantList();
+    private DialArgs mDialArgs = null;
+
     private String mImei;
     private String mImeiSv;
     private String mVmNumber;
@@ -230,6 +254,7 @@
 
         // phone type needs to be set before other initialization as other objects rely on it
         mPrecisePhoneType = precisePhoneType;
+        mVoiceCallSessionStats = new VoiceCallSessionStats(mPhoneId, this);
         initOnce(ci);
         initRatSpecific(precisePhoneType);
         // CarrierSignalAgent uses CarrierActionAgent in construction so it needs to be created
@@ -247,8 +272,16 @@
                 this, this.mCi);
         mDataEnabledSettings = mTelephonyComponentFactory
                 .inject(DataEnabledSettings.class.getName()).makeDataEnabledSettings(this);
+        mDeviceStateMonitor = mTelephonyComponentFactory.inject(DeviceStateMonitor.class.getName())
+                .makeDeviceStateMonitor(this);
 
-        // DcTracker uses SST so needs to be created after it is instantiated
+        // DisplayInfoController creates an OverrideNetworkTypeController, which uses
+        // DeviceStateMonitor so needs to be crated after it is instantiated.
+        mDisplayInfoController = mTelephonyComponentFactory.inject(
+                DisplayInfoController.class.getName()).makeDisplayInfoController(this);
+
+        // DcTracker uses ServiceStateTracker and DisplayInfoController so needs to be created
+        // after they are instantiated
         for (int transport : mTransportManager.getAvailableTransports()) {
             mDcTrackers.put(transport, mTelephonyComponentFactory.inject(DcTracker.class.getName())
                     .makeDcTracker(this, transport));
@@ -262,9 +295,6 @@
                 EVENT_SET_CARRIER_DATA_ENABLED, null, false);
 
         mSST.registerForNetworkAttached(this, EVENT_REGISTERED_TO_NETWORK, null);
-        mDeviceStateMonitor = mTelephonyComponentFactory.inject(DeviceStateMonitor.class.getName())
-                .makeDeviceStateMonitor(this);
-
         mSST.registerForVoiceRegStateOrRatChanged(this, EVENT_VRS_OR_RAT_CHANGED, null);
 
         mSettingsObserver = new SettingsObserver(context, this);
@@ -275,6 +305,9 @@
                 Settings.Global.getUriFor(Settings.Global.DEVICE_PROVISIONING_MOBILE_DATA_ENABLED),
                 EVENT_DEVICE_PROVISIONING_DATA_SETTING_CHANGE);
 
+        SubscriptionController.getInstance().registerForUiccAppsEnabled(this,
+                EVENT_UICC_APPS_ENABLEMENT_SETTING_CHANGED, null, false);
+
         loadTtyMode();
         logd("GsmCdmaPhone: constructor: sub = " + mPhoneId);
     }
@@ -322,7 +355,12 @@
         mCi.registerForOffOrNotAvailable(this, EVENT_RADIO_OFF_OR_NOT_AVAILABLE, null);
         mCi.registerForOn(this, EVENT_RADIO_ON, null);
         mCi.registerForRadioStateChanged(this, EVENT_RADIO_STATE_CHANGED, null);
+        mCi.registerUiccApplicationEnablementChanged(this,
+                EVENT_UICC_APPS_ENABLEMENT_STATUS_CHANGED,
+                null);
         mCi.setOnSuppServiceNotification(this, EVENT_SSN, null);
+        mCi.setOnRegistrationFailed(this, EVENT_REGISTRATION_FAILED, null);
+        mCi.registerForBarringInfoChanged(this, EVENT_BARRING_INFO_CHANGED, null);
 
         //GSM
         mCi.setOnUSSD(this, EVENT_USSD, null);
@@ -340,8 +378,8 @@
         mCarrierOtaSpNumSchema = TelephonyManager.from(mContext).getOtaSpNumberSchemaForPhone(
                 getPhoneId(), "");
 
-        mResetModemOnRadioTechnologyChange = SystemProperties.getBoolean(
-                TelephonyProperties.PROPERTY_RESET_ON_RADIO_TECH_CHANGE, false);
+        mResetModemOnRadioTechnologyChange = TelephonyProperties.reset_on_radio_tech_change()
+                .orElse(false);
 
         mCi.registerForRilConnected(this, EVENT_RIL_CONNECTED, null);
         mCi.registerForVoiceRadioTechChanged(this, EVENT_VOICE_RADIO_TECH_CHANGED, null);
@@ -378,8 +416,7 @@
             mIsPhoneInEcmState = getInEcmMode();
             if (mIsPhoneInEcmState) {
                 // Send a message which will invoke handleExitEmergencyCallbackMode
-                mCi.exitEmergencyCallbackMode(
-                        obtainMessage(EVENT_EXIT_EMERGENCY_CALLBACK_RESPONSE));
+                mCi.exitEmergencyCallbackMode(null);
             }
 
             mCi.setPhoneType(PhoneConstants.PHONE_TYPE_CDMA);
@@ -495,6 +532,7 @@
 
     @UnsupportedAppUsage
     @Override
+    @NonNull
     public ServiceState getServiceState() {
         if (mSST == null || mSST.mSS.getState() != ServiceState.STATE_IN_SERVICE) {
             if (mImsPhone != null) {
@@ -512,8 +550,8 @@
     }
 
     @Override
-    public void getCellLocation(WorkSource workSource, Message rspMsg) {
-        mSST.requestCellLocation(workSource, rspMsg);
+    public void getCellIdentity(WorkSource workSource, Message rspMsg) {
+        mSST.requestCellIdentity(workSource, rspMsg);
     }
 
     @UnsupportedAppUsage
@@ -561,6 +599,16 @@
     }
 
     @Override
+    public DeviceStateMonitor getDeviceStateMonitor() {
+        return mDeviceStateMonitor;
+    }
+
+    @Override
+    public DisplayInfoController getDisplayInfoController() {
+        return mDisplayInfoController;
+    }
+
+    @Override
     public void updateVoiceMail() {
         if (isPhoneTypeGsm()) {
             int countVoiceMessages = 0;
@@ -586,12 +634,53 @@
         return mPendingMMIs;
     }
 
+    private @NonNull DcTracker getActiveDcTrackerForApn(@NonNull String apnType) {
+        int currentTransport = mTransportManager.getCurrentTransport(
+                ApnSetting.getApnTypesBitmaskFromString(apnType));
+        return getDcTracker(currentTransport);
+    }
+
+    @Override
+    public PreciseDataConnectionState getPreciseDataConnectionState(String apnType) {
+        // If we are OOS, then all data connections are null.
+        // FIXME: we need to figure out how to report the EIMS PDN connectivity here, which
+        // should imply emergency attach - today emergency attach is unknown at the AP,
+        // so, we take a guess.
+        boolean isEmergencyData = isPhoneTypeGsm()
+                && apnType.equals(PhoneConstants.APN_TYPE_EMERGENCY);
+
+        if (mSST == null
+                || ((mSST.getCurrentDataConnectionState() != ServiceState.STATE_IN_SERVICE)
+                        && !isEmergencyData)) {
+            return new PreciseDataConnectionState(TelephonyManager.DATA_DISCONNECTED,
+                    TelephonyManager.NETWORK_TYPE_UNKNOWN,
+                    ApnSetting.getApnTypesBitmaskFromString(apnType),
+                    apnType, null, DataFailCause.NONE, null);
+        }
+
+        // must never be null
+        final DcTracker dctForApn = getActiveDcTrackerForApn(apnType);
+
+        int networkType = TelephonyManager.NETWORK_TYPE_UNKNOWN;
+        // Always non-null
+        ServiceState ss = getServiceState();
+        if (ss != null) {
+            networkType = ss.getDataNetworkType();
+        }
+
+        return dctForApn.getPreciseDataConnectionState(apnType, isDataSuspended(), networkType);
+    }
+
+    boolean isDataSuspended() {
+        return mCT.mState != PhoneConstants.State.IDLE && !mSST.isConcurrentVoiceAndDataAllowed();
+    }
+
     @Override
     public PhoneConstants.DataState getDataConnectionState(String apnType) {
         PhoneConstants.DataState ret = PhoneConstants.DataState.DISCONNECTED;
 
         if (mSST == null) {
-            // Radio Technology Change is ongoning, dispose() and removeReferences() have
+            // Radio Technology Change is ongoing, dispose() and removeReferences() have
             // already been called
 
             ret = PhoneConstants.DataState.DISCONNECTED;
@@ -612,8 +701,7 @@
                 switch (getDcTracker(currentTransport).getState(apnType)) {
                     case CONNECTED:
                     case DISCONNECTING:
-                        if (mCT.mState != PhoneConstants.State.IDLE
-                                && !mSST.isConcurrentVoiceAndDataAllowed()) {
+                        if (isDataSuspended()) {
                             ret = PhoneConstants.DataState.SUSPENDED;
                         } else {
                             ret = PhoneConstants.DataState.CONNECTED;
@@ -724,9 +812,9 @@
     private void sendEmergencyCallbackModeChange(){
         //Send an Intent
         Intent intent = new Intent(TelephonyIntents.ACTION_EMERGENCY_CALLBACK_MODE_CHANGED);
-        intent.putExtra(PhoneConstants.PHONE_IN_ECM_STATE, isInEcm());
+        intent.putExtra(TelephonyManager.EXTRA_PHONE_IN_ECM_STATE, isInEcm());
         SubscriptionManager.putPhoneIdAndSubIdExtra(intent, getPhoneId());
-        ActivityManager.broadcastStickyIntent(intent, UserHandle.USER_ALL);
+        mContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL);
         logi("sendEmergencyCallbackModeChange");
     }
 
@@ -734,14 +822,14 @@
     public void sendEmergencyCallStateChange(boolean callActive) {
         if (!isPhoneTypeCdma()) {
             // It possible that this method got called from ImsPhoneCallTracker#
-            logi("sendEmergencyCallbackModeChange - skip for non-cdma");
+            logi("sendEmergencyCallStateChange - skip for non-cdma");
             return;
         }
         if (mBroadcastEmergencyCallStateChanges) {
             Intent intent = new Intent(TelephonyIntents.ACTION_EMERGENCY_CALL_STATE_CHANGED);
-            intent.putExtra(PhoneConstants.PHONE_IN_EMERGENCY_CALL, callActive);
+            intent.putExtra(TelephonyManager.EXTRA_PHONE_IN_EMERGENCY_CALL, callActive);
             SubscriptionManager.putPhoneIdAndSubIdExtra(intent, getPhoneId());
-            ActivityManager.broadcastStickyIntent(intent, UserHandle.USER_ALL);
+            mContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL);
             if (DBG) Rlog.d(LOG_TAG, "sendEmergencyCallStateChange: callActive " + callActive);
         }
     }
@@ -761,12 +849,12 @@
     }
 
     /**
-     * Notify that the CellLocation has changed.
+     * Notify that the cell location has changed.
      *
-     * @param cl the new CellLocation
+     * @param cellIdentity the new CellIdentity
      */
-    public void notifyLocationChanged(CellLocation cl) {
-        mNotifier.notifyCellLocation(this, cl);
+    public void notifyLocationChanged(CellIdentity cellIdentity) {
+        mNotifier.notifyCellLocation(this, cellIdentity);
     }
 
     @Override
@@ -778,13 +866,11 @@
     public void registerForSuppServiceNotification(
             Handler h, int what, Object obj) {
         mSsnRegistrants.addUnique(h, what, obj);
-        if (mSsnRegistrants.size() == 1) mCi.setSuppServiceNotifications(true, null);
     }
 
     @Override
     public void unregisterForSuppServiceNotification(Handler h) {
         mSsnRegistrants.remove(h);
-        if (mSsnRegistrants.size() == 0) mCi.setSuppServiceNotifications(false, null);
     }
 
     @Override
@@ -870,6 +956,14 @@
     }
 
     @Override
+    public void dispose() {
+        // Note: this API is currently never called. We are defining actions here in case
+        // we need to dispose GsmCdmaPhone/Phone object.
+        super.dispose();
+        SubscriptionController.getInstance().unregisterForUiccAppsEnabled(this);
+    }
+
+    @Override
     public void enableEnhancedVoicePrivacy(boolean enable, Message onComplete) {
         if (isPhoneTypeGsm()) {
             loge("enableEnhancedVoicePrivacy: not expected on GSM");
@@ -934,33 +1028,40 @@
         if ( imsPhone != null && imsPhone.getRingingCall().isRinging()) {
             return imsPhone.getRingingCall();
         }
+        //It returns the ringing connections which during SRVCC handover
+        if (!mCT.mRingingCall.isRinging()
+                && mCT.getRingingHandoverConnection() != null
+                && mCT.getRingingHandoverConnection().getCall() != null
+                && mCT.getRingingHandoverConnection().getCall().isRinging()) {
+            return mCT.getRingingHandoverConnection().getCall();
+        }
         return mCT.mRingingCall;
     }
 
     /**
      * ImsService reports "IN_SERVICE" for its voice registration state even if the device
      * has lost the physical link to the tower. This helper method merges the IMS and modem
-     * ServiceState, only overriding the voice registration state when we are registered to IMS over
-     * IWLAN. In this case the voice registration state will always be "OUT_OF_SERVICE", so override
-     * the voice registration state with the data registration state.
+     * ServiceState, only overriding the voice registration state when we are registered to IMS. In
+     * this case the voice registration state may be "OUT_OF_SERVICE", so override the voice
+     * registration state with the data registration state.
      */
     private ServiceState mergeServiceStates(ServiceState baseSs, ServiceState imsSs) {
+        // No need to merge states if the baseSs is IN_SERVICE.
+        if (baseSs.getState() == ServiceState.STATE_IN_SERVICE) {
+            return baseSs;
+        }
         // "IN_SERVICE" in this case means IMS is registered.
-        if (imsSs.getVoiceRegState() != ServiceState.STATE_IN_SERVICE) {
+        if (imsSs.getState() != ServiceState.STATE_IN_SERVICE) {
             return baseSs;
         }
 
-        if (imsSs.getRilDataRadioTechnology() == ServiceState.RIL_RADIO_TECHNOLOGY_IWLAN) {
-            ServiceState newSs = new ServiceState(baseSs);
-            // Voice override for IWLAN. In this case, voice registration is OUT_OF_SERVICE, but
-            // the data RAT is IWLAN, so use that as a basis for determining whether or not the
-            // physical link is available.
-            newSs.setVoiceRegState(baseSs.getDataRegState());
-            newSs.setEmergencyOnly(false); // only get here if voice is IN_SERVICE
-            return newSs;
-        }
-
-        return baseSs;
+        ServiceState newSs = new ServiceState(baseSs);
+        // Voice override for IMS case. In this case, voice registration is OUT_OF_SERVICE, but
+        // IMS is available, so use data registration state as a basis for determining
+        // whether or not the physical link is available.
+        newSs.setVoiceRegState(baseSs.getDataRegistrationState());
+        newSs.setEmergencyOnly(false); // only get here if voice is IN_SERVICE
+        return newSs;
     }
 
     private boolean handleCallDeflectionIncallSupplementaryService(
@@ -1164,15 +1265,55 @@
                 ringingCallState.isAlive());
     }
 
+    private boolean useImsForCall(DialArgs dialArgs) {
+        return isImsUseEnabled()
+                && mImsPhone != null
+                && (mImsPhone.isVolteEnabled() || mImsPhone.isWifiCallingEnabled() ||
+                (mImsPhone.isVideoEnabled() && VideoProfile.isVideo(dialArgs.videoState)))
+                && (mImsPhone.getServiceState().getState() == ServiceState.STATE_IN_SERVICE);
+    }
+
+    @Override
+    public Connection startConference(String[] participantsToDial, DialArgs dialArgs)
+            throws CallStateException {
+        Phone imsPhone = mImsPhone;
+        boolean useImsForCall = useImsForCall(dialArgs);
+        logd("useImsForCall=" + useImsForCall);
+        if (useImsForCall) {
+            try {
+                if (DBG) logd("Trying IMS PS Conference call");
+                return imsPhone.startConference(participantsToDial, dialArgs);
+            } catch (CallStateException e) {
+                if (DBG) logd("IMS PS conference call exception " + e +
+                        "useImsForCall =" + useImsForCall + ", imsPhone =" + imsPhone);
+                 CallStateException ce = new CallStateException(e.getError(), e.getMessage());
+                 ce.setStackTrace(e.getStackTrace());
+                 throw ce;
+            }
+        } else {
+            throw new CallStateException(
+                CallStateException.ERROR_OUT_OF_SERVICE,
+                "cannot dial conference call in out of service");
+        }
+    }
+
     @Override
     public Connection dial(String dialString, @NonNull DialArgs dialArgs)
             throws CallStateException {
         if (!isPhoneTypeGsm() && dialArgs.uusInfo != null) {
             throw new CallStateException("Sending UUS information NOT supported in CDMA!");
         }
-
+        String possibleEmergencyNumber = checkForTestEmergencyNumber(dialString);
+        // Record if the dialed number was swapped for a test emergency number.
+        boolean isDialedNumberSwapped = !TextUtils.equals(dialString, possibleEmergencyNumber);
+        if (isDialedNumberSwapped) {
+            logi("dialString replaced for possible emergency number: " + dialString + " -> "
+                    + possibleEmergencyNumber);
+            dialString = possibleEmergencyNumber;
+        }
         boolean isEmergency = PhoneNumberUtils.isEmergencyNumber(getSubId(), dialString);
         Phone imsPhone = mImsPhone;
+        mDialArgs = dialArgs;
 
         CarrierConfigManager configManager =
                 (CarrierConfigManager) mContext.getSystemService(Context.CARRIER_CONFIG_SERVICE);
@@ -1184,13 +1325,6 @@
         boolean allowWpsOverIms = configManager.getConfigForSubId(getSubId())
                 .getBoolean(CarrierConfigManager.KEY_SUPPORT_WPS_OVER_IMS_BOOL);
 
-        boolean useImsForCall = isImsUseEnabled()
-                 && imsPhone != null
-                 && (imsPhone.isVolteEnabled() || imsPhone.isWifiCallingEnabled() ||
-                 (imsPhone.isVideoEnabled() && VideoProfile.isVideo(dialArgs.videoState)))
-                 && (imsPhone.getServiceState().getState() == ServiceState.STATE_IN_SERVICE)
-                 && (isWpsCall ? allowWpsOverIms : true);
-
         boolean useImsForEmergency = imsPhone != null
                 && isEmergency
                 && alwaysTryImsForEmergencyCarrierConfig
@@ -1199,16 +1333,22 @@
 
         String dialPart = PhoneNumberUtils.extractNetworkPortionAlt(PhoneNumberUtils.
                 stripSeparators(dialString));
-        boolean isUt = (dialPart.startsWith("*") || dialPart.startsWith("#"))
+        boolean isMmiCode = (dialPart.startsWith("*") || dialPart.startsWith("#"))
                 && dialPart.endsWith("#");
-
+        boolean isSuppServiceCode = ImsPhoneMmiCode.isSuppServiceCodes(dialPart, this);
+        boolean isPotentialUssdCode = isMmiCode && !isSuppServiceCode;
         boolean useImsForUt = imsPhone != null && imsPhone.isUtEnabled();
+        boolean useImsForCall = useImsForCall(dialArgs)
+                && (isWpsCall ? allowWpsOverIms : true);
 
         if (DBG) {
             logd("useImsForCall=" + useImsForCall
+                    + ", isEmergency=" + isEmergency
                     + ", useImsForEmergency=" + useImsForEmergency
                     + ", useImsForUt=" + useImsForUt
-                    + ", isUt=" + isUt
+                    + ", isUt=" + isMmiCode
+                    + ", isSuppServiceCode=" + isSuppServiceCode
+                    + ", isPotentialUssdCode=" + isPotentialUssdCode
                     + ", isWpsCall=" + isWpsCall
                     + ", allowWpsOverIms=" + allowWpsOverIms
                     + ", imsPhone=" + imsPhone
@@ -1224,7 +1364,9 @@
 
         Phone.checkWfcWifiOnlyModeBeforeDial(mImsPhone, mPhoneId, mContext);
 
-        if ((useImsForCall && !isUt) || (isUt && useImsForUt) || useImsForEmergency) {
+        if ((useImsForCall && (!isMmiCode || isPotentialUssdCode))
+                || (isMmiCode && useImsForUt)
+                || useImsForEmergency) {
             try {
                 if (DBG) logd("Trying IMS PS call");
                 return imsPhone.dial(dialString, dialArgs);
@@ -1245,32 +1387,43 @@
         }
 
         if (mSST != null && mSST.mSS.getState() == ServiceState.STATE_OUT_OF_SERVICE
-                && mSST.mSS.getDataRegState() != ServiceState.STATE_IN_SERVICE && !isEmergency) {
+                && mSST.mSS.getDataRegistrationState() != ServiceState.STATE_IN_SERVICE
+                && !isEmergency) {
             throw new CallStateException("cannot dial in current state");
         }
         // Check non-emergency voice CS call - shouldn't dial when POWER_OFF
         if (mSST != null && mSST.mSS.getState() == ServiceState.STATE_POWER_OFF /* CS POWER_OFF */
                 && !VideoProfile.isVideo(dialArgs.videoState) /* voice call */
                 && !isEmergency /* non-emergency call */
-                && !(isUt && useImsForUt) /* not UT */) {
+                && !(isMmiCode && useImsForUt) /* not UT */
+                /* If config_allow_ussd_over_ims is false, USSD is sent over the CS pipe instead */
+                && !isPotentialUssdCode) {
             throw new CallStateException(
                 CallStateException.ERROR_POWER_OFF,
                 "cannot dial voice call in airplane mode");
         }
         // Check for service before placing non emergency CS voice call.
-        // Allow dial only if either CS is camped on any RAT (or) PS is in LTE service.
+        // Allow dial only if either CS is camped on any RAT (or) PS is in LTE/NR service.
         if (mSST != null
                 && mSST.mSS.getState() == ServiceState.STATE_OUT_OF_SERVICE /* CS out of service */
-                && !(mSST.mSS.getDataRegState() == ServiceState.STATE_IN_SERVICE
-                    && ServiceState.isLte(mSST.mSS.getRilDataRadioTechnology())) /* PS not in LTE */
+                && !(mSST.mSS.getDataRegistrationState() == ServiceState.STATE_IN_SERVICE
+                && ServiceState.isPsOnlyTech(
+                        mSST.mSS.getRilDataRadioTechnology())) /* PS not in LTE/NR */
                 && !VideoProfile.isVideo(dialArgs.videoState) /* voice call */
-                && !isEmergency /* non-emergency call */) {
+                && !isEmergency /* non-emergency call */
+                /* If config_allow_ussd_over_ims is false, USSD is sent over the CS pipe instead */
+                && !isPotentialUssdCode) {
             throw new CallStateException(
                 CallStateException.ERROR_OUT_OF_SERVICE,
                 "cannot dial voice call in out of service");
         }
         if (DBG) logd("Trying (non-IMS) CS call");
-
+        if (isDialedNumberSwapped && isEmergency) {
+            // Triggers ECM when CS call ends only for test emergency calls using
+            // ril.test.emergencynumber.
+            mIsTestingEmergencyCallbackMode = true;
+            mCi.testingEmergencyCall();
+        }
         if (isPhoneTypeGsm()) {
             return dialInternal(dialString, new DialArgs.Builder<>()
                     .setIntentExtras(dialArgs.intentExtras)
@@ -1484,8 +1637,9 @@
     }
 
     @Override
-    public void setRadioPower(boolean power) {
-        mSST.setRadioPower(power);
+    public void setRadioPower(boolean power, boolean forEmergencyCall,
+            boolean isSelectedPhoneForEmergencyCall, boolean forceApply) {
+        mSST.setRadioPower(power, forEmergencyCall, isSelectedPhoneForEmergencyCall, forceApply);
     }
 
     private void storeVoiceMailNumber(String number) {
@@ -1534,10 +1688,20 @@
                         b.getString(CarrierConfigManager.KEY_DEFAULT_VM_NUMBER_STRING);
                 String defaultVmNumberRoaming =
                         b.getString(CarrierConfigManager.KEY_DEFAULT_VM_NUMBER_ROAMING_STRING);
-                if (!TextUtils.isEmpty(defaultVmNumberRoaming) && mSST.mSS.getRoaming()) {
-                    number = defaultVmNumberRoaming;
-                } else if (!TextUtils.isEmpty(defaultVmNumber)) {
-                    number = defaultVmNumber;
+                String defaultVmNumberRoamingAndImsUnregistered = b.getString(
+                        CarrierConfigManager
+                                .KEY_DEFAULT_VM_NUMBER_ROAMING_AND_IMS_UNREGISTERED_STRING);
+
+                if (!TextUtils.isEmpty(defaultVmNumber)) number = defaultVmNumber;
+                if (mSST.mSS.getRoaming()) {
+                    if (!TextUtils.isEmpty(defaultVmNumberRoamingAndImsUnregistered)
+                            && !mSST.isImsRegistered()) {
+                        // roaming and IMS unregistered case if CC configured
+                        number = defaultVmNumberRoamingAndImsUnregistered;
+                    } else if (!TextUtils.isEmpty(defaultVmNumberRoaming)) {
+                        // roaming default case if CC configured
+                        number = defaultVmNumberRoaming;
+                    }
                 }
             }
         }
@@ -1670,7 +1834,10 @@
 
     @Override
     public ImsiEncryptionInfo getCarrierInfoForImsiEncryption(int keyType) {
-        return CarrierInfoManager.getCarrierInfoForImsiEncryption(keyType, mContext);
+        String operatorNumeric = TelephonyManager.from(mContext)
+                .getSimOperatorNumericForPhone(mPhoneId);
+        return CarrierInfoManager.getCarrierInfoForImsiEncryption(keyType,
+                mContext, operatorNumeric);
     }
 
     @Override
@@ -1714,6 +1881,11 @@
     }
 
     @Override
+    public int getEmergencyNumberDbVersion() {
+        return getEmergencyNumberTracker().getEmergencyNumberDbVersion();
+    }
+
+    @Override
     public void resetCarrierKeysForImsiEncryption() {
         mCIM.resetCarrierKeysForImsiEncryption(mContext, mPhoneId);
     }
@@ -1793,6 +1965,29 @@
         }
     }
 
+    /**
+     * Update non-persisited manual network selection.
+     *
+     * @param nsm contains Plmn info
+     */
+    @Override
+    protected void updateManualNetworkSelection(NetworkSelectMessage nsm) {
+        int subId = getSubId();
+        if (SubscriptionManager.isValidSubscriptionId(subId)) {
+            mManualNetworkSelectionPlmn = nsm.operatorNumeric;
+        } else {
+        //on Phone0 in emergency mode (no SIM), or in some races then clear the cache
+            mManualNetworkSelectionPlmn = "";
+            Rlog.e(LOG_TAG, "Cannot update network selection due to invalid subId "
+                    + subId);
+        }
+    }
+
+    @Override
+    public String getManualNetworkSelectionPlmn() {
+        return (mManualNetworkSelectionPlmn == null) ? "" : mManualNetworkSelectionPlmn;
+    }
+
     @Override
     public String getCdmaPrlVersion() {
         return mSST.getPrlVersion();
@@ -1915,12 +2110,20 @@
 
     @Override
     public void getCallForwardingOption(int commandInterfaceCFReason, Message onComplete) {
+        getCallForwardingOption(commandInterfaceCFReason,
+                CommandsInterface.SERVICE_CLASS_VOICE, onComplete);
+    }
+
+    @Override
+    public void getCallForwardingOption(int commandInterfaceCFReason, int serviceClass,
+            Message onComplete) {
         if (isPhoneTypeGsm() || isImsUtEnabledOverCdma()) {
             Phone imsPhone = mImsPhone;
             if ((imsPhone != null)
                     && ((imsPhone.getServiceState().getState() == ServiceState.STATE_IN_SERVICE)
                     || imsPhone.isUtEnabled())) {
-                imsPhone.getCallForwardingOption(commandInterfaceCFReason, onComplete);
+                imsPhone.getCallForwardingOption(commandInterfaceCFReason, serviceClass,
+                        onComplete);
                 return;
             }
 
@@ -1932,11 +2135,13 @@
                 } else {
                     resp = onComplete;
                 }
-                mCi.queryCallForwardStatus(commandInterfaceCFReason,
-                        CommandsInterface.SERVICE_CLASS_VOICE, null, resp);
+                mCi.queryCallForwardStatus(commandInterfaceCFReason, serviceClass, null, resp);
             }
         } else {
             loge("getCallForwardingOption: not possible in CDMA without IMS");
+            AsyncResult.forMessage(onComplete, null,
+                    CommandException.fromRilErrno(RILConstants.REQUEST_NOT_SUPPORTED));
+            onComplete.sendToTarget();
         }
     }
 
@@ -1946,13 +2151,25 @@
             String dialingNumber,
             int timerSeconds,
             Message onComplete) {
+        setCallForwardingOption(commandInterfaceCFAction, commandInterfaceCFReason,
+                dialingNumber, CommandsInterface.SERVICE_CLASS_VOICE, timerSeconds, onComplete);
+    }
+
+    @Override
+    public void setCallForwardingOption(int commandInterfaceCFAction,
+            int commandInterfaceCFReason,
+            String dialingNumber,
+            int serviceClass,
+            int timerSeconds,
+            Message onComplete) {
         if (isPhoneTypeGsm() || isImsUtEnabledOverCdma()) {
             Phone imsPhone = mImsPhone;
             if ((imsPhone != null)
                     && ((imsPhone.getServiceState().getState() == ServiceState.STATE_IN_SERVICE)
                     || imsPhone.isUtEnabled())) {
                 imsPhone.setCallForwardingOption(commandInterfaceCFAction,
-                        commandInterfaceCFReason, dialingNumber, timerSeconds, onComplete);
+                        commandInterfaceCFReason, dialingNumber, serviceClass,
+                        timerSeconds, onComplete);
                 return;
             }
 
@@ -1969,13 +2186,16 @@
                 }
                 mCi.setCallForward(commandInterfaceCFAction,
                         commandInterfaceCFReason,
-                        CommandsInterface.SERVICE_CLASS_VOICE,
+                        serviceClass,
                         dialingNumber,
                         timerSeconds,
                         resp);
             }
         } else {
             loge("setCallForwardingOption: not possible in CDMA without IMS");
+            AsyncResult.forMessage(onComplete, null,
+                    CommandException.fromRilErrno(RILConstants.REQUEST_NOT_SUPPORTED));
+            onComplete.sendToTarget();
         }
     }
 
@@ -2102,6 +2322,9 @@
             mCi.setCallWaiting(enable, serviceClass, onComplete);
         } else {
             loge("method setCallWaiting is NOT supported in CDMA without IMS!");
+            AsyncResult.forMessage(onComplete, null,
+                    CommandException.fromRilErrno(RILConstants.REQUEST_NOT_SUPPORTED));
+            onComplete.sendToTarget();
         }
     }
 
@@ -2327,7 +2550,7 @@
             } else {
                 found.onUssdFinished(ussdMessage, isUssdRequest);
             }
-        } else if (!isUssdError && ussdMessage != null) {
+        } else if (!isUssdError && !TextUtils.isEmpty(ussdMessage)) {
             // pending USSD not found
             // The network may initiate its own USSD request
 
@@ -2348,18 +2571,40 @@
     @UnsupportedAppUsage
     private void syncClirSetting() {
         SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(getContext());
-        int clirSetting = sp.getInt(CLIR_KEY + getPhoneId(), -1);
-        Rlog.i(LOG_TAG, "syncClirSetting: " + CLIR_KEY + getPhoneId() + "=" + clirSetting);
+        migrateClirSettingIfNeeded(sp);
+
+        int clirSetting = sp.getInt(CLIR_KEY + getSubId(), -1);
+        Rlog.i(LOG_TAG, "syncClirSetting: " + CLIR_KEY + getSubId() + "=" + clirSetting);
         if (clirSetting >= 0) {
             mCi.setCLIR(clirSetting, null);
         }
     }
 
+    /**
+     * Migrate CLIR setting with sudId mapping once if there's CLIR setting mapped with phoneId.
+     */
+    private void migrateClirSettingIfNeeded(SharedPreferences sp) {
+        // Get old CLIR setting mapped with phoneId
+        int clirSetting = sp.getInt("clir_key" + getPhoneId(), -1);
+        if (clirSetting >= 0) {
+            // Migrate CLIR setting to new shared preference key with subId
+            Rlog.i(LOG_TAG, "Migrate CLIR setting: value=" + clirSetting + ", clir_key"
+                    + getPhoneId() + " -> " + CLIR_KEY + getSubId());
+            SharedPreferences.Editor editor = sp.edit();
+            editor.putInt(CLIR_KEY + getSubId(), clirSetting);
+
+            // Remove old CLIR setting key
+            editor.remove("clir_key" + getPhoneId()).commit();
+        }
+    }
+
     private void handleRadioAvailable() {
         mCi.getBasebandVersion(obtainMessage(EVENT_GET_BASEBAND_VERSION_DONE));
 
         mCi.getDeviceIdentity(obtainMessage(EVENT_GET_DEVICE_IDENTITY_DONE));
         mCi.getRadioCapability(obtainMessage(EVENT_GET_RADIO_CAPABILITY));
+        mCi.areUiccApplicationsEnabled(obtainMessage(EVENT_GET_UICC_APPS_ENABLEMENT_DONE));
+
         startLceAfterRadioIsAvailable();
     }
 
@@ -2492,8 +2737,9 @@
             case EVENT_CARRIER_CONFIG_CHANGED:
                 // Only check for the voice radio tech if it not going to be updated by the voice
                 // registration changes.
-                if (!mContext.getResources().getBoolean(com.android.internal.R.bool.
-                        config_switch_phone_on_voice_reg_state_change)) {
+                if (!mContext.getResources().getBoolean(
+                        com.android.internal.R.bool
+                                .config_switch_phone_on_voice_reg_state_change)) {
                     mCi.getVoiceRadioTechnology(obtainMessage(EVENT_REQUEST_VOICE_RADIO_TECH_DONE));
                 }
                 // Force update IMS service if it is available, if it isn't the config will be
@@ -2660,11 +2906,25 @@
                 }
                 break;
 
+            case EVENT_REGISTRATION_FAILED:
+                logd("Event RegistrationFailed Received");
+                ar = (AsyncResult) msg.obj;
+                RegistrationFailedEvent rfe = (RegistrationFailedEvent) ar.result;
+                mNotifier.notifyRegistrationFailed(this, rfe.cellIdentity, rfe.chosenPlmn,
+                        rfe.domain, rfe.causeCode, rfe.additionalCauseCode);
+                break;
+
+            case EVENT_BARRING_INFO_CHANGED:
+                logd("Event BarringInfoChanged Received");
+                ar = (AsyncResult) msg.obj;
+                BarringInfo barringInfo = (BarringInfo) ar.result;
+                mNotifier.notifyBarringInfoChanged(this, barringInfo);
+                break;
+
             case EVENT_SET_CALL_FORWARD_DONE:
                 ar = (AsyncResult)msg.obj;
-                IccRecords r = mIccRecords.get();
                 Cfu cfu = (Cfu) ar.userObj;
-                if (ar.exception == null && r != null) {
+                if (ar.exception == null) {
                     setVoiceCallForwardingFlag(1, msg.arg1 == 1, cfu.mSetCfNumber);
                 }
                 if (cfu.mOnComplete != null) {
@@ -2799,6 +3059,40 @@
                     onComplete.sendToTarget();
                 }
                 break;
+            case EVENT_GET_UICC_APPS_ENABLEMENT_DONE:
+            case EVENT_UICC_APPS_ENABLEMENT_STATUS_CHANGED:
+                ar = (AsyncResult) msg.obj;
+                if (ar == null) return;
+                if (ar.exception != null) {
+                    logd("Received exception on event" + msg.what + " : " + ar.exception);
+                    return;
+                }
+
+                mUiccApplicationsEnabled = (Boolean) ar.result;
+            // Intentional falling through.
+            case EVENT_UICC_APPS_ENABLEMENT_SETTING_CHANGED:
+                reapplyUiccAppsEnablementIfNeeded(ENABLE_UICC_APPS_MAX_RETRIES);
+                break;
+
+            case EVENT_REAPPLY_UICC_APPS_ENABLEMENT_DONE: {
+                ar = (AsyncResult) msg.obj;
+                if (ar == null || ar.exception == null) return;
+                Pair<Boolean, Integer> userObject = (Pair) ar.userObj;
+                if (userObject == null) return;
+                boolean expectedValue = userObject.first;
+                int retries = userObject.second;
+                CommandException.Error error = ((CommandException) ar.exception).getCommandError();
+                loge("Error received when re-applying uicc application"
+                        + " setting to " +  expectedValue + " on phone " + mPhoneId
+                        + " Error code: " + error + " retry count left: " + retries);
+                if (retries > 0 && (error == GENERIC_FAILURE || error == SIM_BUSY)) {
+                    // Retry for certain errors, but not for others like RADIO_NOT_AVAILABLE or
+                    // SIM_ABSENT, as they will trigger it whey they become available.
+                    postDelayed(()->reapplyUiccAppsEnablementIfNeeded(retries - 1),
+                            REAPPLY_UICC_APPS_SETTING_RETRY_TIME_GAP_IN_MS);
+                }
+                break;
+            }
             default:
                 super.handleMessage(msg);
         }
@@ -2893,9 +3187,11 @@
                                 simOperatorNumeric);
                     }
                 }
-                updateDataConnectionTracker();
+                updateCurrentCarrierInProvider();
             }
         }
+
+        reapplyUiccAppsEnablementIfNeeded(ENABLE_UICC_APPS_MAX_RETRIES);
     }
 
     private void processIccRecordEvents(int eventCode) {
@@ -2971,20 +3267,17 @@
     }
 
     private void handleCfuQueryResult(CallForwardInfo[] infos) {
-        IccRecords r = mIccRecords.get();
-        if (r != null) {
-            if (infos == null || infos.length == 0) {
-                // Assume the default is not active
-                // Set unconditional CFF in SIM to false
-                setVoiceCallForwardingFlag(1, false, null);
-            } else {
-                for (int i = 0, s = infos.length; i < s; i++) {
-                    if ((infos[i].serviceClass & SERVICE_CLASS_VOICE) != 0) {
-                        setVoiceCallForwardingFlag(1, (infos[i].status == 1),
-                            infos[i].number);
-                        // should only have the one
-                        break;
-                    }
+        if (infos == null || infos.length == 0) {
+            // Assume the default is not active
+            // Set unconditional CFF in SIM to false
+            setVoiceCallForwardingFlag(1, false, null);
+        } else {
+            for (int i = 0, s = infos.length; i < s; i++) {
+                if ((infos[i].serviceClass & SERVICE_CLASS_VOICE) != 0) {
+                    setVoiceCallForwardingFlag(1, (infos[i].status == 1),
+                        infos[i].number);
+                    // should only have the one
+                    break;
                 }
             }
         }
@@ -3094,8 +3387,8 @@
     @UnsupportedAppUsage
     private boolean isManualSelProhibitedInGlobalMode() {
         boolean isProhibited = false;
-        final String configString = getContext().getResources().getString(com.android.internal.
-                R.string.prohibit_manual_network_selection_in_gobal_mode);
+        final String configString = getContext().getResources().getString(com.android.internal
+                .R.string.prohibit_manual_network_selection_in_gobal_mode);
 
         if (!TextUtils.isEmpty(configString)) {
             String[] configArray = configString.split(";");
@@ -3148,16 +3441,21 @@
             Rlog.d(LOG_TAG, "exitEmergencyCallbackMode: mImsPhone=" + mImsPhone
                     + " isPhoneTypeGsm=" + isPhoneTypeGsm());
         }
-        if (isPhoneTypeGsm()) {
-            if (mImsPhone != null) {
-                mImsPhone.exitEmergencyCallbackMode();
-            }
+        if (mImsPhone != null && mImsPhone.isInImsEcm()) {
+            mImsPhone.exitEmergencyCallbackMode();
         } else {
             if (mWakeLock.isHeld()) {
                 mWakeLock.release();
             }
-            // Send a message which will invoke handleExitEmergencyCallbackMode
-            mCi.exitEmergencyCallbackMode(obtainMessage(EVENT_EXIT_EMERGENCY_CALLBACK_RESPONSE));
+            Message msg = null;
+            if (mIsTestingEmergencyCallbackMode) {
+                // prevent duplicate exit messages from happening due to this message being handled
+                // as well as an UNSOL when the modem exits ECbM. Instead, only register for this
+                // message callback when this is a test and we will not be receiving the UNSOL from
+                // the modem.
+                msg = obtainMessage(EVENT_EXIT_EMERGENCY_CALLBACK_RESPONSE);
+            }
+            mCi.exitEmergencyCallbackMode(msg);
         }
     }
 
@@ -3176,8 +3474,8 @@
 
             // Post this runnable so we will automatically exit
             // if no one invokes exitEmergencyCallbackMode() directly.
-            long delayInMillis = SystemProperties.getLong(
-                    TelephonyProperties.PROPERTY_ECM_EXIT_TIMER, DEFAULT_ECM_EXIT_TIMER_VALUE);
+            long delayInMillis = TelephonyProperties.ecm_exit_timer()
+                    .orElse(DEFAULT_ECM_EXIT_TIMER_VALUE);
             postDelayed(mExitEcmRunnable, delayInMillis);
             // We don't want to go to sleep while in Ecm
             mWakeLock.acquire();
@@ -3197,8 +3495,9 @@
         if (mEcmExitRespRegistrant != null) {
             mEcmExitRespRegistrant.notifyRegistrant(ar);
         }
-        // if exiting ecm success
-        if (ar.exception == null) {
+        // if exiting is successful or we are testing and the modem responded with an error upon
+        // exit, which may occur in some IRadio implementations.
+        if (ar.exception == null || mIsTestingEmergencyCallbackMode) {
             if (isInEcm()) {
                 setIsInEcm(false);
             }
@@ -3214,6 +3513,7 @@
             mDataEnabledSettings.setInternalDataEnabled(true);
             notifyEmergencyCallRegistrants(false);
         }
+        mIsTestingEmergencyCallbackMode = false;
     }
 
     //CDMA
@@ -3232,12 +3532,14 @@
             case CANCEL_ECM_TIMER:
                 removeCallbacks(mExitEcmRunnable);
                 mEcmTimerResetRegistrants.notifyResult(Boolean.TRUE);
+                setEcmCanceledForEmergency(true /*isCanceled*/);
                 break;
             case RESTART_ECM_TIMER:
-                long delayInMillis = SystemProperties.getLong(
-                        TelephonyProperties.PROPERTY_ECM_EXIT_TIMER, DEFAULT_ECM_EXIT_TIMER_VALUE);
+                long delayInMillis = TelephonyProperties.ecm_exit_timer()
+                        .orElse(DEFAULT_ECM_EXIT_TIMER_VALUE);
                 postDelayed(mExitEcmRunnable, delayInMillis);
                 mEcmTimerResetRegistrants.notifyResult(Boolean.FALSE);
+                setEcmCanceledForEmergency(false /*isCanceled*/);
                 break;
             default:
                 Rlog.e(LOG_TAG, "handleTimerInEmergencyCallbackMode, unsupported action " + action);
@@ -3492,11 +3794,11 @@
                 cdmaApplication.getType() == AppType.APPTYPE_RUIM);
     }
 
-    private void phoneObjectUpdater(int newVoiceRadioTech) {
+    protected void phoneObjectUpdater(int newVoiceRadioTech) {
         logd("phoneObjectUpdater: newVoiceRadioTech=" + newVoiceRadioTech);
 
-        // Check for a voice over lte replacement
-        if (ServiceState.isLte(newVoiceRadioTech)
+        // Check for a voice over LTE/NR replacement
+        if (ServiceState.isPsOnlyTech(newVoiceRadioTech)
                 || (newVoiceRadioTech == ServiceState.RIL_RADIO_TECHNOLOGY_UNKNOWN)) {
             CarrierConfigManager configMgr = (CarrierConfigManager)
                     getContext().getSystemService(Context.CARRIER_CONFIG_SERVICE);
@@ -3592,7 +3894,7 @@
         Intent intent = new Intent(TelephonyIntents.ACTION_RADIO_TECHNOLOGY_CHANGED);
         intent.putExtra(PhoneConstants.PHONE_NAME_KEY, getPhoneName());
         SubscriptionManager.putPhoneIdAndSubIdExtra(intent, mPhoneId);
-        ActivityManager.broadcastStickyIntent(intent, UserHandle.USER_ALL);
+        mContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL);
     }
 
     private void switchVoiceRadioTech(int newVoiceRadioTech) {
@@ -3620,9 +3922,11 @@
     }
 
     @Override
-    public void setSignalStrengthReportingCriteria(int[] thresholds, int ran) {
-        mCi.setSignalStrengthReportingCriteria(REPORTING_HYSTERESIS_MILLIS, REPORTING_HYSTERESIS_DB,
-                thresholds, ran, null);
+    public void setSignalStrengthReportingCriteria(
+            int signalStrengthMeasure, int[] thresholds, int ran, boolean isEnabled) {
+        mCi.setSignalStrengthReportingCriteria(new SignalThresholdInfo(signalStrengthMeasure,
+                REPORTING_HYSTERESIS_MILLIS, REPORTING_HYSTERESIS_DB, thresholds, isEnabled),
+                ran, null);
     }
 
     @Override
@@ -3684,15 +3988,15 @@
         pw.println(" mSST=" + mSST);
         pw.println(" mPendingMMIs=" + mPendingMMIs);
         pw.println(" mIccPhoneBookIntManager=" + mIccPhoneBookIntManager);
-        if (VDBG) pw.println(" mImei=" + mImei);
-        if (VDBG) pw.println(" mImeiSv=" + mImeiSv);
-        if (VDBG) pw.println(" mVmNumber=" + mVmNumber);
+        pw.println(" mImei=" + pii(mImei));
+        pw.println(" mImeiSv=" + pii(mImeiSv));
+        pw.println(" mVmNumber=" + pii(mVmNumber));
         pw.println(" mCdmaSSM=" + mCdmaSSM);
         pw.println(" mCdmaSubscriptionSource=" + mCdmaSubscriptionSource);
         pw.println(" mWakeLock=" + mWakeLock);
         pw.println(" isInEcm()=" + isInEcm());
-        if (VDBG) pw.println(" mEsn=" + mEsn);
-        if (VDBG) pw.println(" mMeid=" + mMeid);
+        pw.println(" mEsn=" + pii(mEsn));
+        pw.println(" mMeid=" + pii(mMeid));
         pw.println(" mCarrierOtaSpNumSchema=" + mCarrierOtaSpNumSchema);
         if (!isPhoneTypeGsm()) {
             pw.println(" getCdmaEriIconIndex()=" + getCdmaEriIconIndex());
@@ -3701,6 +4005,7 @@
             pw.println(" isMinInfoReady()=" + isMinInfoReady());
         }
         pw.println(" isCspPlmnEnabled()=" + isCspPlmnEnabled());
+        pw.println(" mManualNetworkSelectionPlmn=" + mManualNetworkSelectionPlmn);
         pw.flush();
     }
 
@@ -3728,9 +4033,44 @@
     }
 
     /**
-     * @return operator numeric.
+     * This allows a short number to be remapped to a test emergency number for testing how the
+     * frameworks handles Emergency Callback Mode without actually calling an emergency number.
+     *
+     * This is not a full test and is not a substitute for testing real emergency
+     * numbers but can be useful.
+     *
+     * To use this feature, first set a test emergency number using
+     * adb shell cmd phone emergency-number-test-mode -a 1-555-555-1212
+     *
+     * and then set the system property ril.test.emergencynumber to a pair of
+     * numbers separated by a colon. If the first number matches the number parameter
+     * this routine returns the second number. Example:
+     *
+     * ril.test.emergencynumber=411:1-555-555-1212
+     *
+     * To test Dial 411 take call then hang up on MO device to enter ECM.
+     *
+     * @param dialString to test if it should be remapped
+     * @return the same number or the remapped number.
      */
-    private String getOperatorNumeric() {
+    private String checkForTestEmergencyNumber(String dialString) {
+        String testEn = SystemProperties.get("ril.test.emergencynumber");
+        if (!TextUtils.isEmpty(testEn)) {
+            String[] values = testEn.split(":");
+            logd("checkForTestEmergencyNumber: values.length=" + values.length);
+            if (values.length == 2) {
+                if (values[0].equals(PhoneNumberUtils.stripSeparators(dialString))) {
+                    logd("checkForTestEmergencyNumber: remap " + dialString + " to " + values[1]);
+                    dialString = values[1];
+                }
+            }
+        }
+        return dialString;
+    }
+
+    @Override
+    @NonNull
+    public String getOperatorNumeric() {
         String operatorNumeric = null;
         if (isPhoneTypeGsm()) {
             IccRecords r = mIccRecords.get();
@@ -3772,7 +4112,7 @@
                     + " operatorNumeric = " + operatorNumeric);
 
         }
-        return operatorNumeric;
+        return TextUtils.emptyIfNull(operatorNumeric);
     }
 
     /**
@@ -3782,7 +4122,7 @@
         int subId = getSubId();
         SubscriptionInfo subInfo = SubscriptionManager.from(getContext())
                 .getActiveSubscriptionInfo(subId);
-        if (subInfo == null) {
+        if (subInfo == null || TextUtils.isEmpty(subInfo.getCountryIso())) {
             return null;
         }
         return subInfo.getCountryIso().toUpperCase();
@@ -3795,7 +4135,8 @@
     private static final int[] VOICE_PS_CALL_RADIO_TECHNOLOGY = {
             ServiceState.RIL_RADIO_TECHNOLOGY_LTE,
             ServiceState.RIL_RADIO_TECHNOLOGY_LTE_CA,
-            ServiceState.RIL_RADIO_TECHNOLOGY_IWLAN
+            ServiceState.RIL_RADIO_TECHNOLOGY_IWLAN,
+            ServiceState.RIL_RADIO_TECHNOLOGY_NR
     };
 
     /**
@@ -3807,10 +4148,10 @@
      * @return the RIL voice radio technology used for CS calls,
      *         see {@code RIL_RADIO_TECHNOLOGY_*} in {@link android.telephony.ServiceState}.
      */
-    public @ServiceState.RilRadioTechnology int getCsCallRadioTech() {
+    public @RilRadioTechnology int getCsCallRadioTech() {
         int calcVrat = ServiceState.RIL_RADIO_TECHNOLOGY_UNKNOWN;
         if (mSST != null) {
-            calcVrat = getCsCallRadioTech(mSST.mSS.getVoiceRegState(),
+            calcVrat = getCsCallRadioTech(mSST.mSS.getState(),
                     mSST.mSS.getRilVoiceRadioTechnology());
         }
 
@@ -3836,7 +4177,7 @@
      * @return the RIL voice radio technology used for CS calls,
      *         see {@code RIL_RADIO_TECHNOLOGY_*} in {@link android.telephony.ServiceState}.
      */
-    private @ServiceState.RilRadioTechnology int getCsCallRadioTech(int vrs, int vrat) {
+    private @RilRadioTechnology int getCsCallRadioTech(int vrs, int vrat) {
         logd("getCsCallRadioTech, current vrs=" + vrs + ", vrat=" + vrat);
         int calcVrat = vrat;
         if (vrs != ServiceState.STATE_IN_SERVICE
@@ -3873,6 +4214,23 @@
         mEcmTimerResetRegistrants.remove(h);
     }
 
+    @Override
+    public void registerForVolteSilentRedial(Handler h, int what, Object obj) {
+        mVolteSilentRedialRegistrants.addUnique(h, what, obj);
+    }
+
+    @Override
+    public void unregisterForVolteSilentRedial(Handler h) {
+        mVolteSilentRedialRegistrants.remove(h);
+    }
+
+    public void notifyVolteSilentRedial(String dialString, int causeCode) {
+        logd("notifyVolteSilentRedial: dialString=" + dialString + " causeCode=" + causeCode);
+        AsyncResult ar = new AsyncResult(null,
+                new SilentRedialParam(dialString, causeCode, mDialArgs), null);
+        mVolteSilentRedialRegistrants.notifyRegistrants(ar);
+    }
+
     /**
      * Sets the SIM voice message waiting indicator records.
      * @param line GSM Subscriber Profile Number, one-based. Only '1' is supported
@@ -3908,6 +4266,10 @@
         Rlog.e(LOG_TAG, "[" + mPhoneId + "] " + s);
     }
 
+    private static String pii(String s) {
+        return Rlog.pii(LOG_TAG, s);
+    }
+
     @Override
     public boolean isUtEnabled() {
         Phone imsPhone = mImsPhone;
@@ -3981,7 +4343,7 @@
      */
     private void loadTtyMode() {
         int ttyMode = TelecomManager.TTY_MODE_OFF;
-        TelecomManager telecomManager = TelecomManager.from(mContext);
+        TelecomManager telecomManager = mContext.getSystemService(TelecomManager.class);
         if (telecomManager != null) {
             ttyMode = telecomManager.getCurrentTtyMode();
         }
@@ -3991,4 +4353,57 @@
                 Settings.Secure.PREFERRED_TTY_MODE, TelecomManager.TTY_MODE_OFF);
         updateUiTtyMode(ttyMode);
     }
+
+    private void reapplyUiccAppsEnablementIfNeeded(int retries) {
+        UiccSlot slot = mUiccController.getUiccSlotForPhone(mPhoneId);
+
+        // If no card is present or we don't have mUiccApplicationsEnabled yet, do nothing.
+        if (slot == null || slot.getCardState() != IccCardStatus.CardState.CARDSTATE_PRESENT
+                || mUiccApplicationsEnabled == null) {
+            return;
+        }
+
+        String iccId = slot.getIccId();
+        if (iccId == null) return;
+
+        SubscriptionInfo info = SubscriptionController.getInstance().getSubInfoForIccId(
+                IccUtils.stripTrailingFs(iccId));
+
+        // If info is null, it could be a new subscription. By default we enable it.
+        boolean expectedValue = info == null ? true : info.areUiccApplicationsEnabled();
+
+        // If for any reason current state is different from configured state, re-apply the
+        // configured state.
+        if (expectedValue != mUiccApplicationsEnabled) {
+            mCi.enableUiccApplications(expectedValue, Message.obtain(
+                    this, EVENT_REAPPLY_UICC_APPS_ENABLEMENT_DONE,
+                    new Pair<Boolean, Integer>(expectedValue, retries)));
+        }
+    }
+
+    // Enable or disable uicc applications.
+    @Override
+    public void enableUiccApplications(boolean enable, Message onCompleteMessage) {
+        // First check if card is present. Otherwise mUiccApplicationsDisabled doesn't make
+        // any sense.
+        UiccSlot slot = mUiccController.getUiccSlotForPhone(mPhoneId);
+        if (slot == null || slot.getCardState() != IccCardStatus.CardState.CARDSTATE_PRESENT) {
+            if (onCompleteMessage != null) {
+                AsyncResult.forMessage(onCompleteMessage, null,
+                        new IllegalStateException("No SIM card is present"));
+                onCompleteMessage.sendToTarget();
+            }
+            return;
+        }
+
+        mCi.enableUiccApplications(enable, onCompleteMessage);
+    }
+
+    /**
+     * Whether disabling a physical subscription is supported or not.
+     */
+    @Override
+    public boolean canDisablePhysicalSubscription() {
+        return mCi.canToggleUiccApplicationsEnablement();
+    }
 }
diff --git a/src/java/com/android/internal/telephony/HardwareConfig.java b/src/java/com/android/internal/telephony/HardwareConfig.java
index 8623354..cdce602 100644
--- a/src/java/com/android/internal/telephony/HardwareConfig.java
+++ b/src/java/com/android/internal/telephony/HardwareConfig.java
@@ -16,9 +16,7 @@
 
 package com.android.internal.telephony;
 
-import android.telephony.Rlog;
 import java.util.BitSet;
-import android.telephony.ServiceState;
 
 /**
  * {@hide}
diff --git a/src/java/com/android/internal/telephony/HbpcdLookup.java b/src/java/com/android/internal/telephony/HbpcdLookup.java
deleted file mode 100644
index d9a3e72..0000000
--- a/src/java/com/android/internal/telephony/HbpcdLookup.java
+++ /dev/null
@@ -1,124 +0,0 @@
-/*
-**
-** Copyright 2014, 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;
-
-import android.net.Uri;
-import android.provider.BaseColumns;
-
-/**
- * @hide
- */
-public class HbpcdLookup {
-    public static final String AUTHORITY = "hbpcd_lookup";
-
-    public static final Uri CONTENT_URI =
-        Uri.parse("content://" + AUTHORITY);
-
-    public static final String PATH_MCC_IDD = "idd";
-    public static final String PATH_MCC_LOOKUP_TABLE = "lookup";
-    public static final String PATH_MCC_SID_CONFLICT = "conflict";
-    public static final String PATH_MCC_SID_RANGE = "range";
-    public static final String PATH_NANP_AREA_CODE = "nanp";
-    public static final String PATH_ARBITRARY_MCC_SID_MATCH = "arbitrary";
-    public static final String PATH_USERADD_COUNTRY = "useradd";
-
-    public static final String ID = "_id";
-    public static final int IDINDEX = 0;
-
-    /**
-     * @hide
-     */
-    public static class MccIdd implements BaseColumns {
-        public static final Uri CONTENT_URI =
-            Uri.parse("content://" + AUTHORITY + "/" + PATH_MCC_IDD);
-        public static final String DEFAULT_SORT_ORDER = "MCC ASC";
-
-        public static final String MCC = "MCC";
-        public static final String IDD = "IDD";
-
-    }
-
-    /**
-     * @hide
-     */
-    public static class MccLookup implements BaseColumns {
-        public static final Uri CONTENT_URI =
-            Uri.parse("content://" + AUTHORITY + "/" + PATH_MCC_LOOKUP_TABLE);
-        public static final String DEFAULT_SORT_ORDER = "MCC ASC";
-
-        public static final String MCC = "MCC";
-        public static final String COUNTRY_CODE = "Country_Code";
-        public static final String COUNTRY_NAME = "Country_Name";
-        public static final String NDD = "NDD";
-        public static final String NANPS = "NANPS";
-        public static final String GMT_OFFSET_LOW = "GMT_Offset_Low";
-        public static final String GMT_OFFSET_HIGH = "GMT_Offset_High";
-        public static final String GMT_DST_LOW = "GMT_DST_Low";
-        public static final String GMT_DST_HIGH = "GMT_DST_High";
-
-    }
-
-    /**
-     * @hide
-     */
-    public static class MccSidConflicts implements BaseColumns {
-        public static final Uri CONTENT_URI =
-            Uri.parse("content://" + AUTHORITY + "/" + PATH_MCC_SID_CONFLICT);
-        public static final String DEFAULT_SORT_ORDER = "MCC ASC";
-
-        public static final String MCC = "MCC";
-        public static final String SID_CONFLICT = "SID_Conflict";
-
-    }
-
-    /**
-     * @hide
-     */
-    public static class MccSidRange implements BaseColumns {
-        public static final Uri CONTENT_URI =
-            Uri.parse("content://" + AUTHORITY + "/" + PATH_MCC_SID_RANGE);
-        public static final String DEFAULT_SORT_ORDER = "MCC ASC";
-
-        public static final String MCC = "MCC";
-        public static final String RANGE_LOW = "SID_Range_Low";
-        public static final String RANGE_HIGH = "SID_Range_High";
-    }
-
-    /**
-     * @hide
-     */
-    public static class ArbitraryMccSidMatch implements BaseColumns {
-        public static final Uri CONTENT_URI =
-            Uri.parse("content://" + AUTHORITY + "/" + PATH_ARBITRARY_MCC_SID_MATCH);
-        public static final String DEFAULT_SORT_ORDER = "MCC ASC";
-
-        public static final String MCC = "MCC";
-        public static final String SID = "SID";
-
-    }
-
-    /**
-     * @hide
-     */
-    public static class NanpAreaCode implements BaseColumns {
-        public static final Uri CONTENT_URI =
-            Uri.parse("content://" + AUTHORITY + "/" + PATH_NANP_AREA_CODE);
-        public static final String DEFAULT_SORT_ORDER = "Area_Code ASC";
-
-        public static final String AREA_CODE = "Area_Code";
-    }
-}
diff --git a/src/java/com/android/internal/telephony/HbpcdUtils.java b/src/java/com/android/internal/telephony/HbpcdUtils.java
deleted file mode 100644
index 2f31942..0000000
--- a/src/java/com/android/internal/telephony/HbpcdUtils.java
+++ /dev/null
@@ -1,163 +0,0 @@
-/*
- * Copyright (C) 2014 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;
-
-import android.content.ContentResolver;
-import android.content.Context;
-import android.database.Cursor;
-import android.telephony.Rlog;
-
-import com.android.internal.telephony.HbpcdLookup.ArbitraryMccSidMatch;
-import com.android.internal.telephony.HbpcdLookup.MccIdd;
-import com.android.internal.telephony.HbpcdLookup.MccLookup;
-import com.android.internal.telephony.HbpcdLookup.MccSidConflicts;
-import com.android.internal.telephony.HbpcdLookup.MccSidRange;
-
-public final class HbpcdUtils {
-    private static final String LOG_TAG = "HbpcdUtils";
-    private static final boolean DBG = false;
-    private ContentResolver resolver = null;
-
-    public HbpcdUtils(Context context) {
-        resolver = context.getContentResolver();
-    }
-
-    /**
-     *  Resolves the unknown MCC with SID and Timezone information.
-    */
-    public int getMcc(int sid, int tz, int DSTflag, boolean isNitzTimeZone) {
-        int tmpMcc = 0;
-
-        // check if SID exists in arbitrary_mcc_sid_match table.
-        // these SIDs are assigned to more than 1 operators, but they are known to
-        // be used by a specific operator, other operators having the same SID are
-        // not using it currently, if that SID is in this table, we don't need to
-        // check other tables.
-        String projection2[] = {ArbitraryMccSidMatch.MCC};
-        Cursor c2 = resolver.query(ArbitraryMccSidMatch.CONTENT_URI, projection2,
-                            ArbitraryMccSidMatch.SID + "=" + sid, null, null);
-
-        if (c2 != null) {
-            int c2Counter = c2.getCount();
-            if (DBG) {
-                Rlog.d(LOG_TAG, "Query unresolved arbitrary table, entries are " + c2Counter);
-            }
-            if (c2Counter == 1) {
-                if (DBG) {
-                    Rlog.d(LOG_TAG, "Query Unresolved arbitrary returned the cursor " + c2);
-                }
-                c2.moveToFirst();
-                tmpMcc = c2.getInt(0);
-                if (DBG) {
-                    Rlog.d(LOG_TAG, "MCC found in arbitrary_mcc_sid_match: " + tmpMcc);
-                }
-                c2.close();
-                return tmpMcc;
-            }
-            c2.close();
-        }
-
-        // Then check if SID exists in mcc_sid_conflict table.
-        // and use the timezone in mcc_lookup table to check which MCC matches.
-        String projection3[] = {MccSidConflicts.MCC};
-        Cursor c3 = resolver.query(MccSidConflicts.CONTENT_URI, projection3,
-                MccSidConflicts.SID_CONFLICT + "=" + sid + " and (((" +
-                MccLookup.GMT_OFFSET_LOW + "<=" + tz + ") and (" + tz + "<=" +
-                MccLookup.GMT_OFFSET_HIGH + ") and (" + "0=" + DSTflag + ")) or ((" +
-                MccLookup.GMT_DST_LOW + "<=" + tz + ") and (" + tz + "<=" +
-                MccLookup.GMT_DST_HIGH + ") and (" + "1=" + DSTflag + ")))",
-                        null, null);
-        if (c3 != null) {
-            int c3Counter = c3.getCount();
-            if (c3Counter > 0) {
-                if (c3Counter > 1) {
-                    Rlog.w(LOG_TAG, "something wrong, get more results for 1 conflict SID: " + c3);
-                }
-                if (DBG) Rlog.d(LOG_TAG, "Query conflict sid returned the cursor " + c3);
-                c3.moveToFirst();
-                tmpMcc = c3.getInt(0);
-                if (DBG) {
-                    Rlog.d(LOG_TAG, "MCC found in mcc_lookup_table. Return tmpMcc = " + tmpMcc);
-                }
-                if (!isNitzTimeZone) {
-                    // time zone is not accurate, it may get wrong mcc, ignore it.
-                    if (DBG) {
-                        Rlog.d(LOG_TAG, "time zone is not accurate, mcc may be " + tmpMcc);
-                    }
-                    tmpMcc = 0;
-                }
-                c3.close();
-                return tmpMcc;
-            } else {
-                c3.close();
-            }
-        }
-
-        // if there is no conflict, then check if SID is in mcc_sid_range.
-        String projection5[] = {MccSidRange.MCC};
-        Cursor c5 = resolver.query(MccSidRange.CONTENT_URI, projection5,
-                MccSidRange.RANGE_LOW + "<=" + sid + " and " +
-                MccSidRange.RANGE_HIGH + ">=" + sid,
-                null, null);
-        if (c5 != null) {
-            if (c5.getCount() > 0) {
-                if (DBG) Rlog.d(LOG_TAG, "Query Range returned the cursor " + c5);
-                c5.moveToFirst();
-                tmpMcc = c5.getInt(0);
-                if (DBG) Rlog.d(LOG_TAG, "SID found in mcc_sid_range. Return tmpMcc = " + tmpMcc);
-                c5.close();
-                return tmpMcc;
-            }
-            c5.close();
-        }
-        if (DBG) Rlog.d(LOG_TAG, "SID NOT found in mcc_sid_range.");
-
-        if (DBG) Rlog.d(LOG_TAG, "Exit getMccByOtherFactors. Return tmpMcc =  " + tmpMcc);
-        // If unknown MCC still could not be resolved,
-        return tmpMcc;
-    }
-
-    /**
-     *  Gets country information with given MCC.
-    */
-    public String getIddByMcc(int mcc) {
-        if (DBG) Rlog.d(LOG_TAG, "Enter getHbpcdInfoByMCC.");
-        String idd = "";
-
-        Cursor c = null;
-
-        String projection[] = {MccIdd.IDD};
-        Cursor cur = resolver.query(MccIdd.CONTENT_URI, projection,
-                MccIdd.MCC + "=" + mcc, null, null);
-        if (cur != null) {
-            if (cur.getCount() > 0) {
-                if (DBG) Rlog.d(LOG_TAG, "Query Idd returned the cursor " + cur);
-                // TODO: for those country having more than 1 IDDs, need more information
-                // to decide which IDD would be used. currently just use the first 1.
-                cur.moveToFirst();
-                idd = cur.getString(0);
-                if (DBG) Rlog.d(LOG_TAG, "IDD = " + idd);
-
-            }
-            cur.close();
-        }
-        if (c != null) c.close();
-
-        if (DBG) Rlog.d(LOG_TAG, "Exit getHbpcdInfoByMCC.");
-        return idd;
-    }
-}
diff --git a/src/java/com/android/internal/telephony/IIccPhoneBook.aidl b/src/java/com/android/internal/telephony/IIccPhoneBook.aidl
index 5090d1a..dc990de 100644
--- a/src/java/com/android/internal/telephony/IIccPhoneBook.aidl
+++ b/src/java/com/android/internal/telephony/IIccPhoneBook.aidl
@@ -18,22 +18,9 @@
 
 import com.android.internal.telephony.uicc.AdnRecord;
 
-
-
-/** Interface for applications to access the ICC phone book.
- *
- * <p>The following code snippet demonstrates a static method to
- * retrieve the IIccPhoneBook interface from Android:</p>
- * <pre>private static IIccPhoneBook getSimPhoneBookInterface()
-            throws DeadObjectException {
-    IServiceManager sm = ServiceManagerNative.getDefault();
-    IIccPhoneBook spb;
-    spb = IIccPhoneBook.Stub.asInterface(sm.getService("iccphonebook"));
-    return spb;
-}
- * </pre>
+/**
+ * Interface for applications to access the ICC phone book.
  */
-
 interface IIccPhoneBook {
 
     /**
@@ -43,6 +30,7 @@
      * @param efid the EF id of a ADN-like SIM
      * @return List of AdnRecord
      */
+    @UnsupportedAppUsage
     List<AdnRecord> getAdnRecordsInEf(int efid);
 
     /**
@@ -53,6 +41,7 @@
      * @param subId user preferred subId
      * @return List of AdnRecord
      */
+    @UnsupportedAppUsage
     List<AdnRecord> getAdnRecordsInEfForSubscriber(int subId, int efid);
 
     /**
@@ -73,6 +62,7 @@
      * @param pin2 required to update EF_FDN, otherwise must be null
      * @return true for success
      */
+    @UnsupportedAppUsage
     boolean updateAdnRecordsInEfBySearch(int efid,
             String oldTag, String oldPhoneNumber,
             String newTag, String newPhoneNumber,
@@ -151,6 +141,7 @@
      *            recordSizes[1]  is the total length of the EF file
      *            recordSizes[2]  is the number of records in the EF file
      */
+    @UnsupportedAppUsage
     int[] getAdnRecordsSize(int efid);
 
     /**
@@ -163,6 +154,7 @@
      *            recordSizes[1]  is the total length of the EF file
      *            recordSizes[2]  is the number of records in the EF file
      */
+    @UnsupportedAppUsage
     int[] getAdnRecordsSizeForSubscriber(int subId, int efid);
 
 }
diff --git a/src/java/com/android/internal/telephony/IccCard.java b/src/java/com/android/internal/telephony/IccCard.java
index d15c44c..39ce269 100644
--- a/src/java/com/android/internal/telephony/IccCard.java
+++ b/src/java/com/android/internal/telephony/IccCard.java
@@ -16,13 +16,14 @@
 
 package com.android.internal.telephony;
 
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
 import android.os.AsyncResult;
 import android.os.Handler;
 import android.os.Message;
 
 import com.android.internal.telephony.IccCardConstants.State;
 import com.android.internal.telephony.uicc.IccCardApplicationStatus;
+import com.android.internal.telephony.uicc.IccCardApplicationStatus.PersoSubState;
 import com.android.internal.telephony.uicc.IccRecords;
 
 /**
@@ -138,6 +139,14 @@
     }
 
     /**
+     * Supply Sim depersonalization code to the RIL
+     */
+    public void supplySimDepersonalization(PersoSubState persoType,
+            String controlKey, Message onComplete) {
+        sendMessageWithCardAbsentException(onComplete);
+    }
+
+    /**
      * Check whether ICC pin lock is enabled
      * This is a sync call which returns the cached pin enabled state
      *
diff --git a/src/java/com/android/internal/telephony/IccPhoneBookInterfaceManager.java b/src/java/com/android/internal/telephony/IccPhoneBookInterfaceManager.java
index e619e61..e230001 100644
--- a/src/java/com/android/internal/telephony/IccPhoneBookInterfaceManager.java
+++ b/src/java/com/android/internal/telephony/IccPhoneBookInterfaceManager.java
@@ -16,13 +16,12 @@
 
 package com.android.internal.telephony;
 
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
 import android.content.pm.PackageManager;
 import android.os.AsyncResult;
 import android.os.Handler;
 import android.os.Looper;
 import android.os.Message;
-import android.telephony.Rlog;
 
 import com.android.internal.telephony.uicc.AdnRecord;
 import com.android.internal.telephony.uicc.AdnRecordCache;
@@ -30,6 +29,7 @@
 import com.android.internal.telephony.uicc.IccConstants;
 import com.android.internal.telephony.uicc.IccFileHandler;
 import com.android.internal.telephony.uicc.IccRecords;
+import com.android.telephony.Rlog;
 
 import java.util.List;
 import java.util.concurrent.atomic.AtomicBoolean;
@@ -190,11 +190,12 @@
             if (mAdnCache != null) {
                 mAdnCache.updateAdnBySearch(efid, oldAdn, newAdn, pin2, response);
                 waitForResult(updateRequest);
+                return (boolean) updateRequest.mResult;
             } else {
                 loge("Failure while trying to update by search due to uninitialised adncache");
+                return false;
             }
         }
-        return (boolean) updateRequest.mResult;
     }
 
     /**
@@ -239,11 +240,12 @@
             if (mAdnCache != null) {
                 mAdnCache.updateAdnByIndex(efid, newAdn, index, pin2, response);
                 waitForResult(updateRequest);
+                return (boolean) updateRequest.mResult;
             } else {
                 loge("Failure while trying to update by index due to uninitialised adncache");
+                return false;
             }
         }
-        return (boolean) updateRequest.mResult;
     }
 
     /**
@@ -301,11 +303,12 @@
             if (mAdnCache != null) {
                 mAdnCache.requestLoadAllAdnLike(efid, mAdnCache.extensionEfForEf(efid), response);
                 waitForResult(loadRequest);
+                return (List<AdnRecord>) loadRequest.mResult;
             } else {
                 loge("Failure while trying to load from SIM due to uninitialised adncache");
+                return null;
             }
         }
-        return (List<AdnRecord>) loadRequest.mResult;
     }
 
     @UnsupportedAppUsage
diff --git a/src/java/com/android/internal/telephony/IccProvider.java b/src/java/com/android/internal/telephony/IccProvider.java
index 3ac4027..1b68ff6 100644
--- a/src/java/com/android/internal/telephony/IccProvider.java
+++ b/src/java/com/android/internal/telephony/IccProvider.java
@@ -16,7 +16,7 @@
 
 package com.android.internal.telephony;
 
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
 import android.content.ContentProvider;
 import android.content.ContentValues;
 import android.content.UriMatcher;
@@ -25,18 +25,17 @@
 import android.database.MergeCursor;
 import android.net.Uri;
 import android.os.RemoteException;
-import android.os.ServiceManager;
-import android.telephony.Rlog;
 import android.telephony.SubscriptionInfo;
 import android.telephony.SubscriptionManager;
+import android.telephony.TelephonyFrameworkInitializer;
 import android.text.TextUtils;
 
 import com.android.internal.telephony.uicc.AdnRecord;
 import com.android.internal.telephony.uicc.IccConstants;
+import com.android.telephony.Rlog;
 
 import java.util.List;
 
-
 /**
  * {@hide}
  */
@@ -81,6 +80,10 @@
 
     private SubscriptionManager mSubscriptionManager;
 
+    @UnsupportedAppUsage
+    public IccProvider() {
+    }
+
     @Override
     public boolean onCreate() {
         mSubscriptionManager = SubscriptionManager.from(getContext());
@@ -401,7 +404,10 @@
         List<AdnRecord> adnRecords = null;
         try {
             IIccPhoneBook iccIpb = IIccPhoneBook.Stub.asInterface(
-                    ServiceManager.getService("simphonebook"));
+                    TelephonyFrameworkInitializer
+                            .getTelephonyServiceManager()
+                            .getIccPhoneBookServiceRegisterer()
+                            .get());
             if (iccIpb != null) {
                 adnRecords = iccIpb.getAdnRecordsInEfForSubscriber(subId, efType);
             }
@@ -443,7 +449,10 @@
 
         try {
             IIccPhoneBook iccIpb = IIccPhoneBook.Stub.asInterface(
-                    ServiceManager.getService("simphonebook"));
+                    TelephonyFrameworkInitializer
+                            .getTelephonyServiceManager()
+                            .getIccPhoneBookServiceRegisterer()
+                            .get());
             if (iccIpb != null) {
                 success = iccIpb.updateAdnRecordsInEfBySearchForSubscriber(subId, efType,
                         "", "", name, number, pin2);
@@ -469,7 +478,10 @@
 
         try {
             IIccPhoneBook iccIpb = IIccPhoneBook.Stub.asInterface(
-                    ServiceManager.getService("simphonebook"));
+                    TelephonyFrameworkInitializer
+                            .getTelephonyServiceManager()
+                            .getIccPhoneBookServiceRegisterer()
+                            .get());
             if (iccIpb != null) {
                 success = iccIpb.updateAdnRecordsInEfBySearchForSubscriber(subId, efType, oldName,
                         oldNumber, newName, newNumber, pin2);
@@ -495,7 +507,10 @@
 
         try {
             IIccPhoneBook iccIpb = IIccPhoneBook.Stub.asInterface(
-                    ServiceManager.getService("simphonebook"));
+                    TelephonyFrameworkInitializer
+                            .getTelephonyServiceManager()
+                            .getIccPhoneBookServiceRegisterer()
+                            .get());
             if (iccIpb != null) {
                 success = iccIpb.updateAdnRecordsInEfBySearchForSubscriber(subId, efType,
                           name, number, "", "", pin2);
diff --git a/src/java/com/android/internal/telephony/IccSmsInterfaceManager.java b/src/java/com/android/internal/telephony/IccSmsInterfaceManager.java
index 8ee349d..2c402de 100644
--- a/src/java/com/android/internal/telephony/IccSmsInterfaceManager.java
+++ b/src/java/com/android/internal/telephony/IccSmsInterfaceManager.java
@@ -21,9 +21,9 @@
 import static android.telephony.SmsManager.STATUS_ON_ICC_UNREAD;
 
 import android.Manifest;
-import android.annotation.UnsupportedAppUsage;
 import android.app.AppOpsManager;
 import android.app.PendingIntent;
+import android.compat.annotation.UnsupportedAppUsage;
 import android.content.ContentResolver;
 import android.content.Context;
 import android.content.pm.PackageManager;
@@ -33,12 +33,14 @@
 import android.os.AsyncResult;
 import android.os.Binder;
 import android.os.Handler;
+import android.os.Looper;
 import android.os.Message;
 import android.os.UserManager;
 import android.provider.Telephony;
-import android.telephony.Rlog;
+import android.telephony.SmsCbMessage;
 import android.telephony.SmsManager;
 import android.telephony.SmsMessage;
+import android.telephony.emergency.EmergencyNumber;
 import android.util.LocalLog;
 import android.util.Log;
 
@@ -48,7 +50,10 @@
 import com.android.internal.telephony.uicc.IccConstants;
 import com.android.internal.telephony.uicc.IccFileHandler;
 import com.android.internal.telephony.uicc.IccUtils;
+import com.android.internal.telephony.uicc.UiccController;
+import com.android.internal.telephony.uicc.UiccProfile;
 import com.android.internal.util.HexDump;
+import com.android.telephony.Rlog;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
@@ -71,6 +76,8 @@
     @UnsupportedAppUsage
     private List<SmsRawData> mSms;
 
+    private String mSmsc;
+
     @UnsupportedAppUsage
     private CellBroadcastRangeManager mCellBroadcastRangeManager =
             new CellBroadcastRangeManager();
@@ -81,6 +88,8 @@
     private static final int EVENT_UPDATE_DONE = 2;
     protected static final int EVENT_SET_BROADCAST_ACTIVATION_DONE = 3;
     protected static final int EVENT_SET_BROADCAST_CONFIG_DONE = 4;
+    private static final int EVENT_GET_SMSC_DONE = 5;
+    private static final int EVENT_SET_SMSC_DONE = 6;
     private static final int SMS_CB_CODE_SCHEME_MIN = 0;
     private static final int SMS_CB_CODE_SCHEME_MAX = 255;
     public static final int SMS_MESSAGE_PRIORITY_NOT_SPECIFIED = -1;
@@ -121,7 +130,7 @@
                             markMessagesAsRead((ArrayList<byte[]>) ar.result);
                         } else {
                             if (Rlog.isLoggable("SMS", Log.DEBUG)) {
-                                log("Cannot load Sms records");
+                                loge("Cannot load Sms records");
                             }
                             mSms = null;
                         }
@@ -136,6 +145,25 @@
                         mLock.notifyAll();
                     }
                     break;
+                case EVENT_GET_SMSC_DONE:
+                    ar = (AsyncResult) msg.obj;
+                    synchronized (mLock) {
+                        if (ar.exception == null) {
+                            mSmsc = (String) ar.result;
+                        } else {
+                            loge("Cannot read SMSC");
+                            mSmsc = null;
+                        }
+                        mLock.notifyAll();
+                    }
+                    break;
+                case EVENT_SET_SMSC_DONE:
+                    ar = (AsyncResult) msg.obj;
+                    synchronized (mLock) {
+                        mSuccess = (ar.exception == null);
+                        mLock.notifyAll();
+                    }
+                    break;
             }
         }
     };
@@ -159,6 +187,13 @@
         mSmsPermissions = new SmsPermissions(phone, context, appOps);
     }
 
+    private void enforceNotOnHandlerThread(String methodName) {
+        if (Looper.myLooper() == mHandler.getLooper()) {
+            throw new RuntimeException("This method " + methodName + " will deadlock if called from"
+                    + " the handler's thread.");
+        }
+    }
+
     protected void markMessagesAsRead(ArrayList<byte[]> messages) {
         if (messages == null) {
             return;
@@ -170,7 +205,7 @@
             //shouldn't really happen, as messages are marked as read, only
             //after importing it from icc.
             if (Rlog.isLoggable("SMS", Log.DEBUG)) {
-                log("markMessagesAsRead - aborting, no icc card present.");
+                loge("markMessagesAsRead - aborting, no icc card present.");
             }
             return;
         }
@@ -178,17 +213,17 @@
         int count = messages.size();
 
         for (int i = 0; i < count; i++) {
-             byte[] ba = messages.get(i);
-             if (ba[0] == STATUS_ON_ICC_UNREAD) {
-                 int n = ba.length;
-                 byte[] nba = new byte[n - 1];
-                 System.arraycopy(ba, 1, nba, 0, n - 1);
-                 byte[] record = makeSmsRecordData(STATUS_ON_ICC_READ, nba);
-                 fh.updateEFLinearFixed(IccConstants.EF_SMS, i + 1, record, null, null);
-                 if (Rlog.isLoggable("SMS", Log.DEBUG)) {
-                     log("SMS " + (i + 1) + " marked as read");
-                 }
-             }
+            byte[] ba = messages.get(i);
+            if ((ba[0] & 0x07) == STATUS_ON_ICC_UNREAD) {
+                int n = ba.length;
+                byte[] nba = new byte[n - 1];
+                System.arraycopy(ba, 1, nba, 0, n - 1);
+                byte[] record = makeSmsRecordData(STATUS_ON_ICC_READ, nba);
+                fh.updateEFLinearFixed(IccConstants.EF_SMS, i + 1, record, null, null);
+                if (Rlog.isLoggable("SMS", Log.DEBUG)) {
+                    log("SMS " + (i + 1) + " marked as read");
+                }
+            }
         }
     }
 
@@ -201,6 +236,14 @@
     }
 
     /**
+     * Enforce the permission for access messages on ICC
+     */
+    private void enforceAccessMessageOnICC(String message) {
+        mContext.enforceCallingOrSelfPermission(
+                Manifest.permission.ACCESS_MESSAGES_ON_ICC, message);
+    }
+
+    /**
      * Update the specified message on the Icc.
      *
      * @param index record index of message to update
@@ -219,7 +262,9 @@
                 " status=" + status + " ==> " +
                 "("+ Arrays.toString(pdu) + ")");
         enforceReceiveAndSend("Updating message on Icc");
-        if (mAppOps.noteOp(AppOpsManager.OP_WRITE_ICC_SMS, Binder.getCallingUid(),
+        enforceAccessMessageOnICC("Updating message on Icc");
+        enforceNotOnHandlerThread("updateMessageOnIccEf");
+        if (mAppOps.noteOp(AppOpsManager.OPSTR_WRITE_ICC_SMS, Binder.getCallingUid(),
                 callingPackage) != AppOpsManager.MODE_ALLOWED) {
             return false;
         }
@@ -252,20 +297,24 @@
             try {
                 mLock.wait();
             } catch (InterruptedException e) {
-                log("interrupted while trying to update by index");
+                loge("interrupted while trying to update by index");
             }
         }
         return mSuccess;
     }
 
     /**
-     * Copy a raw SMS PDU to the Icc.
+     * Copies a raw SMS PDU to the ICC.
      *
-     * @param pdu the raw PDU to store
-     * @param status message status (STATUS_ON_ICC_READ, STATUS_ON_ICC_UNREAD,
-     *               STATUS_ON_ICC_SENT, STATUS_ON_ICC_UNSENT)
-     * @return success or not
-     *
+     * @param callingPackage the package name of the calling app.
+     * @param status message status. One of these status:
+     *               <code>STATUS_ON_ICC_READ</code>
+     *               <code>STATUS_ON_ICC_UNREAD</code>
+     *               <code>STATUS_ON_ICC_SENT</code>
+     *               <code>STATUS_ON_ICC_UNSENT</code>
+     * @param pdu the raw PDU to store.
+     * @param smsc the SMSC for this message. Null means use default.
+     * @return true for success. Otherwise false.
      */
     @UnsupportedAppUsage
     public boolean copyMessageToIccEf(String callingPackage, int status, byte[] pdu, byte[] smsc) {
@@ -274,7 +323,8 @@
                 "pdu=("+ Arrays.toString(pdu) +
                 "), smsc=(" + Arrays.toString(smsc) +")");
         enforceReceiveAndSend("Copying message to Icc");
-        if (mAppOps.noteOp(AppOpsManager.OP_WRITE_ICC_SMS, Binder.getCallingUid(),
+        enforceNotOnHandlerThread("copyMessageToIccEf");
+        if (mAppOps.noteOp(AppOpsManager.OPSTR_WRITE_ICC_SMS, Binder.getCallingUid(),
                 callingPackage) != AppOpsManager.MODE_ALLOWED) {
             return false;
         }
@@ -287,14 +337,13 @@
                 mPhone.mCi.writeSmsToSim(status, IccUtils.bytesToHexString(smsc),
                         IccUtils.bytesToHexString(pdu), response);
             } else {
-                mPhone.mCi.writeSmsToRuim(status, IccUtils.bytesToHexString(pdu),
-                        response);
+                mPhone.mCi.writeSmsToRuim(status, pdu, response);
             }
 
             try {
                 mLock.wait();
             } catch (InterruptedException e) {
-                log("interrupted while trying to update by index");
+                loge("interrupted while trying to update by index");
             }
         }
         return mSuccess;
@@ -313,7 +362,9 @@
         mContext.enforceCallingOrSelfPermission(
                 Manifest.permission.RECEIVE_SMS,
                 "Reading messages from Icc");
-        if (mAppOps.noteOp(AppOpsManager.OP_READ_ICC_SMS, Binder.getCallingUid(),
+        enforceAccessMessageOnICC("Reading messages from Icc");
+        enforceNotOnHandlerThread("getAllMessagesFromIccEf");
+        if (mAppOps.noteOp(AppOpsManager.OPSTR_READ_ICC_SMS, Binder.getCallingUid(),
                 callingPackage) != AppOpsManager.MODE_ALLOWED) {
             return new ArrayList<SmsRawData>();
         }
@@ -321,7 +372,7 @@
 
             IccFileHandler fh = mPhone.getIccFileHandler();
             if (fh == null) {
-                Rlog.e(LOG_TAG, "Cannot load Sms records. No icc card?");
+                loge("Cannot load Sms records. No icc card?");
                 mSms = null;
                 return mSms;
             }
@@ -332,7 +383,7 @@
             try {
                 mLock.wait();
             } catch (InterruptedException e) {
-                log("interrupted while trying to load from the Icc");
+                loge("interrupted while trying to load from the Icc");
             }
         }
         return mSms;
@@ -342,10 +393,11 @@
      * A permissions check before passing to {@link IccSmsInterfaceManager#sendDataInternal}.
      * This method checks if the calling package or itself has the permission to send the data sms.
      */
-    public void sendDataWithSelfPermissions(String callingPackage, String destAddr, String scAddr,
-            int destPort, byte[] data, PendingIntent sentIntent, PendingIntent deliveryIntent,
-            boolean isForVvm) {
-        if (!mSmsPermissions.checkCallingOrSelfCanSendSms(callingPackage, "Sending SMS message")) {
+    public void sendDataWithSelfPermissions(String callingPackage, String callingAttributionTag,
+            String destAddr, String scAddr, int destPort, byte[] data, PendingIntent sentIntent,
+            PendingIntent deliveryIntent, boolean isForVvm) {
+        if (!mSmsPermissions.checkCallingOrSelfCanSendSms(callingPackage, callingAttributionTag,
+                "Sending SMS message")) {
             returnUnspecifiedFailure(sentIntent);
             return;
         }
@@ -354,13 +406,26 @@
     }
 
     /**
-     * A permissions check before passing to {@link IccSmsInterfaceManager#sendDataInternal}.
-     * This method checks only if the calling package has the permission to send the data sms.
+     * @deprecated Use {@link #sendData(String, String, String, String, int, byte[], PendingIntent,
+     * PendingIntent)} instead.
      */
+    @Deprecated
     @UnsupportedAppUsage
     public void sendData(String callingPackage, String destAddr, String scAddr, int destPort,
             byte[] data, PendingIntent sentIntent, PendingIntent deliveryIntent) {
-        if (!mSmsPermissions.checkCallingCanSendSms(callingPackage, "Sending SMS message")) {
+        sendData(callingPackage, null, destAddr, scAddr, destPort, data,
+                sentIntent, deliveryIntent);
+    }
+
+    /**
+     * A permissions check before passing to {@link IccSmsInterfaceManager#sendDataInternal}.
+     * This method checks only if the calling package has the permission to send the data sms.
+     */
+    public void sendData(String callingPackage, String callingAttributionTag,
+            String destAddr, String scAddr, int destPort, byte[] data, PendingIntent sentIntent,
+            PendingIntent deliveryIntent) {
+        if (!mSmsPermissions.checkCallingCanSendSms(callingPackage, callingAttributionTag,
+                "Sending SMS message")) {
             returnUnspecifiedFailure(sentIntent);
             return;
         }
@@ -415,26 +480,28 @@
      */
     public void sendText(String callingPackage, String destAddr, String scAddr,
             String text, PendingIntent sentIntent, PendingIntent deliveryIntent,
-            boolean persistMessageForNonDefaultSmsApp) {
+            boolean persistMessageForNonDefaultSmsApp, long messageId) {
         sendTextInternal(callingPackage, destAddr, scAddr, text, sentIntent, deliveryIntent,
                 persistMessageForNonDefaultSmsApp, SMS_MESSAGE_PRIORITY_NOT_SPECIFIED,
-                false /* expectMore */, SMS_MESSAGE_PERIOD_NOT_SPECIFIED, false /* isForVvm */);
+                false /* expectMore */, SMS_MESSAGE_PERIOD_NOT_SPECIFIED, false /* isForVvm */,
+                messageId);
     }
 
     /**
      * A permissions check before passing to {@link IccSmsInterfaceManager#sendTextInternal}.
      * This method checks if the calling package or itself has the permission to send the sms.
      */
-    public void sendTextWithSelfPermissions(String callingPackage, String destAddr, String scAddr,
-            String text, PendingIntent sentIntent, PendingIntent deliveryIntent,
-            boolean persistMessage, boolean isForVvm) {
-        if (!mSmsPermissions.checkCallingOrSelfCanSendSms(callingPackage, "Sending SMS message")) {
+    public void sendTextWithSelfPermissions(String callingPackage, String callingAttributeTag,
+            String destAddr, String scAddr, String text, PendingIntent sentIntent,
+            PendingIntent deliveryIntent, boolean persistMessage, boolean isForVvm) {
+        if (!mSmsPermissions.checkCallingOrSelfCanSendSms(callingPackage, callingAttributeTag,
+                "Sending SMS message")) {
             returnUnspecifiedFailure(sentIntent);
             return;
         }
         sendTextInternal(callingPackage, destAddr, scAddr, text, sentIntent, deliveryIntent,
                 persistMessage, SMS_MESSAGE_PRIORITY_NOT_SPECIFIED, false /* expectMore */,
-                SMS_MESSAGE_PERIOD_NOT_SPECIFIED, isForVvm);
+                SMS_MESSAGE_PERIOD_NOT_SPECIFIED, isForVvm, 0L /* messageId */);
     }
 
     /**
@@ -481,22 +548,26 @@
      *  Validity Period(Minimum) -> 5 mins
      *  Validity Period(Maximum) -> 635040 mins(i.e.63 weeks).
      *  Any Other values including negative considered as Invalid Validity Period of the message.
+     * @param messageId An id that uniquely identifies the message requested to be sent.
+     *                 Used for logging and diagnostics purposes. The id may be 0.
      */
 
     private void sendTextInternal(String callingPackage, String destAddr, String scAddr,
             String text, PendingIntent sentIntent, PendingIntent deliveryIntent,
             boolean persistMessageForNonDefaultSmsApp, int priority, boolean expectMore,
-            int validityPeriod, boolean isForVvm) {
+            int validityPeriod, boolean isForVvm, long messageId) {
         if (Rlog.isLoggable("SMS", Log.VERBOSE)) {
             log("sendText: destAddr=" + destAddr + " scAddr=" + scAddr
                     + " text='" + text + "' sentIntent=" + sentIntent + " deliveryIntent="
                     + deliveryIntent + " priority=" + priority + " expectMore=" + expectMore
-                    + " validityPeriod=" + validityPeriod + " isForVVM=" + isForVvm);
+                    + " validityPeriod=" + validityPeriod + " isForVVM=" + isForVvm
+                    + " id= " +  messageId);
         }
+        notifyIfOutgoingEmergencySms(destAddr);
         destAddr = filterDestAddress(destAddr);
         mDispatchersController.sendText(destAddr, scAddr, text, sentIntent, deliveryIntent,
                 null/*messageUri*/, callingPackage, persistMessageForNonDefaultSmsApp,
-                priority, expectMore, validityPeriod, isForVvm);
+                priority, expectMore, validityPeriod, isForVvm, messageId);
     }
 
     /**
@@ -545,17 +616,18 @@
      *  Any Other values including negative considered as Invalid Validity Period of the message.
      */
 
-    public void sendTextWithOptions(String callingPackage, String destAddr, String scAddr,
-            String text, PendingIntent sentIntent, PendingIntent deliveryIntent,
-            boolean persistMessageForNonDefaultSmsApp, int priority, boolean expectMore,
-            int validityPeriod) {
-        if (!mSmsPermissions.checkCallingOrSelfCanSendSms(callingPackage, "Sending SMS message")) {
+    public void sendTextWithOptions(String callingPackage, String callingAttributionTag,
+            String destAddr, String scAddr, String text, PendingIntent sentIntent,
+            PendingIntent deliveryIntent, boolean persistMessageForNonDefaultSmsApp, int priority,
+            boolean expectMore, int validityPeriod) {
+        if (!mSmsPermissions.checkCallingOrSelfCanSendSms(callingPackage, callingAttributionTag,
+                "Sending SMS message")) {
             returnUnspecifiedFailure(sentIntent);
             return;
         }
         sendTextInternal(callingPackage, destAddr, scAddr, text, sentIntent, deliveryIntent,
                 persistMessageForNonDefaultSmsApp, priority, expectMore, validityPeriod,
-                false /* isForVvm */);
+                false /* isForVvm */, 0L /* messageId */);
     }
 
     /**
@@ -586,7 +658,7 @@
                         try {
                             receivedIntent.send(result);
                         } catch (PendingIntent.CanceledException e) {
-                            Rlog.d(LOG_TAG, "receivedIntent cancelled.");
+                            loge("receivedIntent cancelled.");
                         }
                     }
                 }
@@ -617,15 +689,19 @@
      *   broadcast when the corresponding message part has been delivered
      *   to the recipient.  The raw pdu of the status report is in the
      *   extended data ("pdu").
+     * @param messageId An id that uniquely identifies the message requested to be sent.
+     *                 Used for logging and diagnostics purposes. The id may be 0.
      */
 
-    public void sendMultipartText(String callingPackage, String destAddr, String scAddr,
-            List<String> parts, List<PendingIntent> sentIntents,
-            List<PendingIntent> deliveryIntents, boolean persistMessageForNonDefaultSmsApp) {
-        sendMultipartTextWithOptions(callingPackage, destAddr, scAddr, parts, sentIntents,
-                deliveryIntents, persistMessageForNonDefaultSmsApp,
+    public void sendMultipartText(String callingPackage, String callingAttributionTag,
+            String destAddr, String scAddr, List<String> parts, List<PendingIntent> sentIntents,
+            List<PendingIntent> deliveryIntents, boolean persistMessageForNonDefaultSmsApp,
+            long messageId) {
+        sendMultipartTextWithOptions(callingPackage, callingAttributionTag, destAddr, scAddr, parts,
+                sentIntents, deliveryIntents, persistMessageForNonDefaultSmsApp,
                 SMS_MESSAGE_PRIORITY_NOT_SPECIFIED, false /* expectMore */,
-                SMS_MESSAGE_PERIOD_NOT_SPECIFIED);
+                SMS_MESSAGE_PERIOD_NOT_SPECIFIED,
+                messageId);
     }
 
     /**
@@ -673,25 +749,28 @@
      *  Validity Period(Minimum) -> 5 mins
      *  Validity Period(Maximum) -> 635040 mins(i.e.63 weeks).
      *  Any Other values including negative considered as Invalid Validity Period of the message.
+     * @param messageId An id that uniquely identifies the message requested to be sent.
+     *                 Used for logging and diagnostics purposes. The id may be 0.
      */
 
-    public void sendMultipartTextWithOptions(String callingPackage, String destAddr,
-            String scAddr, List<String> parts, List<PendingIntent> sentIntents,
+    public void sendMultipartTextWithOptions(String callingPackage, String callingAttributionTag,
+            String destAddr, String scAddr, List<String> parts, List<PendingIntent> sentIntents,
             List<PendingIntent> deliveryIntents, boolean persistMessageForNonDefaultSmsApp,
-            int priority, boolean expectMore, int validityPeriod) {
-        if (!mSmsPermissions.checkCallingCanSendText(
-                persistMessageForNonDefaultSmsApp, callingPackage, "Sending SMS message")) {
+            int priority, boolean expectMore, int validityPeriod, long messageId) {
+        if (!mSmsPermissions.checkCallingCanSendText(persistMessageForNonDefaultSmsApp,
+                callingPackage, callingAttributionTag, "Sending SMS message")) {
             returnUnspecifiedFailure(sentIntents);
             return;
         }
         if (Rlog.isLoggable("SMS", Log.VERBOSE)) {
             int i = 0;
             for (String part : parts) {
-                log("sendMultipartTextWithOptions: destAddr=" + destAddr + ", srAddr=" + scAddr +
-                        ", part[" + (i++) + "]=" + part);
+                log("sendMultipartTextWithOptions: destAddr=" + destAddr + ", srAddr=" + scAddr
+                        + ", part[" + (i++) + "]=" + part
+                        + " id: " + messageId);
             }
         }
-
+        notifyIfOutgoingEmergencySms(destAddr);
         destAddr = filterDestAddress(destAddr);
 
         if (parts.size() > 1 && parts.size() < 10 && !SmsMessage.hasEmsSupport()) {
@@ -719,7 +798,7 @@
                 mDispatchersController.sendText(destAddr, scAddr, singlePart, singleSentIntent,
                         singleDeliveryIntent, null /* messageUri */, callingPackage,
                         persistMessageForNonDefaultSmsApp, priority, expectMore, validityPeriod,
-                        false /* isForVvm */);
+                        false /* isForVvm */, messageId);
             }
             return;
         }
@@ -730,7 +809,7 @@
                                       (ArrayList<PendingIntent>) sentIntents,
                                       (ArrayList<PendingIntent>) deliveryIntents,
                                       null, callingPackage, persistMessageForNonDefaultSmsApp,
-                                          priority, expectMore, validityPeriod);
+                                          priority, expectMore, validityPeriod, messageId);
 
     }
 
@@ -786,7 +865,7 @@
         }
 
         // Status bits for this record.  See TS 51.011 10.5.3
-        data[0] = (byte)(status & 7);
+        data[0] = (byte) (status & 0x07);
 
         System.arraycopy(pdu, 0, data, 1, pdu.length);
 
@@ -798,6 +877,54 @@
         return data;
     }
 
+    /**
+     * Gets the SMSC address from (U)SIM.
+     *
+     * @return the SMSC address string, null if failed.
+     */
+    public String getSmscAddressFromIccEf(String callingPackage) {
+        if (!mSmsPermissions.checkCallingOrSelfCanGetSmscAddress(
+                callingPackage, "getSmscAddressFromIccEf")) {
+            return null;
+        }
+        enforceNotOnHandlerThread("getSmscAddressFromIccEf");
+        synchronized (mLock) {
+            mSmsc = null;
+            Message response = mHandler.obtainMessage(EVENT_GET_SMSC_DONE);
+            mPhone.mCi.getSmscAddress(response);
+            try {
+                mLock.wait();
+            } catch (InterruptedException e) {
+                loge("interrupted while trying to read SMSC");
+            }
+        }
+        return mSmsc;
+    }
+
+    /**
+     * Sets the SMSC address on (U)SIM.
+     *
+     * @param smsc the SMSC address string.
+     * @return true for success, false otherwise.
+     */
+    public boolean setSmscAddressOnIccEf(String callingPackage, String smsc) {
+        if (!mSmsPermissions.checkCallingOrSelfCanSetSmscAddress(
+                callingPackage, "setSmscAddressOnIccEf")) {
+            return false;
+        }
+        synchronized (mLock) {
+            mSuccess = false;
+            Message response = mHandler.obtainMessage(EVENT_SET_SMSC_DONE);
+            mPhone.mCi.setSmscAddress(smsc, response);
+            try {
+                mLock.wait();
+            } catch (InterruptedException e) {
+                loge("interrupted while trying to write SMSC");
+            }
+        }
+        return mSuccess;
+    }
+
     public boolean enableCellBroadcast(int messageIdentifier, int ranType) {
         return enableCellBroadcastRange(messageIdentifier, messageIdentifier, ranType);
     }
@@ -807,9 +934,12 @@
     }
 
     public boolean enableCellBroadcastRange(int startMessageId, int endMessageId, int ranType) {
-        if (ranType == SmsManager.CELL_BROADCAST_RAN_TYPE_GSM) {
+        mContext.enforceCallingPermission(android.Manifest.permission.RECEIVE_EMERGENCY_BROADCAST,
+                "enabling cell broadcast range [" + startMessageId + "-" + endMessageId + "]. "
+                        + "ranType=" + ranType);
+        if (ranType == SmsCbMessage.MESSAGE_FORMAT_3GPP) {
             return enableGsmBroadcastRange(startMessageId, endMessageId);
-        } else if (ranType == SmsManager.CELL_BROADCAST_RAN_TYPE_CDMA) {
+        } else if (ranType == SmsCbMessage.MESSAGE_FORMAT_3GPP2) {
             return enableCdmaBroadcastRange(startMessageId, endMessageId);
         } else {
             throw new IllegalArgumentException("Not a supported RAN Type");
@@ -817,9 +947,12 @@
     }
 
     public boolean disableCellBroadcastRange(int startMessageId, int endMessageId, int ranType) {
-        if (ranType == SmsManager.CELL_BROADCAST_RAN_TYPE_GSM ) {
+        mContext.enforceCallingPermission(android.Manifest.permission.RECEIVE_EMERGENCY_BROADCAST,
+                "disabling cell broadcast range [" + startMessageId + "-" + endMessageId
+                        + "]. ranType=" + ranType);
+        if (ranType == SmsCbMessage.MESSAGE_FORMAT_3GPP) {
             return disableGsmBroadcastRange(startMessageId, endMessageId);
-        } else if (ranType == SmsManager.CELL_BROADCAST_RAN_TYPE_CDMA)  {
+        } else if (ranType == SmsCbMessage.MESSAGE_FORMAT_3GPP2)  {
             return disableCdmaBroadcastRange(startMessageId, endMessageId);
         } else {
             throw new IllegalArgumentException("Not a supported RAN Type");
@@ -829,8 +962,7 @@
     @UnsupportedAppUsage
     synchronized public boolean enableGsmBroadcastRange(int startMessageId, int endMessageId) {
 
-        mContext.enforceCallingPermission(
-                "android.permission.RECEIVE_SMS",
+        mContext.enforceCallingPermission(android.Manifest.permission.RECEIVE_EMERGENCY_BROADCAST,
                 "Enabling cell broadcast SMS");
 
         String client = mContext.getPackageManager().getNameForUid(
@@ -860,8 +992,7 @@
     @UnsupportedAppUsage
     synchronized public boolean disableGsmBroadcastRange(int startMessageId, int endMessageId) {
 
-        mContext.enforceCallingPermission(
-                "android.permission.RECEIVE_SMS",
+        mContext.enforceCallingPermission(android.Manifest.permission.RECEIVE_EMERGENCY_BROADCAST,
                 "Disabling cell broadcast SMS");
 
         String client = mContext.getPackageManager().getNameForUid(
@@ -891,8 +1022,7 @@
     @UnsupportedAppUsage
     synchronized public boolean enableCdmaBroadcastRange(int startMessageId, int endMessageId) {
 
-        mContext.enforceCallingPermission(
-                "android.permission.RECEIVE_SMS",
+        mContext.enforceCallingPermission(android.Manifest.permission.RECEIVE_EMERGENCY_BROADCAST,
                 "Enabling cdma broadcast SMS");
 
         String client = mContext.getPackageManager().getNameForUid(
@@ -921,8 +1051,7 @@
     @UnsupportedAppUsage
     synchronized public boolean disableCdmaBroadcastRange(int startMessageId, int endMessageId) {
 
-        mContext.enforceCallingPermission(
-                "android.permission.RECEIVE_SMS",
+        mContext.enforceCallingPermission(android.Manifest.permission.RECEIVE_EMERGENCY_BROADCAST,
                 "Disabling cell broadcast SMS");
 
         String client = mContext.getPackageManager().getNameForUid(
@@ -948,6 +1077,17 @@
         return true;
     }
 
+    /**
+     * Reset all cell broadcast ranges. Previously enabled ranges will become invalid after this.
+     */
+    public void resetAllCellBroadcastRanges() {
+        mContext.enforceCallingPermission(android.Manifest.permission.RECEIVE_EMERGENCY_BROADCAST,
+                "resetAllCellBroadcastRanges");
+        mCdmaBroadcastRangeManager.clearRanges();
+        mCellBroadcastRangeManager.clearRanges();
+        log("Cell broadcast ranges reset.");
+    }
+
     class CellBroadcastRangeManager extends IntRangeManager {
         private ArrayList<SmsBroadcastConfigInfo> mConfigList =
                 new ArrayList<SmsBroadcastConfigInfo>();
@@ -1030,9 +1170,10 @@
 
     @UnsupportedAppUsage
     private boolean setCellBroadcastConfig(SmsBroadcastConfigInfo[] configs) {
-        if (DBG)
+        if (DBG) {
             log("Calling setGsmBroadcastConfig with " + configs.length + " configurations");
-
+        }
+        enforceNotOnHandlerThread("setCellBroadcastConfig");
         synchronized (mLock) {
             Message response = mHandler.obtainMessage(EVENT_SET_BROADCAST_CONFIG_DONE);
 
@@ -1042,7 +1183,7 @@
             try {
                 mLock.wait();
             } catch (InterruptedException e) {
-                log("interrupted while trying to set cell broadcast config");
+                loge("interrupted while trying to set cell broadcast config");
             }
         }
 
@@ -1050,9 +1191,11 @@
     }
 
     private boolean setCellBroadcastActivation(boolean activate) {
-        if (DBG)
+        if (DBG) {
             log("Calling setCellBroadcastActivation(" + activate + ')');
+        }
 
+        enforceNotOnHandlerThread("setCellBroadcastConfig");
         synchronized (mLock) {
             Message response = mHandler.obtainMessage(EVENT_SET_BROADCAST_ACTIVATION_DONE);
 
@@ -1062,7 +1205,7 @@
             try {
                 mLock.wait();
             } catch (InterruptedException e) {
-                log("interrupted while trying to set cell broadcast activation");
+                loge("interrupted while trying to set cell broadcast activation");
             }
         }
 
@@ -1071,9 +1214,11 @@
 
     @UnsupportedAppUsage
     private boolean setCdmaBroadcastConfig(CdmaSmsBroadcastConfigInfo[] configs) {
-        if (DBG)
+        if (DBG) {
             log("Calling setCdmaBroadcastConfig with " + configs.length + " configurations");
+        }
 
+        enforceNotOnHandlerThread("setCdmaBroadcastConfig");
         synchronized (mLock) {
             Message response = mHandler.obtainMessage(EVENT_SET_BROADCAST_CONFIG_DONE);
 
@@ -1083,7 +1228,7 @@
             try {
                 mLock.wait();
             } catch (InterruptedException e) {
-                log("interrupted while trying to set cdma broadcast config");
+                loge("interrupted while trying to set cdma broadcast config");
             }
         }
 
@@ -1091,9 +1236,11 @@
     }
 
     private boolean setCdmaBroadcastActivation(boolean activate) {
-        if (DBG)
+        if (DBG) {
             log("Calling setCdmaBroadcastActivation(" + activate + ")");
+        }
 
+        enforceNotOnHandlerThread("setCdmaBroadcastActivation");
         synchronized (mLock) {
             Message response = mHandler.obtainMessage(EVENT_SET_BROADCAST_ACTIVATION_DONE);
 
@@ -1103,7 +1250,7 @@
             try {
                 mLock.wait();
             } catch (InterruptedException e) {
-                log("interrupted while trying to set cdma broadcast activation");
+                loge("interrupted while trying to set cdma broadcast activation");
             }
         }
 
@@ -1112,7 +1259,15 @@
 
     @UnsupportedAppUsage
     protected void log(String msg) {
-        Log.d(LOG_TAG, "[IccSmsInterfaceManager] " + msg);
+        Rlog.d(LOG_TAG, msg);
+    }
+
+    protected void loge(String msg) {
+        Rlog.e(LOG_TAG, msg);
+    }
+
+    protected void loge(String msg, Throwable e) {
+        Rlog.e(LOG_TAG, msg, e);
     }
 
     @UnsupportedAppUsage
@@ -1125,10 +1280,22 @@
         return mDispatchersController.getImsSmsFormat();
     }
 
+    /**
+     * @deprecated Use {@link #sendStoredText(String, String, Uri, String, PendingIntent,
+     * PendingIntent)} instead
+     */
+    @Deprecated
     @UnsupportedAppUsage
     public void sendStoredText(String callingPkg, Uri messageUri, String scAddress,
             PendingIntent sentIntent, PendingIntent deliveryIntent) {
-        if (!mSmsPermissions.checkCallingCanSendSms(callingPkg, "Sending SMS message")) {
+        sendStoredText(callingPkg, null, messageUri, scAddress, sentIntent, deliveryIntent);
+    }
+
+    public void sendStoredText(String callingPkg, String callingAttributionTag,
+            Uri messageUri, String scAddress, PendingIntent sentIntent,
+            PendingIntent deliveryIntent) {
+        if (!mSmsPermissions.checkCallingCanSendSms(callingPkg, callingAttributionTag,
+                "Sending SMS message")) {
             returnUnspecifiedFailure(sentIntent);
             return;
         }
@@ -1138,49 +1305,64 @@
         }
         final ContentResolver resolver = mContext.getContentResolver();
         if (!isFailedOrDraft(resolver, messageUri)) {
-            Log.e(LOG_TAG, "[IccSmsInterfaceManager]sendStoredText: not FAILED or DRAFT message");
+            loge("sendStoredText: not FAILED or DRAFT message");
             returnUnspecifiedFailure(sentIntent);
             return;
         }
         final String[] textAndAddress = loadTextAndAddress(resolver, messageUri);
         if (textAndAddress == null) {
-            Log.e(LOG_TAG, "[IccSmsInterfaceManager]sendStoredText: can not load text");
+            loge("sendStoredText: can not load text");
             returnUnspecifiedFailure(sentIntent);
             return;
         }
+        notifyIfOutgoingEmergencySms(textAndAddress[1]);
         textAndAddress[1] = filterDestAddress(textAndAddress[1]);
         mDispatchersController.sendText(textAndAddress[1], scAddress, textAndAddress[0],
                 sentIntent, deliveryIntent, messageUri, callingPkg,
                 true /* persistMessageForNonDefaultSmsApp */, SMS_MESSAGE_PRIORITY_NOT_SPECIFIED,
-                false /* expectMore */, SMS_MESSAGE_PERIOD_NOT_SPECIFIED, false /* isForVvm */);
+                false /* expectMore */, SMS_MESSAGE_PERIOD_NOT_SPECIFIED, false /* isForVvm */,
+                0L /* messageId */);
     }
 
+    /**
+     * @deprecated Use {@link #sendStoredMultipartText(String, String, Uri, String, List, List)}
+     * instead
+     */
+    @Deprecated
     @UnsupportedAppUsage
     public void sendStoredMultipartText(String callingPkg, Uri messageUri, String scAddress,
             List<PendingIntent> sentIntents, List<PendingIntent> deliveryIntents) {
-        if (!mSmsPermissions.checkCallingCanSendSms(callingPkg, "Sending SMS message")) {
+        sendStoredMultipartText(callingPkg, null, messageUri, scAddress, sentIntents,
+                deliveryIntents);
+    }
+
+    public void sendStoredMultipartText(String callingPkg,
+            String callingAttributionTag, Uri messageUri, String scAddress,
+            List<PendingIntent> sentIntents, List<PendingIntent> deliveryIntents) {
+        if (!mSmsPermissions.checkCallingCanSendSms(callingPkg, callingAttributionTag,
+                "Sending SMS message")) {
             returnUnspecifiedFailure(sentIntents);
             return;
         }
         final ContentResolver resolver = mContext.getContentResolver();
         if (!isFailedOrDraft(resolver, messageUri)) {
-            Log.e(LOG_TAG, "[IccSmsInterfaceManager]sendStoredMultipartText: "
-                    + "not FAILED or DRAFT message");
+            loge("sendStoredMultipartText: not FAILED or DRAFT message");
             returnUnspecifiedFailure(sentIntents);
             return;
         }
         final String[] textAndAddress = loadTextAndAddress(resolver, messageUri);
         if (textAndAddress == null) {
-            Log.e(LOG_TAG, "[IccSmsInterfaceManager]sendStoredMultipartText: can not load text");
+            loge("sendStoredMultipartText: can not load text");
             returnUnspecifiedFailure(sentIntents);
             return;
         }
         final ArrayList<String> parts = SmsManager.getDefault().divideMessage(textAndAddress[0]);
         if (parts == null || parts.size() < 1) {
-            Log.e(LOG_TAG, "[IccSmsInterfaceManager]sendStoredMultipartText: can not divide text");
+            loge("sendStoredMultipartText: can not divide text");
             returnUnspecifiedFailure(sentIntents);
             return;
         }
+        notifyIfOutgoingEmergencySms(textAndAddress[1]);
         textAndAddress[1] = filterDestAddress(textAndAddress[1]);
 
         if (parts.size() > 1 && parts.size() < 10 && !SmsMessage.hasEmsSupport()) {
@@ -1210,7 +1392,7 @@
                         true  /* persistMessageForNonDefaultSmsApp */,
                         SMS_MESSAGE_PRIORITY_NOT_SPECIFIED,
                         false /* expectMore */, SMS_MESSAGE_PERIOD_NOT_SPECIFIED,
-                        false /* isForVvm */);
+                        false /* isForVvm */, 0L /* messageId */);
             }
             return;
         }
@@ -1226,7 +1408,30 @@
                 true  /* persistMessageForNonDefaultSmsApp */,
                 SMS_MESSAGE_PRIORITY_NOT_SPECIFIED,
                 false /* expectMore */,
-                SMS_MESSAGE_PERIOD_NOT_SPECIFIED);
+                SMS_MESSAGE_PERIOD_NOT_SPECIFIED,
+                0L /* messageId */);
+    }
+
+    public int getSmsCapacityOnIcc() {
+        mContext.enforceCallingOrSelfPermission(
+                android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE,
+                "getSmsCapacityOnIcc");
+
+        int numberOnIcc = 0;
+        if (mPhone.getIccRecordsLoaded()) {
+            final UiccProfile uiccProfile = UiccController.getInstance()
+                    .getUiccProfileForPhone(mPhone.getPhoneId());
+            if(uiccProfile != null) {
+                numberOnIcc = uiccProfile.getIccRecords().getSmsCapacityOnIcc();
+            } else {
+                loge("uiccProfile is null");
+            }
+        } else {
+            loge("getSmsCapacityOnIcc - aborting, no icc card present.");
+        }
+
+        log("getSmsCapacityOnIcc().numberOnIcc = " + numberOnIcc);
+        return numberOnIcc;
     }
 
     private boolean isFailedOrDraft(ContentResolver resolver, Uri messageUri) {
@@ -1248,7 +1453,7 @@
                         || type == Telephony.Sms.MESSAGE_TYPE_FAILED;
             }
         } catch (SQLiteException e) {
-            Log.e(LOG_TAG, "[IccSmsInterfaceManager]isFailedOrDraft: query message type failed", e);
+            loge("isFailedOrDraft: query message type failed", e);
         } finally {
             if (cursor != null) {
                 cursor.close();
@@ -1279,7 +1484,7 @@
                 return new String[]{ cursor.getString(0), cursor.getString(1) };
             }
         } catch (SQLiteException e) {
-            Log.e(LOG_TAG, "[IccSmsInterfaceManager]loadText: query message text failed", e);
+            loge("loadText: query message text failed", e);
         } finally {
             if (cursor != null) {
                 cursor.close();
@@ -1289,6 +1494,14 @@
         return null;
     }
 
+    private void notifyIfOutgoingEmergencySms(String destAddr) {
+        EmergencyNumber emergencyNumber = mPhone.getEmergencyNumberTracker().getEmergencyNumber(
+                destAddr);
+        if (emergencyNumber != null) {
+            mPhone.notifyOutgoingEmergencySms(emergencyNumber);
+        }
+    }
+
     private void returnUnspecifiedFailure(PendingIntent pi) {
         if (pi != null) {
             try {
@@ -1310,12 +1523,13 @@
 
     @UnsupportedAppUsage
     private String filterDestAddress(String destAddr) {
-        String result  = null;
-        result = SmsNumberUtils.filterDestAddr(mPhone, destAddr);
+        String result = SmsNumberUtils.filterDestAddr(mContext, mPhone.getSubId(), destAddr);
         return result != null ? result : destAddr;
     }
 
     public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+        pw.println("Enabled GSM channels: " + mCellBroadcastRangeManager);
+        pw.println("Enabled CDMA channels: " + mCdmaBroadcastRangeManager);
         pw.println("CellBroadcast log:");
         mCellBroadcastLocalLog.dump(fd, pw, args);
         pw.println("SMS dispatcher controller log:");
diff --git a/src/java/com/android/internal/telephony/ImsSmsDispatcher.java b/src/java/com/android/internal/telephony/ImsSmsDispatcher.java
index e54940f..c99b282 100644
--- a/src/java/com/android/internal/telephony/ImsSmsDispatcher.java
+++ b/src/java/com/android/internal/telephony/ImsSmsDispatcher.java
@@ -23,9 +23,9 @@
 import android.provider.Telephony.Sms.Intents;
 import android.telephony.CarrierConfigManager;
 import android.telephony.PhoneNumberUtils;
-import android.telephony.Rlog;
 import android.telephony.ServiceState;
 import android.telephony.ims.ImsReasonInfo;
+import android.telephony.ims.RegistrationManager;
 import android.telephony.ims.aidl.IImsSmsListener;
 import android.telephony.ims.feature.MmTelFeature;
 import android.telephony.ims.stub.ImsRegistrationImplBase;
@@ -33,12 +33,15 @@
 import android.telephony.ims.stub.ImsSmsImplBase.SendStatusResult;
 import android.util.Pair;
 
+import com.android.ims.FeatureConnector;
 import com.android.ims.ImsException;
 import com.android.ims.ImsManager;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.telephony.GsmAlphabet.TextEncodingDetails;
 import com.android.internal.telephony.metrics.TelephonyMetrics;
+import com.android.internal.telephony.uicc.IccUtils;
 import com.android.internal.telephony.util.SMSDispatcherUtil;
+import com.android.telephony.Rlog;
 
 import java.util.HashMap;
 import java.util.Map;
@@ -52,7 +55,7 @@
  */
 public class ImsSmsDispatcher extends SMSDispatcher {
 
-    private static final String TAG = "ImsSmsDispacher";
+    private static final String TAG = "ImsSmsDispatcher";
 
     @VisibleForTesting
     public Map<Integer, SmsTracker> mTrackers = new ConcurrentHashMap<>();
@@ -62,7 +65,7 @@
     private volatile boolean mIsSmsCapable;
     private volatile boolean mIsImsServiceUp;
     private volatile boolean mIsRegistered;
-    private final ImsManager.Connector mImsManagerConnector;
+    private final FeatureConnector<ImsManager> mImsManagerConnector;
     /** Telephony metrics instance for logging metrics event */
     private TelephonyMetrics mMetrics = TelephonyMetrics.getInstance();
 
@@ -70,12 +73,12 @@
      * Listen to the IMS service state change
      *
      */
-    private android.telephony.ims.ImsMmTelManager.RegistrationCallback mRegistrationCallback =
-            new android.telephony.ims.ImsMmTelManager.RegistrationCallback() {
+    private RegistrationManager.RegistrationCallback mRegistrationCallback =
+            new RegistrationManager.RegistrationCallback() {
                 @Override
                 public void onRegistered(
                         @ImsRegistrationImplBase.ImsRegistrationTech int imsRadioTech) {
-                    Rlog.d(TAG, "onImsConnected imsRadioTech=" + imsRadioTech);
+                    logd("onImsConnected imsRadioTech=" + imsRadioTech);
                     synchronized (mLock) {
                         mIsRegistered = true;
                     }
@@ -84,7 +87,7 @@
                 @Override
                 public void onRegistering(
                         @ImsRegistrationImplBase.ImsRegistrationTech int imsRadioTech) {
-                    Rlog.d(TAG, "onImsProgressing imsRadioTech=" + imsRadioTech);
+                    logd("onImsProgressing imsRadioTech=" + imsRadioTech);
                     synchronized (mLock) {
                         mIsRegistered = false;
                     }
@@ -92,7 +95,7 @@
 
                 @Override
                 public void onUnregistered(ImsReasonInfo info) {
-                    Rlog.d(TAG, "onImsDisconnected imsReasonInfo=" + info);
+                    logd("onImsDisconnected imsReasonInfo=" + info);
                     synchronized (mLock) {
                         mIsRegistered = false;
                     }
@@ -114,22 +117,29 @@
     private final IImsSmsListener mImsSmsListener = new IImsSmsListener.Stub() {
         @Override
         public void onSendSmsResult(int token, int messageRef, @SendStatusResult int status,
-                int reason) throws RemoteException {
-            Rlog.d(TAG, "onSendSmsResult token=" + token + " messageRef=" + messageRef
-                    + " status=" + status + " reason=" + reason);
-            mMetrics.writeOnImsServiceSmsSolicitedResponse(mPhone.getPhoneId(), status, reason);
+                int reason, int networkReasonCode) {
+            logd("onSendSmsResult token=" + token + " messageRef=" + messageRef
+                    + " status=" + status + " reason=" + reason + " networkReasonCode="
+                    + networkReasonCode);
+            // TODO integrate networkReasonCode into IMS SMS metrics.
             SmsTracker tracker = mTrackers.get(token);
+            mMetrics.writeOnImsServiceSmsSolicitedResponse(mPhone.getPhoneId(), status, reason,
+                    (tracker != null ? tracker.mMessageId : 0L));
             if (tracker == null) {
                 throw new IllegalArgumentException("Invalid token.");
             }
             tracker.mMessageRef = messageRef;
             switch(status) {
                 case ImsSmsImplBase.SEND_STATUS_OK:
+                    if (tracker.mDeliveryIntent == null) {
+                        // Remove the tracker here if a status report is not requested.
+                        mTrackers.remove(token);
+                    }
                     tracker.onSent(mContext);
                     mPhone.notifySmsSent(tracker.mDestAddress);
                     break;
                 case ImsSmsImplBase.SEND_STATUS_ERROR:
-                    tracker.onFailed(mContext, reason, 0 /* errorCode */);
+                    tracker.onFailed(mContext, reason, networkReasonCode);
                     mTrackers.remove(token);
                     break;
                 case ImsSmsImplBase.SEND_STATUS_ERROR_RETRY:
@@ -145,17 +155,33 @@
         }
 
         @Override
-        public void onSmsStatusReportReceived(int token, int messageRef, String format, byte[] pdu)
+        public void onSmsStatusReportReceived(int token, String format, byte[] pdu)
                 throws RemoteException {
-            Rlog.d(TAG, "Status report received.");
-            SmsTracker tracker = mTrackers.get(token);
+            logd("Status report received.");
+            android.telephony.SmsMessage message =
+                    android.telephony.SmsMessage.createFromPdu(pdu, format);
+            if (message == null || message.mWrappedSmsMessage == null) {
+                throw new RemoteException(
+                        "Status report received with a PDU that could not be parsed.");
+            }
+            int messageRef = message.mWrappedSmsMessage.mMessageRef;
+            SmsTracker tracker = null;
+            int key = 0;
+            for (Map.Entry<Integer, SmsTracker> entry : mTrackers.entrySet()) {
+                if (messageRef == ((SmsTracker) entry.getValue()).mMessageRef) {
+                    tracker = entry.getValue();
+                    key = entry.getKey();
+                    break;
+                }
+            }
+
             if (tracker == null) {
-                throw new RemoteException("Invalid token.");
+                throw new RemoteException("No tracker for messageRef " + messageRef);
             }
             Pair<Boolean, Boolean> result = mSmsDispatchersController.handleSmsStatusReport(
                     tracker, format, pdu);
-            Rlog.d(TAG, "Status report handle result, success: " + result.first +
-                    "complete: " + result.second);
+            logd("Status report handle result, success: " + result.first
+                    + " complete: " + result.second);
             try {
                 getImsManager().acknowledgeSmsReport(
                         token,
@@ -163,21 +189,21 @@
                         result.first ? ImsSmsImplBase.STATUS_REPORT_STATUS_OK
                                 : ImsSmsImplBase.STATUS_REPORT_STATUS_ERROR);
             } catch (ImsException e) {
-                Rlog.e(TAG, "Failed to acknowledgeSmsReport(). Error: "
+                loge("Failed to acknowledgeSmsReport(). Error: "
                         + e.getMessage());
             }
             if (result.second) {
-                mTrackers.remove(token);
+                mTrackers.remove(key);
             }
         }
 
         @Override
         public void onSmsReceived(int token, String format, byte[] pdu) {
-            Rlog.d(TAG, "SMS received.");
+            logd("SMS received.");
             android.telephony.SmsMessage message =
                     android.telephony.SmsMessage.createFromPdu(pdu, format);
             mSmsDispatchersController.injectSmsPdu(message, format, result -> {
-                Rlog.d(TAG, "SMS handled result: " + result);
+                logd("SMS handled result: " + result);
                 int mappedResult;
                 switch (result) {
                     case Intents.RESULT_SMS_HANDLED:
@@ -198,11 +224,11 @@
                         getImsManager().acknowledgeSms(token,
                                 message.mWrappedSmsMessage.mMessageRef, mappedResult);
                     } else {
-                        Rlog.w(TAG, "SMS Received with a PDU that could not be parsed.");
+                        logw("SMS Received with a PDU that could not be parsed.");
                         getImsManager().acknowledgeSms(token, 0, mappedResult);
                     }
                 } catch (ImsException e) {
-                    Rlog.e(TAG, "Failed to acknowledgeSms(). Error: " + e.getMessage());
+                    loge("Failed to acknowledgeSms(). Error: " + e.getMessage());
                 }
             }, true);
         }
@@ -211,11 +237,16 @@
     public ImsSmsDispatcher(Phone phone, SmsDispatchersController smsDispatchersController) {
         super(phone, smsDispatchersController);
 
-        mImsManagerConnector = new ImsManager.Connector(mContext, mPhone.getPhoneId(),
-                new ImsManager.Connector.Listener() {
+        mImsManagerConnector = new FeatureConnector<>(mContext, mPhone.getPhoneId(),
+                new FeatureConnector.Listener<ImsManager>() {
+                    @Override
+                    public ImsManager getFeatureManager() {
+                        return ImsManager.getInstance(mContext, phone.getPhoneId());
+                    }
+
                     @Override
                     public void connectionReady(ImsManager manager) throws ImsException {
-                        Rlog.d(TAG, "ImsManager: connection ready.");
+                        logd("ImsManager: connection ready.");
                         synchronized (mLock) {
                             setListeners();
                             mIsImsServiceUp = true;
@@ -224,12 +255,12 @@
 
                     @Override
                     public void connectionUnavailable() {
-                        Rlog.d(TAG, "ImsManager: connection unavailable.");
+                        logd("ImsManager: connection unavailable.");
                         synchronized (mLock) {
                             mIsImsServiceUp = false;
                         }
                     }
-                });
+                }, "ImsSmsDispatcher");
         mImsManagerConnector.connect();
     }
 
@@ -241,9 +272,9 @@
     }
 
     private boolean isLteService() {
-        return ((mPhone.getServiceState().getRilVoiceRadioTechnology() ==
+        return ((mPhone.getServiceState().getRilDataRadioTechnology() ==
             ServiceState.RIL_RADIO_TECHNOLOGY_LTE) && (mPhone.getServiceState().
-                getState() == ServiceState.STATE_IN_SERVICE));
+                getDataRegistrationState() == ServiceState.STATE_IN_SERVICE));
     }
 
     private boolean isLimitedLteService() {
@@ -259,7 +290,7 @@
         PersistableBundle b;
         boolean eSmsCarrierSupport = false;
         if (!PhoneNumberUtils.isLocalEmergencyNumber(mContext, mPhone.getSubId(), destAddr)) {
-            Rlog.e(TAG, "Emergency Sms is not supported for: " + Rlog.pii(TAG, destAddr));
+            loge("Emergency Sms is not supported for: " + Rlog.pii(TAG, destAddr));
             return false;
         }
 
@@ -268,18 +299,18 @@
             CarrierConfigManager configManager = (CarrierConfigManager) mPhone.getContext()
                     .getSystemService(Context.CARRIER_CONFIG_SERVICE);
             if (configManager == null) {
-                Rlog.e(TAG, "configManager is null");
+                loge("configManager is null");
                 return false;
             }
             b = configManager.getConfigForSubId(getSubId());
             if (b == null) {
-                Rlog.e(TAG, "PersistableBundle is null");
+                loge("PersistableBundle is null");
                 return false;
             }
             eSmsCarrierSupport = b.getBoolean(
                     CarrierConfigManager.KEY_SUPPORT_EMERGENCY_SMS_OVER_IMS_BOOL);
             boolean lteOrLimitedLte = isEmergencySmsPossible();
-            Rlog.i(TAG, "isEmergencySmsSupport emergencySmsCarrierSupport: "
+            logi("isEmergencySmsSupport emergencySmsCarrierSupport: "
                     + eSmsCarrierSupport + " destAddr: " + Rlog.pii(TAG, destAddr)
                     + " mIsImsServiceUp: " + mIsImsServiceUp + " lteOrLimitedLte: "
                     + lteOrLimitedLte);
@@ -292,7 +323,7 @@
 
     public boolean isAvailable() {
         synchronized (mLock) {
-            Rlog.d(TAG, "isAvailable: up=" + mIsImsServiceUp + ", reg= " + mIsRegistered
+            logd("isAvailable: up=" + mIsImsServiceUp + ", reg= " + mIsRegistered
                     + ", cap= " + mIsSmsCapable);
             return mIsImsServiceUp && mIsRegistered && mIsSmsCapable;
         }
@@ -303,7 +334,7 @@
         try {
             return getImsManager().getSmsFormat();
         } catch (ImsException e) {
-            Rlog.e(TAG, "Failed to get sms format. Error: " + e.getMessage());
+            loge("Failed to get sms format. Error: " + e.getMessage());
             return SmsConstants.FORMAT_UNKNOWN;
         }
     }
@@ -337,7 +368,7 @@
 
     @Override
     public void sendSms(SmsTracker tracker) {
-        Rlog.d(TAG, "sendSms: "
+        logd("sendSms: "
                 + " mRetryCount=" + tracker.mRetryCount
                 + " mMessageRef=" + tracker.mMessageRef
                 + " SS=" + mPhone.getServiceState().getState());
@@ -370,13 +401,13 @@
                     token,
                     tracker.mMessageRef,
                     format,
-                    smsc != null ? new String(smsc) : null,
+                    smsc != null ? IccUtils.bytesToHexString(smsc) : null,
                     isRetry,
                     pdu);
             mMetrics.writeImsServiceSendSms(mPhone.getPhoneId(), format,
                     ImsSmsImplBase.SEND_STATUS_OK);
         } catch (ImsException e) {
-            Rlog.e(TAG, "sendSms failed. Falling back to PSTN. Error: " + e.getMessage());
+            loge("sendSms failed. Falling back to PSTN. Error: " + e.getMessage());
             fallbackToPstn(token, tracker);
             mMetrics.writeImsServiceSendSms(mPhone.getPhoneId(), format,
                     ImsSmsImplBase.SEND_STATUS_ERROR_FALLBACK);
@@ -402,4 +433,24 @@
     public IImsSmsListener getSmsListener() {
         return mImsSmsListener;
     }
+
+    private void logd(String s) {
+        Rlog.d(TAG + " [" + getPhoneId(mPhone) + "]", s);
+    }
+
+    private void logi(String s) {
+        Rlog.i(TAG + " [" + getPhoneId(mPhone) + "]", s);
+    }
+
+    private void logw(String s) {
+        Rlog.w(TAG + " [" + getPhoneId(mPhone) + "]", s);
+    }
+
+    private void loge(String s) {
+        Rlog.e(TAG + " [" + getPhoneId(mPhone) + "]", s);
+    }
+
+    private static String getPhoneId(Phone phone) {
+        return (phone != null) ? Integer.toString(phone.getPhoneId()) : "?";
+    }
 }
diff --git a/src/java/com/android/internal/telephony/InboundSmsHandler.java b/src/java/com/android/internal/telephony/InboundSmsHandler.java
index 616681a..63c5c1c 100644
--- a/src/java/com/android/internal/telephony/InboundSmsHandler.java
+++ b/src/java/com/android/internal/telephony/InboundSmsHandler.java
@@ -16,17 +16,21 @@
 
 package com.android.internal.telephony;
 
+import static android.provider.Telephony.Sms.Intents.RESULT_SMS_DATABASE_ERROR;
+import static android.provider.Telephony.Sms.Intents.RESULT_SMS_DISPATCH_FAILURE;
+import static android.provider.Telephony.Sms.Intents.RESULT_SMS_INVALID_URI;
+import static android.provider.Telephony.Sms.Intents.RESULT_SMS_NULL_MESSAGE;
+import static android.provider.Telephony.Sms.Intents.RESULT_SMS_NULL_PDU;
 import static android.service.carrier.CarrierMessagingService.RECEIVE_OPTIONS_SKIP_NOTIFY_WHEN_CREDENTIAL_PROTECTED_STORAGE_UNAVAILABLE;
 import static android.telephony.TelephonyManager.PHONE_TYPE_CDMA;
 
-import android.annotation.UnsupportedAppUsage;
 import android.app.Activity;
-import android.app.ActivityManager;
 import android.app.AppOpsManager;
 import android.app.BroadcastOptions;
 import android.app.Notification;
 import android.app.NotificationManager;
 import android.app.PendingIntent;
+import android.compat.annotation.UnsupportedAppUsage;
 import android.content.BroadcastReceiver;
 import android.content.ComponentName;
 import android.content.ContentResolver;
@@ -36,17 +40,16 @@
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.content.pm.IPackageManager;
-import android.content.pm.UserInfo;
+import android.content.pm.PackageManager;
 import android.database.Cursor;
 import android.database.SQLException;
 import android.net.Uri;
 import android.os.AsyncResult;
 import android.os.Binder;
-import android.os.Build;
 import android.os.Bundle;
-import android.os.IDeviceIdleController;
 import android.os.Message;
 import android.os.PowerManager;
+import android.os.PowerWhitelistManager;
 import android.os.RemoteException;
 import android.os.ServiceManager;
 import android.os.UserHandle;
@@ -55,8 +58,6 @@
 import android.provider.Telephony;
 import android.provider.Telephony.Sms.Intents;
 import android.service.carrier.CarrierMessagingService;
-import android.telephony.Rlog;
-import android.telephony.SmsManager;
 import android.telephony.SmsMessage;
 import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyManager;
@@ -69,13 +70,17 @@
 import com.android.internal.telephony.SmsConstants.MessageClass;
 import com.android.internal.telephony.metrics.TelephonyMetrics;
 import com.android.internal.telephony.util.NotificationChannelController;
+import com.android.internal.telephony.util.TelephonyUtils;
 import com.android.internal.util.HexDump;
+import com.android.internal.util.IndentingPrintWriter;
 import com.android.internal.util.State;
 import com.android.internal.util.StateMachine;
+import com.android.telephony.Rlog;
 
 import java.io.ByteArrayOutputStream;
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
+import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.HashMap;
 import java.util.List;
@@ -229,17 +234,16 @@
     protected Phone mPhone;
 
     @UnsupportedAppUsage
-    protected CellBroadcastHandler mCellBroadcastHandler;
-
-    @UnsupportedAppUsage
     private UserManager mUserManager;
 
     protected TelephonyMetrics mMetrics = TelephonyMetrics.getInstance();
 
     private LocalLog mLocalLog = new LocalLog(64);
+    private LocalLog mCarrierServiceLocalLog = new LocalLog(10);
 
-    @UnsupportedAppUsage
-    IDeviceIdleController mDeviceIdleController;
+    PowerWhitelistManager mPowerWhitelistManager;
+
+    protected CellBroadcastServiceManager mCellBroadcastServiceManager;
 
     // Delete permanently from raw table
     private final int DELETE_PERMANENTLY = 1;
@@ -263,13 +267,12 @@
      * @param storageMonitor the SmsStorageMonitor to check for storage availability
      */
     protected InboundSmsHandler(String name, Context context, SmsStorageMonitor storageMonitor,
-            Phone phone, CellBroadcastHandler cellBroadcastHandler) {
+            Phone phone) {
         super(name);
 
         mContext = context;
         mStorageMonitor = storageMonitor;
         mPhone = phone;
-        mCellBroadcastHandler = cellBroadcastHandler;
         mResolver = context.getContentResolver();
         mWapPush = new WapPushOverSms(context);
 
@@ -282,8 +285,9 @@
         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()
-                .inject(IDeviceIdleController.class.getName()).getIDeviceIdleController();
+        mPowerWhitelistManager =
+                (PowerWhitelistManager) mContext.getSystemService(Context.POWER_WHITELIST_MANAGER);
+        mCellBroadcastServiceManager = new CellBroadcastServiceManager(context, phone);
 
         addState(mDefaultState);
         addState(mStartupState, mDefaultState);
@@ -308,6 +312,7 @@
     @Override
     protected void onQuitting() {
         mWapPush.dispose();
+        mCellBroadcastServiceManager.disable();
 
         while (mWakeLock.isHeld()) {
             mWakeLock.release();
@@ -320,6 +325,40 @@
         return mPhone;
     }
 
+    @Override
+    protected String getWhatToString(int what) {
+        String whatString;
+        switch (what) {
+            case EVENT_NEW_SMS:
+                whatString = "EVENT_NEW_SMS";
+                break;
+            case EVENT_BROADCAST_SMS:
+                whatString = "EVENT_BROADCAST_SMS";
+                break;
+            case EVENT_BROADCAST_COMPLETE:
+                whatString = "EVENT_BROADCAST_COMPLETE";
+                break;
+            case EVENT_RETURN_TO_IDLE:
+                whatString = "EVENT_RETURN_TO_IDLE";
+                break;
+            case EVENT_RELEASE_WAKELOCK:
+                whatString = "EVENT_RELEASE_WAKELOCK";
+                break;
+            case EVENT_START_ACCEPTING_SMS:
+                whatString = "EVENT_START_ACCEPTING_SMS";
+                break;
+            case EVENT_INJECT_SMS:
+                whatString = "EVENT_INJECT_SMS";
+                break;
+            case EVENT_UPDATE_TRACKER:
+                whatString = "EVENT_UPDATE_TRACKER";
+                break;
+            default:
+                whatString = "UNKNOWN EVENT " + what;
+        }
+        return whatString;
+    }
+
     /**
      * This parent state throws an exception (for debug builds) or prints an error for unhandled
      * message types.
@@ -329,12 +368,15 @@
         public boolean processMessage(Message msg) {
             switch (msg.what) {
                 default: {
-                    String errorText = "processMessage: unhandled message type " + msg.what +
-                        " currState=" + getCurrentState().getName();
-                    if (Build.IS_DEBUGGABLE) {
+                    String errorText = "processMessage: unhandled message type "
+                            + getWhatToString(msg.what) + " currState="
+                            + getCurrentState().getName();
+                    if (TelephonyUtils.IS_DEBUGGABLE) {
                         loge("---- Dumping InboundSmsHandler ----");
                         loge("Total records=" + getLogRecCount());
                         for (int i = Math.max(getLogRecSize() - 20, 0); i < getLogRecSize(); i++) {
+                            // getLogRec(i).toString() will call the overridden getWhatToString
+                            // method which has more information
                             loge("Rec[%d]: %s\n" + i + getLogRec(i).toString());
                         }
                         loge("---- Dumped InboundSmsHandler ----");
@@ -357,7 +399,7 @@
     private class StartupState extends State {
         @Override
         public void enter() {
-            if (DBG) log("entering Startup state");
+            if (DBG) log("StartupState.enter: entering StartupState");
             // Set wakelock timeout to 0 during startup, this will ensure that the wakelock is not
             // held if there are no pending messages to be handled.
             setWakeLockTimeout(0);
@@ -365,7 +407,7 @@
 
         @Override
         public boolean processMessage(Message msg) {
-            log("StartupState.processMessage:" + msg.what);
+            log("StartupState.processMessage: processing " + getWhatToString(msg.what));
             switch (msg.what) {
                 case EVENT_NEW_SMS:
                 case EVENT_INJECT_SMS:
@@ -394,20 +436,19 @@
     private class IdleState extends State {
         @Override
         public void enter() {
-            if (DBG) log("entering Idle state");
+            if (DBG) log("IdleState.enter: entering IdleState");
             sendMessageDelayed(EVENT_RELEASE_WAKELOCK, getWakeLockTimeout());
         }
 
         @Override
         public void exit() {
             mWakeLock.acquire();
-            if (DBG) log("acquired wakelock, leaving Idle state");
+            if (DBG) log("IdleState.exit: acquired wakelock, leaving IdleState");
         }
 
         @Override
         public boolean processMessage(Message msg) {
-            log("IdleState.processMessage:" + msg.what);
-            if (DBG) log("Idle state processing message type " + msg.what);
+            if (DBG) log("IdleState.processMessage: processing " + getWhatToString(msg.what));
             switch (msg.what) {
                 case EVENT_NEW_SMS:
                 case EVENT_INJECT_SMS:
@@ -421,9 +462,11 @@
                     if (DBG) {
                         if (mWakeLock.isHeld()) {
                             // this is okay as long as we call release() for every acquire()
-                            log("mWakeLock is still held after release");
+                            log("IdleState.processMessage: EVENT_RELEASE_WAKELOCK: mWakeLock is "
+                                    + "still held after release");
                         } else {
-                            log("mWakeLock released");
+                            log("IdleState.processMessage: EVENT_RELEASE_WAKELOCK: mWakeLock "
+                                    + "released");
                         }
                     }
                     return HANDLED;
@@ -450,17 +493,17 @@
     private class DeliveringState extends State {
         @Override
         public void enter() {
-            if (DBG) log("entering Delivering state");
+            if (DBG) log("DeliveringState.enter: entering DeliveringState");
         }
 
         @Override
         public void exit() {
-            if (DBG) log("leaving Delivering state");
+            if (DBG) log("DeliveringState.exit: leaving DeliveringState");
         }
 
         @Override
         public boolean processMessage(Message msg) {
-            log("DeliveringState.processMessage:" + msg.what);
+            if (DBG) log("DeliveringState.processMessage: processing " + getWhatToString(msg.what));
             switch (msg.what) {
                 case EVENT_NEW_SMS:
                     // handle new SMS from RIL
@@ -485,8 +528,8 @@
                         // processMessagePart() returns false, the state machine will be stuck in
                         // DeliveringState until next message is received. Send message to
                         // transition to idle to avoid that so that wakelock can be released
-                        log("No broadcast sent on processing EVENT_BROADCAST_SMS in Delivering " +
-                                "state. Return to Idle state");
+                        log("DeliveringState.processMessage: EVENT_BROADCAST_SMS: No broadcast "
+                                + "sent. Return to IdleState");
                         sendMessage(EVENT_RETURN_TO_IDLE);
                     }
                     return HANDLED;
@@ -512,9 +555,8 @@
                 case EVENT_BROADCAST_COMPLETE:
                 case EVENT_START_ACCEPTING_SMS:
                 default:
-                    String errorMsg = "Unhandled msg in delivering state, msg.what = " + msg.what;
-                    loge(errorMsg);
-                    mLocalLog.log(errorMsg);
+                    logeWithLocalLog("Unhandled msg in delivering state, msg.what = "
+                            + getWhatToString(msg.what));
                     // let DefaultState handle these unexpected message types
                     return NOT_HANDLED;
             }
@@ -534,12 +576,12 @@
 
         @Override
         public void enter() {
-            if (DBG) log("entering Waiting state");
+            if (DBG) log("WaitingState.enter: entering WaitingState");
         }
 
         @Override
         public void exit() {
-            if (DBG) log("exiting Waiting state");
+            if (DBG) log("WaitingState.exit: leaving WaitingState");
             // Before moving to idle state, set wakelock timeout to WAKE_LOCK_TIMEOUT milliseconds
             // to give any receivers time to take their own wake locks
             setWakeLockTimeout(WAKELOCK_TIMEOUT);
@@ -549,7 +591,7 @@
 
         @Override
         public boolean processMessage(Message msg) {
-            log("WaitingState.processMessage:" + msg.what);
+            if (DBG) log("WaitingState.processMessage: processing " + getWhatToString(msg.what));
             switch (msg.what) {
                 case EVENT_BROADCAST_SMS:
                     // defer until the current broadcast completes
@@ -559,8 +601,7 @@
                                 + " destPort = " + mLastDeliveredSmsTracker.getDestPort()
                                 + " timestamp = " + mLastDeliveredSmsTracker.getTimestamp()
                                 + " currentTimestamp = " + System.currentTimeMillis();
-                        logd(str);
-                        mLocalLog.log(str);
+                        logWithLocalLog(str, mLastDeliveredSmsTracker.getMessageId());
                     }
                     deferMessage(msg);
                     return HANDLED;
@@ -601,7 +642,7 @@
             result = dispatchMessage(sms.mWrappedSmsMessage);
         } catch (RuntimeException ex) {
             loge("Exception dispatching message", ex);
-            result = Intents.RESULT_SMS_GENERIC_ERROR;
+            result = RESULT_SMS_DISPATCH_FAILURE;
         }
 
         // RESULT_OK means that the SMS will be acknowledged by special handling,
@@ -624,14 +665,15 @@
             callback = (SmsDispatchersController.SmsInjectionCallback) ar.userObj;
             SmsMessage sms = (SmsMessage) ar.result;
             if (sms == null) {
-                result = Intents.RESULT_SMS_GENERIC_ERROR;
+                loge("Null injected sms");
+                result = RESULT_SMS_NULL_PDU;
             } else {
                 mLastSmsWasInjected = true;
                 result = dispatchMessage(sms.mWrappedSmsMessage);
             }
         } catch (RuntimeException ex) {
             loge("Exception dispatching message", ex);
-            result = Intents.RESULT_SMS_GENERIC_ERROR;
+            result = RESULT_SMS_DISPATCH_FAILURE;
         }
 
         if (callback != null) {
@@ -651,7 +693,7 @@
         // If sms is null, there was a parsing error.
         if (smsb == null) {
             loge("dispatchSmsMessage: message is null");
-            return Intents.RESULT_SMS_GENERIC_ERROR;
+            return RESULT_SMS_NULL_MESSAGE;
         }
 
         if (mSmsReceiveDisabled) {
@@ -664,14 +706,14 @@
         // onlyCore indicates if the device is in cryptkeeper
         boolean onlyCore = false;
         try {
-            onlyCore = IPackageManager.Stub.asInterface(ServiceManager.getService("package")).
-                    isOnlyCoreApps();
+            onlyCore = IPackageManager.Stub.asInterface(ServiceManager.getService("package"))
+                    .isOnlyCoreApps();
         } catch (RemoteException e) {
         }
         if (onlyCore) {
             // Device is unable to receive SMS in encrypted state
             log("Received a short message in encrypted state. Rejecting.");
-            return Intents.RESULT_SMS_GENERIC_ERROR;
+            return Intents.RESULT_SMS_RECEIVED_WHILE_ENCRYPTED;
         }
 
         int result = dispatchMessageRadioSpecific(smsb);
@@ -718,9 +760,7 @@
             // broadcast SMS_REJECTED_ACTION intent
             Intent intent = new Intent(Intents.SMS_REJECTED_ACTION);
             intent.putExtra("result", result);
-            // Allow registered broadcast receivers to get this intent even
-            // when they are in the background.
-            intent.addFlags(Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
+            intent.putExtra("subId", mPhone.getSubId());
             mContext.sendBroadcast(intent, android.Manifest.permission.RECEIVE_SMS);
         }
         acknowledgeLastIncomingSms(success, result, response);
@@ -756,7 +796,7 @@
             }
             tracker = TelephonyComponentFactory.getInstance()
                     .inject(InboundSmsTracker.class.getName())
-                    .makeInboundSmsTracker(sms.getPdu(),
+                    .makeInboundSmsTracker(mContext, sms.getPdu(),
                     sms.getTimestampMillis(), destPort, is3gpp2(), false,
                     sms.getOriginatingAddress(), sms.getDisplayOriginatingAddress(),
                     sms.getMessageBody(), sms.getMessageClass() == MessageClass.CLASS_0,
@@ -768,7 +808,7 @@
             int destPort = (portAddrs != null ? portAddrs.destPort : -1);
             tracker = TelephonyComponentFactory.getInstance()
                     .inject(InboundSmsTracker.class.getName())
-                    .makeInboundSmsTracker(sms.getPdu(),
+                    .makeInboundSmsTracker(mContext, sms.getPdu(),
                     sms.getTimestampMillis(), destPort, is3gpp2(), sms.getOriginatingAddress(),
                     sms.getDisplayOriginatingAddress(), concatRef.refNumber, concatRef.seqNumber,
                     concatRef.msgCount, false, sms.getMessageBody(),
@@ -787,21 +827,28 @@
      * Helper to add the tracker to the raw table and then send a message to broadcast it, if
      * successful. Returns the SMS intent status to return to the SMSC.
      * @param tracker the tracker to save to the raw table and then deliver
-     * @return {@link Intents#RESULT_SMS_HANDLED} or {@link Intents#RESULT_SMS_GENERIC_ERROR}
-     * or {@link Intents#RESULT_SMS_DUPLICATED}
+     * @return {@link Intents#RESULT_SMS_HANDLED} or one of these errors:<br>
+     * <code>RESULT_SMS_UNSUPPORTED</code><br>
+     * <code>RESULT_SMS_DUPLICATED</code><br>
+     * <code>RESULT_SMS_DISPATCH_FAILURE</code><br>
+     * <code>RESULT_SMS_NULL_PDU</code><br>
+     * <code>RESULT_SMS_NULL_MESSAGE</code><br>
+     * <code>RESULT_SMS_RECEIVED_WHILE_ENCRYPTED</code><br>
+     * <code>RESULT_SMS_DATABASE_ERROR</code><br>
+     * <code>RESULT_SMS_INVALID_URI</code><br>
      */
     protected int addTrackerToRawTableAndSendMessage(InboundSmsTracker tracker, boolean deDup) {
-        switch(addTrackerToRawTable(tracker, deDup)) {
-        case Intents.RESULT_SMS_HANDLED:
-            sendMessage(EVENT_BROADCAST_SMS, tracker);
-            return Intents.RESULT_SMS_HANDLED;
+        int result = addTrackerToRawTable(tracker, deDup);
+        switch(result) {
+            case Intents.RESULT_SMS_HANDLED:
+                sendMessage(EVENT_BROADCAST_SMS, tracker);
+                return Intents.RESULT_SMS_HANDLED;
 
-        case Intents.RESULT_SMS_DUPLICATED:
-            return Intents.RESULT_SMS_HANDLED;
+            case Intents.RESULT_SMS_DUPLICATED:
+                return Intents.RESULT_SMS_HANDLED;
 
-        case Intents.RESULT_SMS_GENERIC_ERROR:
-        default:
-            return Intents.RESULT_SMS_GENERIC_ERROR;
+            default:
+                return result;
         }
     }
 
@@ -824,7 +871,7 @@
         // Do not process when the message count is invalid.
         if (messageCount <= 0) {
             loge("processMessagePart: returning false due to invalid message count "
-                    + messageCount);
+                    + messageCount, tracker.getMessageId());
             return false;
         }
 
@@ -853,6 +900,9 @@
                     // earlier segments. In that case, the broadcast will be sent as soon as all
                     // segments are in the table, and any later EVENT_BROADCAST_SMS messages will
                     // get a row count of 0 and return.
+                    log("processMessagePart: returning false. Only " + cursorCount + " of "
+                            + messageCount + " segments " + " have arrived. refNumber: "
+                            + refNumber, tracker.getMessageId());
                     return false;
                 }
 
@@ -871,7 +921,8 @@
                         loge(String.format(
                                 "processMessagePart: invalid seqNumber = %d, messageCount = %d",
                                 index + tracker.getIndexOffset(),
-                                messageCount));
+                                messageCount),
+                                tracker.getMessageId());
                         continue;
                     }
 
@@ -907,8 +958,11 @@
                                         .get(DISPLAY_ADDRESS_COLUMN)), null);
                     }
                 }
+                log("processMessagePart: all " + messageCount + " segments "
+                        + " received. refNumber: " + refNumber, tracker.getMessageId());
             } catch (SQLException e) {
-                loge("Can't access multipart SMS database", e);
+                loge("processMessagePart: Can't access multipart SMS database, id: "
+                        + tracker.getMessageId(), e);
                 return false;
             } finally {
                 if (cursor != null) {
@@ -917,12 +971,14 @@
             }
         }
 
+        final boolean isWapPush = (destPort == SmsHeader.PORT_WAP_PUSH);
+
         // At this point, all parts of the SMS are received. Update metrics for incoming SMS.
         // WAP-PUSH messages are handled below to also keep track of the result of the processing.
-        String format = (!tracker.is3gpp2() ? SmsConstants.FORMAT_3GPP : SmsConstants.FORMAT_3GPP2);
-        if (destPort != SmsHeader.PORT_WAP_PUSH) {
+        String format = tracker.getFormat();
+        if (!isWapPush) {
             mMetrics.writeIncomingSmsSession(mPhone.getPhoneId(), mLastSmsWasInjected,
-                    format, timestamps, block);
+                    format, timestamps, block, tracker.getMessageId());
         }
 
         // Do not process null pdu(s). Check for that and return false in that case.
@@ -930,46 +986,58 @@
         if (pduList.size() == 0 || pduList.contains(null)) {
             String errorMsg = "processMessagePart: returning false due to "
                     + (pduList.size() == 0 ? "pduList.size() == 0" : "pduList.contains(null)");
-            loge(errorMsg);
-            mLocalLog.log(errorMsg);
+            logeWithLocalLog(errorMsg, tracker.getMessageId());
             return false;
         }
 
-        SmsBroadcastReceiver resultReceiver = new SmsBroadcastReceiver(tracker);
-
-        if (!mUserManager.isUserUnlocked()) {
-            return processMessagePartWithUserLocked(tracker, pdus, destPort, resultReceiver);
-        }
-
-        if (destPort == SmsHeader.PORT_WAP_PUSH) {
-            // Build up the data stream
-            ByteArrayOutputStream output = new ByteArrayOutputStream();
+        ByteArrayOutputStream output = new ByteArrayOutputStream();
+        if (isWapPush) {
             for (byte[] pdu : pdus) {
                 // 3GPP needs to extract the User Data from the PDU; 3GPP2 has already done this
-                if (!tracker.is3gpp2()) {
+                if (format == SmsConstants.FORMAT_3GPP) {
                     SmsMessage msg = SmsMessage.createFromPdu(pdu, SmsConstants.FORMAT_3GPP);
                     if (msg != null) {
                         pdu = msg.getUserData();
                     } else {
-                        loge("processMessagePart: SmsMessage.createFromPdu returned null");
+                        loge("processMessagePart: SmsMessage.createFromPdu returned null",
+                                tracker.getMessageId());
                         mMetrics.writeIncomingWapPush(mPhone.getPhoneId(), mLastSmsWasInjected,
-                                format, timestamps, false);
+                                SmsConstants.FORMAT_3GPP, timestamps, false,
+                                tracker.getMessageId());
                         return false;
                     }
                 }
                 output.write(pdu, 0, pdu.length);
             }
+        }
+
+        SmsBroadcastReceiver resultReceiver = new SmsBroadcastReceiver(tracker);
+
+        if (!mUserManager.isUserUnlocked()) {
+            log("processMessagePart: !isUserUnlocked; calling processMessagePartWithUserLocked. "
+                    + "Port: " + destPort, tracker.getMessageId());
+            return processMessagePartWithUserLocked(
+                    tracker,
+                    (isWapPush ? new byte[][] {output.toByteArray()} : pdus),
+                    destPort,
+                    resultReceiver);
+        }
+
+        if (isWapPush) {
             int result = mWapPush.dispatchWapPdu(output.toByteArray(), resultReceiver,
-                    this, address, tracker.getSubId());
-            if (DBG) log("dispatchWapPdu() returned " + result);
+                    this, address, tracker.getSubId(), tracker.getMessageId());
+            if (DBG) {
+                log("processMessagePart: dispatchWapPdu() returned " + result,
+                        tracker.getMessageId());
+            }
             // Add result of WAP-PUSH into metrics. RESULT_SMS_HANDLED indicates that the WAP-PUSH
             // needs to be ignored, so treating it as a success case.
             if (result == Activity.RESULT_OK || result == Intents.RESULT_SMS_HANDLED) {
                 mMetrics.writeIncomingWapPush(mPhone.getPhoneId(), mLastSmsWasInjected,
-                        format, timestamps, true);
+                        format, timestamps, true, tracker.getMessageId());
             } else {
                 mMetrics.writeIncomingWapPush(mPhone.getPhoneId(), mLastSmsWasInjected,
-                        format, timestamps, false);
+                        format, timestamps, false, tracker.getMessageId());
             }
             // result is Activity.RESULT_OK if an ordered broadcast was sent
             if (result == Activity.RESULT_OK) {
@@ -977,6 +1045,8 @@
             } else {
                 deleteFromRawTable(tracker.getDeleteWhere(), tracker.getDeleteWhereArgs(),
                         MARK_DELETED);
+                loge("processMessagePart: returning false as the ordered broadcast for WAP push "
+                        + "was not sent", tracker.getMessageId());
                 return false;
             }
         }
@@ -984,6 +1054,8 @@
         if (block) {
             deleteFromRawTable(tracker.getDeleteWhere(), tracker.getDeleteWhereArgs(),
                     DELETE_PERMANENTLY);
+            log("processMessagePart: returning false as the phone number is blocked",
+                    tracker.getMessageId());
             return false;
         }
 
@@ -991,8 +1063,8 @@
             pdus, destPort, tracker, resultReceiver, true /* userUnlocked */);
 
         if (!filterInvoked) {
-            dispatchSmsDeliveryIntent(pdus, tracker.getFormat(), destPort, resultReceiver,
-                    tracker.isClass0(), tracker.getSubId());
+            dispatchSmsDeliveryIntent(pdus, format, destPort, resultReceiver,
+                    tracker.isClass0(), tracker.getSubId(), tracker.getMessageId());
         }
 
         return true;
@@ -1009,7 +1081,6 @@
      */
     private boolean processMessagePartWithUserLocked(InboundSmsTracker tracker,
             byte[][] pdus, int destPort, SmsBroadcastReceiver resultReceiver) {
-        log("Credential-encrypted storage not available. Port: " + destPort);
         if (destPort == SmsHeader.PORT_WAP_PUSH && mWapPush.isWapPushForMms(pdus[0], this)) {
             showNewMessageNotification();
             return false;
@@ -1087,17 +1158,27 @@
         CarrierServicesSmsFilterCallback filterCallback =
                 new CarrierServicesSmsFilterCallback(
                         pdus, destPort, tracker.getFormat(), resultReceiver, userUnlocked,
-                        tracker.isClass0(), tracker.getSubId());
+                        tracker.isClass0(), tracker.getSubId(), tracker.getMessageId());
         CarrierServicesSmsFilter carrierServicesFilter = new CarrierServicesSmsFilter(
                 mContext, mPhone, pdus, destPort, tracker.getFormat(),
-                filterCallback, getName(), mLocalLog);
+                filterCallback, getName() + "::CarrierServicesSmsFilter", mCarrierServiceLocalLog,
+                tracker.getMessageId());
         if (carrierServicesFilter.filter()) {
+            log("filterSms: SMS is being handled by carrier service", tracker.getMessageId());
             return true;
         }
 
         if (VisualVoicemailSmsFilter.filter(
                 mContext, pdus, tracker.getFormat(), destPort, tracker.getSubId())) {
-            log("Visual voicemail SMS dropped");
+            logWithLocalLog("filterSms: Visual voicemail SMS dropped", tracker.getMessageId());
+            dropSms(resultReceiver);
+            return true;
+        }
+
+        MissedIncomingCallSmsFilter missedIncomingCallSmsFilter =
+                new MissedIncomingCallSmsFilter(mPhone);
+        if (missedIncomingCallSmsFilter.filter(pdus, tracker.getFormat())) {
+            logWithLocalLog("filterSms: Missed incoming call SMS received", tracker.getMessageId());
             dropSms(resultReceiver);
             return true;
         }
@@ -1115,67 +1196,77 @@
      * @param user user to deliver the intent to
      */
     @UnsupportedAppUsage
-    public void dispatchIntent(Intent intent, String permission, int appOp,
+    public void dispatchIntent(Intent intent, String permission, String appOp,
             Bundle opts, BroadcastReceiver resultReceiver, UserHandle user, int subId) {
         intent.addFlags(Intent.FLAG_RECEIVER_NO_ABORT);
         final String action = intent.getAction();
-        if (Intents.SMS_DELIVER_ACTION.equals(action)
-                || Intents.SMS_RECEIVED_ACTION.equals(action)
-                || Intents.WAP_PUSH_DELIVER_ACTION.equals(action)
-                || Intents.WAP_PUSH_RECEIVED_ACTION.equals(action)) {
-            // Some intents need to be delivered with high priority:
-            // SMS_DELIVER, SMS_RECEIVED, WAP_PUSH_DELIVER, WAP_PUSH_RECEIVED
-            // In some situations, like after boot up or system under load, normal
-            // intent delivery could take a long time.
-            // This flag should only be set for intents for visible, timely operations
-            // which is true for the intents above.
-            intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
-        }
         SubscriptionManager.putPhoneIdAndSubIdExtra(intent, mPhone.getPhoneId());
 
         // override the subId value in the intent with the values from tracker as they can be
         // different, specifically if the message is coming from SmsBroadcastUndelivered
         if (SubscriptionManager.isValidSubscriptionId(subId)) {
-            intent.putExtra(PhoneConstants.SUBSCRIPTION_KEY, subId);
-            intent.putExtra(SubscriptionManager.EXTRA_SUBSCRIPTION_INDEX, subId);
+            SubscriptionManager.putSubscriptionIdExtra(intent, subId);
         }
 
         if (user.equals(UserHandle.ALL)) {
             // Get a list of currently started users.
             int[] users = null;
-            try {
-                users = ActivityManager.getService().getRunningUserIds();
-            } catch (RemoteException re) {
+            final List<UserHandle> userHandles = mUserManager.getUserHandles(false);
+            final List<UserHandle> runningUserHandles = new ArrayList();
+            for (UserHandle handle : userHandles) {
+                if (mUserManager.isUserRunning(handle)) {
+                    runningUserHandles.add(handle);
+                }
             }
-            if (users == null) {
+            if (runningUserHandles.isEmpty()) {
                 users = new int[] {user.getIdentifier()};
+            } else {
+                users = new int[runningUserHandles.size()];
+                for (int i = 0; i < runningUserHandles.size(); i++) {
+                    users[i] = runningUserHandles.get(i).getIdentifier();
+                }
             }
             // Deliver the broadcast only to those running users that are permitted
             // by user policy.
             for (int i = users.length - 1; i >= 0; i--) {
-                UserHandle targetUser = new UserHandle(users[i]);
-                if (users[i] != UserHandle.USER_SYSTEM) {
+                UserHandle targetUser = UserHandle.of(users[i]);
+                if (users[i] != UserHandle.SYSTEM.getIdentifier()) {
                     // Is the user not allowed to use SMS?
-                    if (mUserManager.hasUserRestriction(UserManager.DISALLOW_SMS, targetUser)) {
+                    if (hasUserRestriction(UserManager.DISALLOW_SMS, targetUser)) {
                         continue;
                     }
                     // Skip unknown users and managed profiles as well
-                    UserInfo info = mUserManager.getUserInfo(users[i]);
-                    if (info == null || info.isManagedProfile()) {
+                    if (mUserManager.isManagedProfile(users[i])) {
                         continue;
                     }
                 }
-                // Only pass in the resultReceiver when the USER_SYSTEM is processed.
-                mContext.sendOrderedBroadcastAsUser(intent, targetUser, permission, appOp, opts,
-                        users[i] == UserHandle.USER_SYSTEM ? resultReceiver : null,
-                        getHandler(), Activity.RESULT_OK, null, null);
+                // Only pass in the resultReceiver when the user SYSTEM is processed.
+                try {
+                    mContext.createPackageContextAsUser(mContext.getPackageName(), 0, targetUser)
+                            .sendOrderedBroadcast(intent, Activity.RESULT_OK, permission, appOp,
+                                    users[i] == UserHandle.SYSTEM.getIdentifier()
+                                            ? resultReceiver : null, getHandler(),
+                                    null /* initialData */, null /* initialExtras */, opts);
+                } catch (PackageManager.NameNotFoundException ignored) {
+                }
             }
         } else {
-            mContext.sendOrderedBroadcastAsUser(intent, user, permission, appOp, opts,
-                    resultReceiver, getHandler(), Activity.RESULT_OK, null, null);
+            try {
+                mContext.createPackageContextAsUser(mContext.getPackageName(), 0, user)
+                        .sendOrderedBroadcast(intent, Activity.RESULT_OK, permission, appOp,
+                                resultReceiver, getHandler(), null /* initialData */,
+                                null /* initialExtras */, opts);
+            } catch (PackageManager.NameNotFoundException ignored) {
+            }
         }
     }
 
+    private boolean hasUserRestriction(String restrictionKey, UserHandle userHandle) {
+        final List<UserManager.EnforcingUser> sources = mUserManager
+                .getUserRestrictionSources(restrictionKey, userHandle);
+        return (sources != null && !sources.isEmpty());
+    }
+
     /**
      * Helper for {@link SmsBroadcastUndelivered} to delete an old message in the raw table.
      */
@@ -1209,14 +1300,11 @@
             bopts.setBackgroundActivityStartsAllowed(true);
             bundle = bopts.toBundle();
         }
-        try {
-            long duration = mDeviceIdleController.addPowerSaveTempWhitelistAppForSms(
-                    pkgName, 0, reason);
-            if (bopts == null) bopts = BroadcastOptions.makeBasic();
-            bopts.setTemporaryAppWhitelistDuration(duration);
-            bundle = bopts.toBundle();
-        } catch (RemoteException e) {
-        }
+        long duration = mPowerWhitelistManager.whitelistAppTemporarilyForEvent(
+                pkgName, PowerWhitelistManager.EVENT_SMS, reason);
+        if (bopts == null) bopts = BroadcastOptions.makeBasic();
+        bopts.setTemporaryAppWhitelistDuration(duration);
+        bundle = bopts.toBundle();
 
         return bundle;
     }
@@ -1231,10 +1319,13 @@
      * @param resultReceiver the receiver handling the delivery result
      */
     private void dispatchSmsDeliveryIntent(byte[][] pdus, String format, int destPort,
-            SmsBroadcastReceiver resultReceiver, boolean isClass0, int subId) {
+            SmsBroadcastReceiver resultReceiver, boolean isClass0, int subId, long messageId) {
         Intent intent = new Intent();
         intent.putExtra("pdus", pdus);
         intent.putExtra("format", format);
+        if (messageId != 0L) {
+            intent.putExtra("messageId", messageId);
+        }
 
         if (destPort == -1) {
             intent.setAction(Intents.SMS_DELIVER_ACTION);
@@ -1245,21 +1336,12 @@
             if (componentName != null) {
                 // Deliver SMS message only to this receiver.
                 intent.setComponent(componentName);
-                log("Delivering SMS to: " + componentName.getPackageName() +
-                    " " + componentName.getClassName());
+                logWithLocalLog("Delivering SMS to: " + componentName.getPackageName()
+                        + " " + componentName.getClassName(), messageId);
             } else {
                 intent.setComponent(null);
             }
 
-            // TODO: Validate that this is the right place to store the SMS.
-            if (SmsManager.getDefault().getAutoPersisting()) {
-                final Uri uri = writeInboxMessage(intent);
-                if (uri != null) {
-                    // Pass this to SMS apps so that they know where it is stored
-                    intent.putExtra("uri", uri.toString());
-                }
-            }
-
             // Handle app specific sms messages.
             AppSmsManager appManager = mPhone.getAppSmsManager();
             if (appManager.handleSmsReceivedIntent(intent)) {
@@ -1272,14 +1354,11 @@
             Uri uri = Uri.parse("sms://localhost:" + destPort);
             intent.setData(uri);
             intent.setComponent(null);
-            // Allow registered broadcast receivers to get this intent even
-            // when they are in the background.
-            intent.addFlags(Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
         }
 
         Bundle options = handleSmsWhitelisting(intent.getComponent(), isClass0);
         dispatchIntent(intent, android.Manifest.permission.RECEIVE_SMS,
-                AppOpsManager.OP_RECEIVE_SMS, options, resultReceiver, UserHandle.SYSTEM, subId);
+                AppOpsManager.OPSTR_RECEIVE_SMS, options, resultReceiver, UserHandle.SYSTEM, subId);
     }
 
     /**
@@ -1302,14 +1381,16 @@
             // moveToNext() returns false if no duplicates were found
             if (cursor != null && cursor.moveToNext()) {
                 if (cursor.getCount() != 1) {
-                    loge("Exact match query returned " + cursor.getCount() + " rows");
+                    logeWithLocalLog("checkAndHandleDuplicate: Exact match query returned "
+                            + cursor.getCount() + " rows", tracker.getMessageId());
                 }
 
                 // if the exact matching row is marked deleted, that means this message has already
                 // been received and processed, and can be discarded as dup
                 if (cursor.getInt(
                         PDU_DELETED_FLAG_PROJECTION_INDEX_MAPPING.get(DELETED_FLAG_COLUMN)) == 1) {
-                    loge("Discarding duplicate message segment: " + tracker);
+                    logWithLocalLog("checkAndHandleDuplicate: Discarding duplicate "
+                            + "message/segment: " + tracker, tracker.getMessageId());
                     logDupPduMismatch(cursor, tracker);
                     return true;   // reject message
                 } else {
@@ -1320,7 +1401,8 @@
                         // delete the old message segment permanently
                         deleteFromRawTable(exactMatchQuery.first, exactMatchQuery.second,
                                 DELETE_PERMANENTLY);
-                        loge("Replacing duplicate message: " + tracker);
+                        logWithLocalLog("checkAndHandleDuplicate: Replacing duplicate message: "
+                                + tracker, tracker.getMessageId());
                         logDupPduMismatch(cursor, tracker);
                     }
                 }
@@ -1345,12 +1427,14 @@
                 // moveToNext() returns false if no duplicates were found
                 if (cursor != null && cursor.moveToNext()) {
                     if (cursor.getCount() != 1) {
-                        loge("Inexact match query returned " + cursor.getCount() + " rows");
+                        logeWithLocalLog("checkAndHandleDuplicate: Inexact match query returned "
+                                + cursor.getCount() + " rows", tracker.getMessageId());
                     }
                     // delete the old message segment permanently
                     deleteFromRawTable(inexactMatchQuery.first, inexactMatchQuery.second,
                             DELETE_PERMANENTLY);
-                    loge("Replacing duplicate message segment: " + tracker);
+                    logWithLocalLog("checkAndHandleDuplicate: Replacing duplicate message segment: "
+                            + tracker);
                     logDupPduMismatch(cursor, tracker);
                 }
             } finally {
@@ -1369,8 +1453,9 @@
         byte[] pdu = tracker.getPdu();
         byte[] oldPdu = HexDump.hexStringToByteArray(oldPduString);
         if (!Arrays.equals(oldPdu, tracker.getPdu())) {
-            loge("Warning: dup message PDU of length " + pdu.length
-                    + " is different from existing PDU of length " + oldPdu.length);
+            logeWithLocalLog("Warning: dup message PDU of length " + pdu.length
+                    + " is different from existing PDU of length " + oldPdu.length,
+                    tracker.getMessageId());
         }
     }
 
@@ -1391,11 +1476,12 @@
                     return Intents.RESULT_SMS_DUPLICATED;   // reject message
                 }
             } catch (SQLException e) {
-                loge("Can't access SMS database", e);
-                return Intents.RESULT_SMS_GENERIC_ERROR;    // reject message
+                loge("addTrackerToRawTable: Can't access SMS database, id: "
+                        + tracker.getMessageId(), e);
+                return RESULT_SMS_DATABASE_ERROR;    // reject message
             }
         } else {
-            logd("Skipped message de-duping logic");
+            log("addTrackerToRawTable: Skipped message de-duping logic", tracker.getMessageId());
         }
 
         String address = tracker.getAddress();
@@ -1403,9 +1489,12 @@
         String count = Integer.toString(tracker.getMessageCount());
         ContentValues values = tracker.getContentValues();
 
-        if (VDBG) log("adding content values to raw table: " + values.toString());
+        if (VDBG) {
+            log("addTrackerToRawTable: adding content values to raw table: " + values.toString(),
+                    tracker.getMessageId());
+        }
         Uri newUri = mResolver.insert(sRawUri, values);
-        if (DBG) log("URI of new row -> " + newUri);
+        if (DBG) log("addTrackerToRawTable: URI of new row: " + newUri, tracker.getMessageId());
 
         try {
             long rowId = ContentUris.parseId(newUri);
@@ -1419,8 +1508,9 @@
             }
             return Intents.RESULT_SMS_HANDLED;
         } catch (Exception e) {
-            loge("error parsing URI for new row: " + newUri, e);
-            return Intents.RESULT_SMS_GENERIC_ERROR;
+            loge("addTrackerToRawTable: error parsing URI for new row: " + newUri + " id: "
+                    + tracker.getMessageId(), e);
+            return RESULT_SMS_INVALID_URI;
         }
     }
 
@@ -1460,36 +1550,30 @@
                 intent.setAction(Intents.SMS_RECEIVED_ACTION);
                 // Allow registered broadcast receivers to get this intent even
                 // when they are in the background.
-                intent.addFlags(Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
                 intent.setComponent(null);
                 // All running users will be notified of the received sms.
                 Bundle options = handleSmsWhitelisting(null, false /* bgActivityStartAllowed */);
 
                 dispatchIntent(intent, android.Manifest.permission.RECEIVE_SMS,
-                        AppOpsManager.OP_RECEIVE_SMS,
+                        AppOpsManager.OPSTR_RECEIVE_SMS,
                         options, this, UserHandle.ALL, subId);
             } else if (action.equals(Intents.WAP_PUSH_DELIVER_ACTION)) {
                 // Now dispatch the notification only intent
                 intent.setAction(Intents.WAP_PUSH_RECEIVED_ACTION);
                 intent.setComponent(null);
-                // Allow registered broadcast receivers to get this intent even
-                // when they are in the background.
-                intent.addFlags(Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
                 // Only the primary user will receive notification of incoming mms.
                 // That app will do the actual downloading of the mms.
-                Bundle options = null;
-                try {
-                    long duration = mDeviceIdleController.addPowerSaveTempWhitelistAppForMms(
-                            mContext.getPackageName(), 0, "mms-broadcast");
-                    BroadcastOptions bopts = BroadcastOptions.makeBasic();
-                    bopts.setTemporaryAppWhitelistDuration(duration);
-                    options = bopts.toBundle();
-                } catch (RemoteException e) {
-                }
+                long duration = mPowerWhitelistManager.whitelistAppTemporarilyForEvent(
+                        mContext.getPackageName(),
+                        PowerWhitelistManager.EVENT_MMS,
+                        "mms-broadcast");
+                BroadcastOptions bopts = BroadcastOptions.makeBasic();
+                bopts.setTemporaryAppWhitelistDuration(duration);
+                Bundle options = bopts.toBundle();
 
                 String mimeType = intent.getType();
                 dispatchIntent(intent, WapPushOverSms.getPermissionForType(mimeType),
-                        WapPushOverSms.getAppOpsPermissionForIntent(mimeType), options, this,
+                        WapPushOverSms.getAppOpsStringPermissionForIntent(mimeType), options, this,
                         UserHandle.SYSTEM, subId);
             } else {
                 // Now that the intents have been deleted we can clean up the PDU data.
@@ -1533,10 +1617,11 @@
         private final boolean mUserUnlocked;
         private final boolean mIsClass0;
         private final int mSubId;
+        private final long mMessageId;
 
         CarrierServicesSmsFilterCallback(byte[][] pdus, int destPort, String smsFormat,
                 SmsBroadcastReceiver smsBroadcastReceiver,  boolean userUnlocked,
-                boolean isClass0, int subId) {
+                boolean isClass0, int subId, long messageId) {
             mPdus = pdus;
             mDestPort = destPort;
             mSmsFormat = smsFormat;
@@ -1544,22 +1629,24 @@
             mUserUnlocked = userUnlocked;
             mIsClass0 = isClass0;
             mSubId = subId;
+            mMessageId = messageId;
         }
 
         @Override
         public void onFilterComplete(int result) {
-            logv("onFilterComplete: result is " + result);
+            log("onFilterComplete: result is " + result, mMessageId);
             if ((result & CarrierMessagingService.RECEIVE_OPTIONS_DROP) == 0) {
                 if (VisualVoicemailSmsFilter.filter(mContext, mPdus,
                         mSmsFormat, mDestPort, mSubId)) {
-                    log("Visual voicemail SMS dropped");
+                    logWithLocalLog("Visual voicemail SMS dropped", mMessageId);
                     dropSms(mSmsBroadcastReceiver);
                     return;
                 }
 
                 if (mUserUnlocked) {
                     dispatchSmsDeliveryIntent(
-                            mPdus, mSmsFormat, mDestPort, mSmsBroadcastReceiver, mIsClass0, mSubId);
+                            mPdus, mSmsFormat, mDestPort, mSmsBroadcastReceiver, mIsClass0, mSubId,
+                            mMessageId);
                 } else {
                     // Don't do anything further, leave the message in the raw table if the
                     // credential-encrypted storage is still locked and show the new message
@@ -1592,6 +1679,44 @@
     }
 
     /**
+     * Log with debug level in logcat and LocalLog
+     * @param logMsg msg to log
+     */
+    protected void logWithLocalLog(String logMsg) {
+        log(logMsg);
+        mLocalLog.log(logMsg);
+    }
+
+    /**
+     * Log with debug level in logcat and LocalLog
+     * @param logMsg msg to log
+     * @param id unique message id
+     */
+    protected void logWithLocalLog(String logMsg, long id) {
+        log(logMsg, id);
+        mLocalLog.log(logMsg + ", id: " + id);
+    }
+
+    /**
+     * Log with error level in logcat and LocalLog
+     * @param logMsg msg to log
+     */
+    protected void logeWithLocalLog(String logMsg) {
+        loge(logMsg);
+        mLocalLog.log(logMsg);
+    }
+
+    /**
+     * Log with error level in logcat and LocalLog
+     * @param logMsg msg to log
+     * @param id unique message id
+     */
+    protected void logeWithLocalLog(String logMsg, long id) {
+        loge(logMsg, id);
+        mLocalLog.log(logMsg + ", id: " + id);
+    }
+
+    /**
      * Log with debug level.
      * @param s the string to log
      */
@@ -1602,6 +1727,15 @@
     }
 
     /**
+     * Log with debug level.
+     * @param s the string to log
+     * @param id unique message id
+     */
+    protected void log(String s, long id) {
+        log(s + ", id: " + id);
+    }
+
+    /**
      * Log with error level.
      * @param s the string to log
      */
@@ -1614,6 +1748,15 @@
     /**
      * Log with error level.
      * @param s the string to log
+     * @param id unique message id
+     */
+    protected void loge(String s, long id) {
+        loge(s + ", id: " + id);
+    }
+
+    /**
+     * Log with error level.
+     * @param s the string to log
      * @param e is a Throwable which logs additional information.
      */
     @Override
@@ -1700,12 +1843,23 @@
     }
 
     @Override
-    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+    public void dump(FileDescriptor fd, PrintWriter printWriter, String[] args) {
+        IndentingPrintWriter pw = new IndentingPrintWriter(printWriter, "  ");
+        pw.println(getName() + " extends StateMachine:");
+        pw.increaseIndent();
         super.dump(fd, pw, args);
-        if (mCellBroadcastHandler != null) {
-            mCellBroadcastHandler.dump(fd, pw, args);
+        if (mCellBroadcastServiceManager != null) {
+            mCellBroadcastServiceManager.dump(fd, pw, args);
         }
+        pw.println("mLocalLog:");
+        pw.increaseIndent();
         mLocalLog.dump(fd, pw, args);
+        pw.decreaseIndent();
+        pw.println("mCarrierServiceLocalLog:");
+        pw.increaseIndent();
+        mCarrierServiceLocalLog.dump(fd, pw, args);
+        pw.decreaseIndent();
+        pw.decreaseIndent();
     }
 
     // Some providers send formfeeds in their messages. Convert those formfeeds to newlines.
@@ -1749,6 +1903,32 @@
         }
     }
 
+    protected byte[] decodeHexString(String hexString) {
+        if (hexString == null || hexString.length() % 2 == 1) {
+            return null;
+        }
+        byte[] bytes = new byte[hexString.length() / 2];
+        for (int i = 0; i < hexString.length(); i += 2) {
+            bytes[i / 2] = hexToByte(hexString.substring(i, i + 2));
+        }
+        return bytes;
+    }
+
+    private byte hexToByte(String hexString) {
+        int firstDigit = toDigit(hexString.charAt(0));
+        int secondDigit = toDigit(hexString.charAt(1));
+        return (byte) ((firstDigit << 4) + secondDigit);
+    }
+
+    private int toDigit(char hexChar) {
+        int digit = Character.digit(hexChar, 16);
+        if (digit == -1) {
+            return 0;
+        }
+        return digit;
+    }
+
+
     /**
      * Registers the broadcast receiver to launch the default SMS app when the user clicks the
      * new message notification.
@@ -1758,4 +1938,29 @@
         userFilter.addAction(ACTION_OPEN_SMS_APP);
         context.registerReceiver(new NewMessageNotificationActionReceiver(), userFilter);
     }
+
+    protected abstract class CbTestBroadcastReceiver extends BroadcastReceiver {
+
+        protected abstract void handleTestAction(Intent intent);
+
+        protected final String mTestAction;
+
+        public CbTestBroadcastReceiver(String testAction) {
+            mTestAction = testAction;
+        }
+
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            logd("Received test intent action=" + intent.getAction());
+            if (intent.getAction().equals(mTestAction)) {
+                // Return early if phone_id is explicilty included and does not match mPhone.
+                // If phone_id extra is not included, continue.
+                int phoneId = mPhone.getPhoneId();
+                if (intent.getIntExtra("phone_id", phoneId) != phoneId) {
+                    return;
+                }
+                handleTestAction(intent);
+            }
+        }
+    }
 }
diff --git a/src/java/com/android/internal/telephony/InboundSmsTracker.java b/src/java/com/android/internal/telephony/InboundSmsTracker.java
index 4d9971e..8a1f6bd 100644
--- a/src/java/com/android/internal/telephony/InboundSmsTracker.java
+++ b/src/java/com/android/internal/telephony/InboundSmsTracker.java
@@ -16,14 +16,23 @@
 
 package com.android.internal.telephony;
 
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
 import android.content.ContentValues;
+import android.content.Context;
 import android.database.Cursor;
+import android.telephony.SubscriptionManager;
+import android.telephony.TelephonyManager;
+import android.text.TextUtils;
 import android.util.Pair;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.HexDump;
+import com.android.telephony.Rlog;
 
+import java.io.UnsupportedEncodingException;
+import java.nio.ByteBuffer;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
 import java.util.Arrays;
 import java.util.Date;
 
@@ -33,6 +42,8 @@
  * outgoing messages.
  */
 public class InboundSmsTracker {
+    // Need 8 bytes to get a message id as a long.
+    private static final int NUM_OF_BYTES_HASH_VALUE_FOR_MESSAGE_ID = 8;
 
     // Fields for single and multi-part messages
     private final byte[] mPdu;
@@ -43,6 +54,7 @@
     private final String mMessageBody;
     private final boolean mIsClass0;
     private final int mSubId;
+    private final long mMessageId;
 
     // Fields for concatenating multi-part SMS messages
     private final String mAddress;
@@ -95,6 +107,7 @@
     /**
      * Create a tracker for a single-part SMS.
      *
+     * @param context
      * @param pdu the message PDU
      * @param timestamp the message timestamp
      * @param destPort the destination port
@@ -104,9 +117,9 @@
      * @param displayAddress email address if this message was from an email gateway, otherwise same
      *                       as originating address
      */
-    public InboundSmsTracker(byte[] pdu, long timestamp, int destPort, boolean is3gpp2,
-            boolean is3gpp2WapPdu, String address, String displayAddress, String messageBody,
-            boolean isClass0, int subId) {
+    public InboundSmsTracker(Context context, byte[] pdu, long timestamp, int destPort,
+            boolean is3gpp2, boolean is3gpp2WapPdu, String address, String displayAddress,
+            String messageBody, boolean isClass0, int subId) {
         mPdu = pdu;
         mTimestamp = timestamp;
         mDestPort = destPort;
@@ -121,6 +134,7 @@
         mSequenceNumber = getIndexOffset();     // 0 or 1, depending on type
         mMessageCount = 1;
         mSubId = subId;
+        mMessageId = createMessageId(context, timestamp, subId);
     }
 
     /**
@@ -142,10 +156,10 @@
      * @param messageCount the total number of segments
      * @param is3gpp2WapPdu true for 3GPP2 format WAP PDU; false otherwise
      */
-    public InboundSmsTracker(byte[] pdu, long timestamp, int destPort, boolean is3gpp2,
-            String address, String displayAddress, int referenceNumber, int sequenceNumber,
-            int messageCount, boolean is3gpp2WapPdu, String messageBody, boolean isClass0,
-            int subId) {
+    public InboundSmsTracker(Context context, byte[] pdu, long timestamp, int destPort,
+             boolean is3gpp2, String address, String displayAddress, int referenceNumber,
+             int sequenceNumber, int messageCount, boolean is3gpp2WapPdu, String messageBody,
+             boolean isClass0, int subId) {
         mPdu = pdu;
         mTimestamp = timestamp;
         mDestPort = destPort;
@@ -161,6 +175,7 @@
         mSequenceNumber = sequenceNumber;
         mMessageCount = messageCount;
         mSubId = subId;
+        mMessageId = createMessageId(context, timestamp, subId);
     }
 
     /**
@@ -168,7 +183,7 @@
      * Since this constructor is used only for recovery during startup, the Dispatcher is null.
      * @param cursor a Cursor pointing to the row to construct this SmsTracker for
      */
-    public InboundSmsTracker(Cursor cursor, boolean isCurrentFormat3gpp2) {
+    public InboundSmsTracker(Context context, Cursor cursor, boolean isCurrentFormat3gpp2) {
         mPdu = HexDump.hexStringToByteArray(cursor.getString(InboundSmsHandler.PDU_COLUMN));
 
         // TODO: add a column to raw db to store this
@@ -224,6 +239,7 @@
                     Integer.toString(mReferenceNumber), Integer.toString(mMessageCount)};
         }
         mMessageBody = cursor.getString(InboundSmsHandler.MESSAGE_BODY_COLUMN);
+        mMessageId = createMessageId(context, mTimestamp, mSubId);
     }
 
     public ContentValues getContentValues() {
@@ -301,6 +317,8 @@
             builder.append(") deleteArgs=(").append(Arrays.toString(mDeleteWhereArgs));
             builder.append(')');
         }
+        builder.append(" id=");
+        builder.append(mMessageId);
         builder.append('}');
         return builder.toString();
     }
@@ -396,6 +414,43 @@
         return where + " AND (" + whereDestPort + ")";
     }
 
+    private static long createMessageId(Context context, long timestamp, int subId) {
+        int slotId = SubscriptionManager.getSlotIndex(subId);
+        TelephonyManager telephonyManager =
+                (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
+        String deviceId = telephonyManager.getImei(slotId);
+        if (TextUtils.isEmpty(deviceId)) {
+            return 0L;
+        }
+        String messagePrint = deviceId + timestamp;
+        return getShaValue(messagePrint);
+    }
+
+    private static long getShaValue(String messagePrint) {
+        try {
+            return ByteBuffer.wrap(getShaBytes(messagePrint,
+                    NUM_OF_BYTES_HASH_VALUE_FOR_MESSAGE_ID)).getLong();
+        } catch (final NoSuchAlgorithmException | UnsupportedEncodingException e) {
+            Rlog.e("InboundSmsTracker", "Exception while getting SHA value for message",
+                    e);
+        }
+        return 0L;
+    }
+
+    private static byte[] getShaBytes(String messagePrint, int maxNumOfBytes)
+            throws NoSuchAlgorithmException, UnsupportedEncodingException {
+        MessageDigest messageDigest = MessageDigest.getInstance("SHA-1");
+        messageDigest.reset();
+        messageDigest.update(messagePrint.getBytes("UTF-8"));
+        byte[] hashResult = messageDigest.digest();
+        if (hashResult.length >= maxNumOfBytes) {
+            byte[] truncatedHashResult = new byte[maxNumOfBytes];
+            System.arraycopy(hashResult, 0, truncatedHashResult, 0, maxNumOfBytes);
+            return truncatedHashResult;
+        }
+        return hashResult;
+    }
+
     /**
      * Sequence numbers for concatenated messages start at 1. The exception is CDMA WAP PDU
      * messages, which use a 0-based index.
@@ -437,4 +492,8 @@
     public String[] getDeleteWhereArgs() {
         return mDeleteWhereArgs;
     }
+
+    public long getMessageId() {
+        return mMessageId;
+    }
 }
diff --git a/src/java/com/android/internal/telephony/IntRangeManager.java b/src/java/com/android/internal/telephony/IntRangeManager.java
index b82a20e..9cf5627 100644
--- a/src/java/com/android/internal/telephony/IntRangeManager.java
+++ b/src/java/com/android/internal/telephony/IntRangeManager.java
@@ -16,9 +16,11 @@
 
 package com.android.internal.telephony;
 
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
+
 import java.util.ArrayList;
 import java.util.Iterator;
+import java.util.stream.Collectors;
 
 /**
  * Clients can enable reception of SMS-CB messages for specific ranges of
@@ -140,8 +142,12 @@
             }
             mClients.add(range);    // append to end of list
         }
-    }
 
+        @Override
+        public String toString() {
+            return "[" + mStartId + "-" + mEndId + "]";
+        }
+    }
     /**
      * The message id range for a single client.
      */
@@ -183,6 +189,13 @@
     protected IntRangeManager() {}
 
     /**
+     * Clear all the ranges.
+     */
+    public synchronized void clearRanges() {
+        mRanges.clear();
+    }
+
+    /**
      * Enable a range for the specified client and update ranges
      * if necessary. If {@link #finishUpdate} returns failure,
      * false is returned and the range is not added.
@@ -668,4 +681,9 @@
      * @return true if successful, false otherwise
      */
     protected abstract boolean finishUpdate();
+
+    @Override
+    public String toString() {
+        return mRanges.stream().map(IntRange::toString).collect(Collectors.joining(","));
+    }
 }
diff --git a/src/java/com/android/internal/telephony/IntentBroadcaster.java b/src/java/com/android/internal/telephony/IntentBroadcaster.java
index 7528819..5ba4929 100644
--- a/src/java/com/android/internal/telephony/IntentBroadcaster.java
+++ b/src/java/com/android/internal/telephony/IntentBroadcaster.java
@@ -16,7 +16,6 @@
 
 package com.android.internal.telephony;
 
-import android.app.ActivityManager;
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
@@ -51,12 +50,12 @@
                     while (iterator.hasNext()) {
                         Map.Entry pair = (Map.Entry) iterator.next();
                         Intent i = (Intent) pair.getValue();
-                        i.putExtra(TelephonyIntents.EXTRA_REBROADCAST_ON_UNLOCK, true);
+                        i.putExtra(Intent.EXTRA_REBROADCAST_ON_UNLOCK, true);
                         iterator.remove();
                         logd("Rebroadcasting intent " + i.getAction() + " "
                                 + i.getStringExtra(IccCardConstants.INTENT_KEY_ICC_STATE)
                                 + " for slotId " + pair.getKey());
-                        ActivityManager.broadcastStickyIntent(i, UserHandle.USER_ALL);
+                        context.sendStickyBroadcastAsUser(i, UserHandle.ALL);
                     }
                 }
             }
@@ -86,13 +85,13 @@
      * Wrapper for ActivityManager.broadcastStickyIntent() that also stores intent to be rebroadcast
      * on USER_UNLOCKED
      */
-    public void broadcastStickyIntent(Intent intent, int slotId) {
+    public void broadcastStickyIntent(Context context, Intent intent, int phoneId) {
         logd("Broadcasting and adding intent for rebroadcast: " + intent.getAction() + " "
                 + intent.getStringExtra(IccCardConstants.INTENT_KEY_ICC_STATE)
-                + " for slotId " + slotId);
+                + " for phoneId " + phoneId);
         synchronized (mRebroadcastIntents) {
-            ActivityManager.broadcastStickyIntent(intent, UserHandle.USER_ALL);
-            mRebroadcastIntents.put(slotId, intent);
+            context.sendStickyBroadcastAsUser(intent, UserHandle.ALL);
+            mRebroadcastIntents.put(phoneId, intent);
         }
     }
 
diff --git a/src/java/com/android/internal/telephony/LocaleTracker.java b/src/java/com/android/internal/telephony/LocaleTracker.java
index 13d7dfb..89778e6 100755
--- a/src/java/com/android/internal/telephony/LocaleTracker.java
+++ b/src/java/com/android/internal/telephony/LocaleTracker.java
@@ -25,16 +25,13 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
-import android.net.wifi.WifiManager;
+import android.content.SharedPreferences;
 import android.os.AsyncResult;
 import android.os.Handler;
 import android.os.Looper;
 import android.os.Message;
+import android.sysprop.TelephonyProperties;
 import android.telephony.CellInfo;
-import android.telephony.CellInfoGsm;
-import android.telephony.CellInfoLte;
-import android.telephony.CellInfoWcdma;
-import android.telephony.Rlog;
 import android.telephony.ServiceState;
 import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyManager;
@@ -42,12 +39,17 @@
 import android.util.LocalLog;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.telephony.MccTable.MccMnc;
+import com.android.internal.telephony.util.TelephonyUtils;
 import com.android.internal.util.IndentingPrintWriter;
+import com.android.telephony.Rlog;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
+import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.List;
+import java.util.Locale;
 import java.util.Map;
 import java.util.Objects;
 
@@ -56,7 +58,6 @@
  */
 public class LocaleTracker extends Handler {
     private static final boolean DBG = true;
-    private static final String TAG = LocaleTracker.class.getSimpleName();
 
     /** Event for getting cell info from the modem */
     private static final int EVENT_REQUEST_CELL_INFO = 1;
@@ -76,6 +77,31 @@
     /** Event to fire if the operator from ServiceState is considered truly lost */
     private static final int EVENT_OPERATOR_LOST = 6;
 
+    /** Event to override the current locale */
+    private static final int EVENT_OVERRIDE_LOCALE = 7;
+
+    /**
+     * The broadcast intent action to override the current country for testing purposes
+     *
+     * <p> This broadcast is not effective on user build.
+     *
+     * <p>Example: To override the current country <code>
+     * adb shell am broadcast -a com.android.internal.telephony.action.COUNTRY_OVERRIDE
+     * --es country us </code>
+     *
+     * <p> To remove the override <code>
+     * adb shell am broadcast -a com.android.internal.telephony.action.COUNTRY_OVERRIDE
+     * --ez reset true</code>
+     */
+    private static final String ACTION_COUNTRY_OVERRIDE =
+            "com.android.internal.telephony.action.COUNTRY_OVERRIDE";
+
+    /** The extra for country override */
+    private static final String EXTRA_COUNTRY = "country";
+
+    /** The extra for country override reset */
+    private static final String EXTRA_RESET = "reset";
+
     // Todo: Read this from Settings.
     /** The minimum delay to get cell info from the modem */
     private static final long CELL_INFO_MIN_DELAY_MS = 2 * SECOND_IN_MILLIS;
@@ -98,6 +124,12 @@
     /** The maximum fail count to prevent delay time overflow */
     private static final int MAX_FAIL_COUNT = 30;
 
+    /** The last known country iso */
+    private static final String LAST_KNOWN_COUNTRY_ISO_SHARED_PREFS_KEY =
+            "last_known_country_iso";
+
+    private String mTag;
+
     private final Phone mPhone;
 
     private final NitzStateMachine mNitzStateMachine;
@@ -120,6 +152,10 @@
     @Nullable
     private String mCurrentCountryIso;
 
+    /** The country override for testing purposes */
+    @Nullable
+    private String mCountryOverride;
+
     /** Current service state. Must be one of ServiceState.STATE_XXX. */
     private int mLastServiceState = ServiceState.STATE_POWER_OFF;
 
@@ -138,6 +174,13 @@
                             intent.getIntExtra(TelephonyManager.EXTRA_SIM_STATE,
                                     TelephonyManager.SIM_STATE_UNKNOWN), 0).sendToTarget();
                 }
+            } else if (ACTION_COUNTRY_OVERRIDE.equals(intent.getAction())) {
+                String countryOverride = intent.getStringExtra(EXTRA_COUNTRY);
+                boolean reset = intent.getBooleanExtra(EXTRA_RESET, false);
+                if (reset) countryOverride = null;
+                log("Received country override: " + countryOverride);
+                // countryOverride null to reset the override.
+                obtainMessage(EVENT_OVERRIDE_LOCALE, countryOverride).sendToTarget();
             }
         }
     };
@@ -181,6 +224,11 @@
                 updateTrackingStatus();
                 break;
 
+            case EVENT_OVERRIDE_LOCALE:
+                mCountryOverride = (String) msg.obj;
+                updateLocale();
+                break;
+
             default:
                 throw new IllegalStateException("Unexpected message arrives. msg = " + msg.what);
         }
@@ -198,15 +246,30 @@
         mPhone = phone;
         mNitzStateMachine = nitzStateMachine;
         mSimState = TelephonyManager.SIM_STATE_UNKNOWN;
+        mTag = LocaleTracker.class.getSimpleName() + "-" + mPhone.getPhoneId();
 
         final IntentFilter filter = new IntentFilter();
         filter.addAction(TelephonyManager.ACTION_SIM_CARD_STATE_CHANGED);
+        if (TelephonyUtils.IS_DEBUGGABLE) {
+            filter.addAction(ACTION_COUNTRY_OVERRIDE);
+        }
         mPhone.getContext().registerReceiver(mBroadcastReceiver, filter);
 
         mPhone.registerForServiceStateChanged(this, EVENT_SERVICE_STATE_CHANGED, null);
         mPhone.registerForCellInfo(this, EVENT_UNSOL_CELL_INFO, null);
     }
 
+    private @NonNull String getCarrierCountry() {
+        // The locale from the "ro.carrier" system property or R.array.carrier_properties.
+        // This will be overwritten by the Locale from the SIM language settings (EF-PL, EF-LI)
+        // if applicable.
+        final Locale carrierLocale = mPhone.getLocaleFromCarrierProperties();
+        if (carrierLocale != null && !TextUtils.isEmpty(carrierLocale.getCountry())) {
+            return carrierLocale.getCountry();
+        }
+        return "";
+    }
+
     /**
      * Get the device's current country.
      *
@@ -226,23 +289,16 @@
     private String getMccFromCellInfo() {
         String selectedMcc = null;
         if (mCellInfoList != null) {
-            Map<String, Integer> countryCodeMap = new HashMap<>();
+            Map<String, Integer> mccMap = new HashMap<>();
             int maxCount = 0;
             for (CellInfo cellInfo : mCellInfoList) {
-                String mcc = null;
-                if (cellInfo instanceof CellInfoGsm) {
-                    mcc = ((CellInfoGsm) cellInfo).getCellIdentity().getMccString();
-                } else if (cellInfo instanceof CellInfoLte) {
-                    mcc = ((CellInfoLte) cellInfo).getCellIdentity().getMccString();
-                } else if (cellInfo instanceof CellInfoWcdma) {
-                    mcc = ((CellInfoWcdma) cellInfo).getCellIdentity().getMccString();
-                }
+                String mcc = cellInfo.getCellIdentity().getMccString();
                 if (mcc != null) {
                     int count = 1;
-                    if (countryCodeMap.containsKey(mcc)) {
-                        count = countryCodeMap.get(mcc) + 1;
+                    if (mccMap.containsKey(mcc)) {
+                        count = mccMap.get(mcc) + 1;
                     }
-                    countryCodeMap.put(mcc, count);
+                    mccMap.put(mcc, count);
                     // This is unlikely, but if MCC from cell info looks different, we choose the
                     // MCC that occurs most.
                     if (count > maxCount) {
@@ -256,6 +312,44 @@
     }
 
     /**
+     * Get the most frequent MCC + MNC combination with the specified MCC using cell tower
+     * information. If no one combination is more frequent than any other an arbitrary MCC + MNC is
+     * returned with the matching MCC. The MNC value returned can be null if it is not provided by
+     * the cell tower information.
+     *
+     * @param mccToMatch the MCC to match
+     * @return a matching {@link MccMnc}. Null if the information is not available.
+     */
+    @Nullable
+    private MccMnc getMccMncFromCellInfo(@NonNull String mccToMatch) {
+        MccMnc selectedMccMnc = null;
+        if (mCellInfoList != null) {
+            Map<MccMnc, Integer> mccMncMap = new HashMap<>();
+            int maxCount = 0;
+            for (CellInfo cellInfo : mCellInfoList) {
+                String mcc = cellInfo.getCellIdentity().getMccString();
+                if (Objects.equals(mcc, mccToMatch)) {
+                    String mnc = cellInfo.getCellIdentity().getMncString();
+                    MccMnc mccMnc = new MccMnc(mcc, mnc);
+                    int count = 1;
+                    if (mccMncMap.containsKey(mccMnc)) {
+                        count = mccMncMap.get(mccMnc) + 1;
+                    }
+                    mccMncMap.put(mccMnc, count);
+                    // We keep track of the MCC+MNC combination that occurs most frequently, if
+                    // there is one. A null MNC is treated like any other distinct MCC+MNC
+                    // combination.
+                    if (count > maxCount) {
+                        maxCount = count;
+                        selectedMccMnc = mccMnc;
+                    }
+                }
+            }
+        }
+        return selectedMccMnc;
+    }
+
+    /**
      * Called when SIM card state changed. Only when we absolutely know the SIM is absent, we get
      * cell info from the network. Other SIM states like NOT_READY might be just a transitioning
      * state.
@@ -398,63 +492,113 @@
      */
     private synchronized void updateLocale() {
         // If MCC is available from network service state, use it first.
-        String mcc = null;
-        String countryIso = "";
+        String countryIso = getCarrierCountry();
+        String countryIsoDebugInfo = "getCarrierCountry()";
+
+        // For time zone detection we want the best geographical match we can get, which may differ
+        // from the countryIso.
+        String timeZoneCountryIso = null;
+        String timeZoneCountryIsoDebugInfo = null;
+
         if (!TextUtils.isEmpty(mOperatorNumeric)) {
-            try {
-                mcc = mOperatorNumeric.substring(0, 3);
-                countryIso = MccTable.countryCodeForMcc(mcc);
-            } catch (StringIndexOutOfBoundsException ex) {
-                loge("updateLocale: Can't get country from operator numeric. mcc = "
-                        + mcc + ". ex=" + ex);
+            MccMnc mccMnc = MccMnc.fromOperatorNumeric(mOperatorNumeric);
+            if (mccMnc != null) {
+                countryIso = MccTable.countryCodeForMcc(mccMnc.mcc);
+                countryIsoDebugInfo = "OperatorNumeric(" + mOperatorNumeric
+                        + "): MccTable.countryCodeForMcc(\"" + mccMnc.mcc + "\")";
+                timeZoneCountryIso = MccTable.geoCountryCodeForMccMnc(mccMnc);
+                timeZoneCountryIsoDebugInfo =
+                        "OperatorNumeric: MccTable.geoCountryCodeForMccMnc(" + mccMnc + ")";
+            } else {
+                loge("updateLocale: Can't get country from operator numeric. mOperatorNumeric = "
+                        + mOperatorNumeric);
             }
         }
 
         // If for any reason we can't get country from operator numeric, try to get it from cell
         // info.
         if (TextUtils.isEmpty(countryIso)) {
-            mcc = getMccFromCellInfo();
-            countryIso = MccTable.countryCodeForMcc(mcc);
+            String mcc = getMccFromCellInfo();
+            if (mcc != null) {
+                countryIso = MccTable.countryCodeForMcc(mcc);
+                countryIsoDebugInfo = "CellInfo: MccTable.countryCodeForMcc(\"" + mcc + "\")";
+
+                MccMnc mccMnc = getMccMncFromCellInfo(mcc);
+                if (mccMnc != null) {
+                    timeZoneCountryIso = MccTable.geoCountryCodeForMccMnc(mccMnc);
+                    timeZoneCountryIsoDebugInfo =
+                            "CellInfo: MccTable.geoCountryCodeForMccMnc(" + mccMnc + ")";
+                }
+            }
         }
 
-        log("updateLocale: mcc = " + mcc + ", country = " + countryIso);
-        boolean countryChanged = false;
+        if (mCountryOverride != null) {
+            countryIso = mCountryOverride;
+            countryIsoDebugInfo = "mCountryOverride = \"" + mCountryOverride + "\"";
+            timeZoneCountryIso = countryIso;
+            timeZoneCountryIsoDebugInfo = countryIsoDebugInfo;
+        }
+
+        if (mLastServiceState == ServiceState.STATE_POWER_OFF) {
+            countryIso = "";
+        }
+
+        log("updateLocale: countryIso = " + countryIso
+                + ", countryIsoDebugInfo = " + countryIsoDebugInfo);
         if (!Objects.equals(countryIso, mCurrentCountryIso)) {
-            String msg = "updateLocale: Change the current country to \"" + countryIso
-                    + "\", mcc = " + mcc + ", mCellInfoList = " + mCellInfoList;
+            String msg = "updateLocale: Change the current country to \"" + countryIso + "\""
+                    + ", countryIsoDebugInfo = " + countryIsoDebugInfo
+                    + ", mCellInfoList = " + mCellInfoList;
             log(msg);
             mLocalLog.log(msg);
             mCurrentCountryIso = countryIso;
 
-            TelephonyManager.setTelephonyProperty(mPhone.getPhoneId(),
-                    TelephonyProperties.PROPERTY_OPERATOR_ISO_COUNTRY, mCurrentCountryIso);
-
-            // Set the country code for wifi. This sets allowed wifi channels based on the
-            // country of the carrier we see. If we can't see any, reset to 0 so we don't
-            // broadcast on forbidden channels.
-            WifiManager wifiManager = (WifiManager) mPhone.getContext()
-                    .getSystemService(Context.WIFI_SERVICE);
-            if (wifiManager != null) {
-                wifiManager.setCountryCode(countryIso);
-            } else {
-                msg = "Wifi manager is not available.";
-                log(msg);
-                mLocalLog.log(msg);
+            // Update the last known country ISO
+            if (!TextUtils.isEmpty(mCurrentCountryIso)) {
+                updateLastKnownCountryIso(mCurrentCountryIso);
             }
 
+            int phoneId = mPhone.getPhoneId();
+            if (SubscriptionManager.isValidPhoneId(phoneId)) {
+                List<String> newProp = new ArrayList<>(
+                        TelephonyProperties.operator_iso_country());
+                while (newProp.size() <= phoneId) newProp.add(null);
+                newProp.set(phoneId, mCurrentCountryIso);
+                TelephonyProperties.operator_iso_country(newProp);
+            }
 
             Intent intent = new Intent(TelephonyManager.ACTION_NETWORK_COUNTRY_CHANGED);
             intent.putExtra(TelephonyManager.EXTRA_NETWORK_COUNTRY, countryIso);
+            intent.putExtra(TelephonyManager.EXTRA_LAST_KNOWN_NETWORK_COUNTRY,
+                    getLastKnownCountryIso());
             SubscriptionManager.putPhoneIdAndSubIdExtra(intent, mPhone.getPhoneId());
             mPhone.getContext().sendBroadcast(intent);
-
-            countryChanged = true;
         }
 
-        if (TextUtils.isEmpty(countryIso)) {
-            mNitzStateMachine.handleNetworkCountryCodeUnavailable();
+        // Pass the geographical country information to the telephony time zone detection code.
+
+        boolean isTestMcc = false;
+        if (!TextUtils.isEmpty(mOperatorNumeric)) {
+            // For a test cell (MCC 001), the NitzStateMachine requires handleCountryDetected("") in
+            // order to pass compliance tests. http://b/142840879
+            if (mOperatorNumeric.startsWith("001")) {
+                isTestMcc = true;
+                timeZoneCountryIso = "";
+                timeZoneCountryIsoDebugInfo = "Test cell: " + mOperatorNumeric;
+            }
+        }
+        if (timeZoneCountryIso == null) {
+            // After this timeZoneCountryIso may still be null.
+            timeZoneCountryIso = countryIso;
+            timeZoneCountryIsoDebugInfo = "Defaulted: " + countryIsoDebugInfo;
+        }
+        log("updateLocale: timeZoneCountryIso = " + timeZoneCountryIso
+                + ", timeZoneCountryIsoDebugInfo = " + timeZoneCountryIsoDebugInfo);
+
+        if (TextUtils.isEmpty(timeZoneCountryIso) && !isTestMcc) {
+            mNitzStateMachine.handleCountryUnavailable();
         } else {
-            mNitzStateMachine.handleNetworkCountryCodeSet(countryChanged);
+            mNitzStateMachine.handleCountryDetected(timeZoneCountryIso);
         }
     }
 
@@ -463,12 +607,36 @@
         return mIsTracking;
     }
 
+    private void updateLastKnownCountryIso(String countryIso) {
+        if (!TextUtils.isEmpty(countryIso)) {
+            final SharedPreferences prefs = mPhone.getContext().getSharedPreferences(
+                    LAST_KNOWN_COUNTRY_ISO_SHARED_PREFS_KEY, Context.MODE_PRIVATE);
+            final SharedPreferences.Editor editor = prefs.edit();
+            editor.putString(LAST_KNOWN_COUNTRY_ISO_SHARED_PREFS_KEY, countryIso);
+            editor.commit();
+            log("update country iso in sharedPrefs " + countryIso);
+        }
+    }
+
+    /**
+     *  Return the last known country ISO before device is not camping on a network
+     *  (e.g. Airplane Mode)
+     *
+     *  @return The device's last known country ISO.
+     */
+    @NonNull
+    public String getLastKnownCountryIso() {
+        final SharedPreferences prefs = mPhone.getContext().getSharedPreferences(
+                LAST_KNOWN_COUNTRY_ISO_SHARED_PREFS_KEY, Context.MODE_PRIVATE);
+        return prefs.getString(LAST_KNOWN_COUNTRY_ISO_SHARED_PREFS_KEY, "");
+    }
+
     private void log(String msg) {
-        Rlog.d(TAG, msg);
+        Rlog.d(mTag, msg);
     }
 
     private void loge(String msg) {
-        Rlog.e(TAG, msg);
+        Rlog.e(mTag, msg);
     }
 
     /**
@@ -480,7 +648,7 @@
      */
     public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
         final IndentingPrintWriter ipw = new IndentingPrintWriter(pw, "  ");
-        pw.println("LocaleTracker:");
+        pw.println("LocaleTracker-" + mPhone.getPhoneId() + ":");
         ipw.increaseIndent();
         ipw.println("mIsTracking = " + mIsTracking);
         ipw.println("mOperatorNumeric = " + mOperatorNumeric);
diff --git a/src/java/com/android/internal/telephony/MccTable.java b/src/java/com/android/internal/telephony/MccTable.java
index d515c58..9d0c8c6 100644
--- a/src/java/com/android/internal/telephony/MccTable.java
+++ b/src/java/com/android/internal/telephony/MccTable.java
@@ -16,29 +16,29 @@
 
 package com.android.internal.telephony;
 
-import android.annotation.UnsupportedAppUsage;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.app.ActivityManager;
+import android.compat.annotation.UnsupportedAppUsage;
 import android.content.Context;
-import android.content.res.Configuration;
 import android.os.Build;
-import android.os.RemoteException;
 import android.os.SystemProperties;
 import android.text.TextUtils;
-import android.util.Slog;
+import android.timezone.TelephonyLookup;
+import android.timezone.TelephonyNetwork;
+import android.timezone.TelephonyNetworkFinder;
 
-import com.android.internal.app.LocaleStore;
-import com.android.internal.app.LocaleStore.LocaleInfo;
-
-import libcore.icu.ICU;
-import libcore.timezone.TimeZoneFinder;
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.telephony.util.TelephonyUtils;
+import com.android.telephony.Rlog;
 
 import java.util.ArrayList;
-import java.util.Arrays;
 import java.util.Collections;
 import java.util.HashMap;
-import java.util.List;
 import java.util.Locale;
 import java.util.Map;
+import java.util.Objects;
 
 /**
  * Mobile Country Code
@@ -48,19 +48,30 @@
 public final class MccTable {
     static final String LOG_TAG = "MccTable";
 
+    @GuardedBy("MccTable.class")
+    private static TelephonyNetworkFinder sTelephonyNetworkFinder;
+
     static ArrayList<MccEntry> sTable;
 
-    static class MccEntry implements Comparable<MccEntry> {
+    /**
+     * Container class for mcc and iso. This class implements compareTo so that it can be sorted
+     * by mcc.
+     */
+    public static class MccEntry implements Comparable<MccEntry> {
         final int mMcc;
-        @UnsupportedAppUsage
-        final String mIso;
+        @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.Q,
+                publicAlternatives = "There is no alternative for {@code MccTable.MccEntry.mIso}, "
+                        + "but it was included in hidden APIs due to a static analysis false "
+                        + "positive and has been made greylist-max-q. Please file a bug if you "
+                        + "still require this API.")
+        public final String mIso;
         final int mSmallestDigitsMnc;
 
-        MccEntry(int mnc, String iso, int smallestDigitsMCC) {
+        MccEntry(int mcc, String iso, int smallestDigitsMCC) {
             if (iso == null) {
                 throw new NullPointerException();
             }
-            mMcc = mnc;
+            mMcc = mcc;
             mIso = iso;
             mSmallestDigitsMnc = smallestDigitsMCC;
         }
@@ -71,8 +82,83 @@
         }
     }
 
-    @UnsupportedAppUsage
-    private static MccEntry entryForMcc(int mcc) {
+    /**
+     * A combination of MCC and MNC. The MNC is optional and may be null.
+     *
+     * @hide
+     */
+    @VisibleForTesting
+    public static class MccMnc {
+        @NonNull
+        public final String mcc;
+
+        @Nullable
+        public final String mnc;
+
+        /**
+         * Splits the supplied String in two: the first three characters are treated as the MCC,
+         * the remaining characters are treated as the MNC.
+         */
+        @Nullable
+        public static MccMnc fromOperatorNumeric(@NonNull String operatorNumeric) {
+            Objects.requireNonNull(operatorNumeric);
+            String mcc;
+            try {
+                mcc = operatorNumeric.substring(0, 3);
+            } catch (StringIndexOutOfBoundsException e) {
+                return null;
+            }
+
+            String mnc;
+            try {
+                mnc = operatorNumeric.substring(3);
+            } catch (StringIndexOutOfBoundsException e) {
+                mnc = null;
+            }
+            return new MccMnc(mcc, mnc);
+        }
+
+        /**
+         * Creates an MccMnc using the supplied values.
+         */
+        public MccMnc(@NonNull String mcc, @Nullable String mnc) {
+            this.mcc = Objects.requireNonNull(mcc);
+            this.mnc = mnc;
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            if (this == o) {
+                return true;
+            }
+            if (o == null || getClass() != o.getClass()) {
+                return false;
+            }
+            MccMnc mccMnc = (MccMnc) o;
+            return mcc.equals(mccMnc.mcc)
+                    && Objects.equals(mnc, mccMnc.mnc);
+        }
+
+        @Override
+        public int hashCode() {
+            return Objects.hash(mcc, mnc);
+        }
+
+        @Override
+        public String toString() {
+            return "MccMnc{"
+                    + "mcc='" + mcc + '\''
+                    + ", mnc='" + mnc + '\''
+                    + '}';
+        }
+    }
+
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.Q,
+            publicAlternatives = "There is no alternative for {@code MccTable.entryForMcc}, "
+                    + "but it was included in hidden APIs due to a static analysis false positive "
+                    + "and has been made greylist-max-q. Please file a bug if you still require "
+                    + "this API.")
+    public static MccEntry entryForMcc(int mcc) {
         MccEntry m = new MccEntry(mcc, "", 0);
 
         int index = Collections.binarySearch(sTable, m);
@@ -85,26 +171,11 @@
     }
 
     /**
-     * Returns a default time zone ID for the given MCC.
-     * @param mcc Mobile Country Code
-     * @return default TimeZone ID, or null if not specified
+     * Given a GSM Mobile Country Code, returns a lower-case ISO 3166 alpha-2 country code if
+     * available. Returns empty string if unavailable.
      */
     @UnsupportedAppUsage
-    public static String defaultTimeZoneForMcc(int mcc) {
-        MccEntry entry = entryForMcc(mcc);
-        if (entry == null) {
-            return null;
-        }
-        final String lowerCaseCountryCode = entry.mIso;
-        return TimeZoneFinder.getInstance().lookupDefaultTimeZoneIdByCountry(lowerCaseCountryCode);
-    }
-
-    /**
-     * Given a GSM Mobile Country Code, returns
-     * an ISO two-character country code if available.
-     * Returns "" if unavailable.
-     */
-    @UnsupportedAppUsage
+    @NonNull
     public static String countryCodeForMcc(int mcc) {
         MccEntry entry = entryForMcc(mcc);
 
@@ -116,11 +187,11 @@
     }
 
     /**
-     * Given a GSM Mobile Country Code, returns
-     * an ISO two-character country code if available.
-     * Returns empty string if unavailable.
+     * Given a GSM Mobile Country Code, returns a lower-case ISO 3166 alpha-2 country code if
+     * available. Returns empty string if unavailable.
      */
-    public static String countryCodeForMcc(String mcc) {
+    @NonNull
+    public static String countryCodeForMcc(@NonNull String mcc) {
         try {
             return countryCodeForMcc(Integer.parseInt(mcc));
         } catch (NumberFormatException ex) {
@@ -129,39 +200,61 @@
     }
 
     /**
-     * Given a GSM Mobile Country Code, returns
-     * an ISO 2-3 character language code if available.
-     * Returns null if unavailable.
+     * Given a combination of MCC and MNC, returns a lower case ISO 3166 alpha-2 country code for
+     * the device's geographical location.
+     *
+     * <p>This can give a better geographical result than {@link #countryCodeForMcc(String)}
+     * (which provides the official "which country is the MCC assigned to?" answer) for cases when
+     * MNC is also available: Sometimes an MCC can be used by multiple countries and the MNC can
+     * help distinguish, or the MCC assigned to a country isn't used for geopolitical reasons.
+     * When the geographical country is needed  (e.g. time zone detection) this version can provide
+     * more pragmatic results than the official MCC-only answer. This method falls back to calling
+     * {@link #countryCodeForMcc(int)} if no special MCC+MNC cases are found.
+     * Returns empty string if no code can be determined.
      */
-    @UnsupportedAppUsage
-    public static String defaultLanguageForMcc(int mcc) {
-        MccEntry entry = entryForMcc(mcc);
-        if (entry == null) {
-            Slog.d(LOG_TAG, "defaultLanguageForMcc(" + mcc + "): no country for mcc");
+    @NonNull
+    public static String geoCountryCodeForMccMnc(@NonNull MccMnc mccMnc) {
+        String countryCode = null;
+        if (mccMnc.mnc != null) {
+            countryCode = countryCodeForMccMncNoFallback(mccMnc);
+        }
+        if (TextUtils.isEmpty(countryCode)) {
+            // Try the MCC-only fallback.
+            countryCode = MccTable.countryCodeForMcc(mccMnc.mcc);
+        }
+        return countryCode;
+    }
+
+    @Nullable
+    private static String countryCodeForMccMncNoFallback(MccMnc mccMnc) {
+        synchronized (MccTable.class) {
+            if (sTelephonyNetworkFinder == null) {
+                sTelephonyNetworkFinder = TelephonyLookup.getInstance().getTelephonyNetworkFinder();
+            }
+        }
+        if (sTelephonyNetworkFinder == null) {
+            // This should not happen under normal circumstances, only when the data is missing.
             return null;
         }
-
-        final String country = entry.mIso;
-
-        // Choose English as the default language for India.
-        if ("in".equals(country)) {
-            return "en";
+        TelephonyNetwork network =
+                sTelephonyNetworkFinder.findNetworkByMccMnc(mccMnc.mcc, mccMnc.mnc);
+        if (network == null) {
+            return null;
         }
-
-        // Ask CLDR for the language this country uses...
-        Locale likelyLocale = ICU.addLikelySubtags(new Locale("und", country));
-        String likelyLanguage = likelyLocale.getLanguage();
-        Slog.d(LOG_TAG, "defaultLanguageForMcc(" + mcc + "): country " + country + " uses " +
-               likelyLanguage);
-        return likelyLanguage;
+        return network.getCountryIsoCode();
     }
 
+
     /**
      * Given a GSM Mobile Country Code, returns
      * the smallest number of digits that M if available.
      * Returns 2 if unavailable.
      */
-    @UnsupportedAppUsage
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.Q,
+            publicAlternatives = "There is no alternative for {@code MccTable"
+                    + ".smallestDigitsMccForMnc}, but it was included in hidden APIs due to a "
+                    + "static analysis false positive and has been made greylist-max-q. Please "
+                    + "file a bug if you still require this API.")
     public static int smallestDigitsMccForMnc(int mcc) {
         MccEntry entry = entryForMcc(mcc);
 
@@ -179,49 +272,37 @@
      * @param mccmnc truncated imsi with just the MCC and MNC - MNC assumed to be from 4th to end
      */
     public static void updateMccMncConfiguration(Context context, String mccmnc) {
-        Slog.d(LOG_TAG, "updateMccMncConfiguration mccmnc='" + mccmnc);
+        Rlog.d(LOG_TAG, "updateMccMncConfiguration mccmnc='" + mccmnc);
 
-        if (Build.IS_DEBUGGABLE) {
+        if (TelephonyUtils.IS_DEBUGGABLE) {
             String overrideMcc = SystemProperties.get("persist.sys.override_mcc");
             if (!TextUtils.isEmpty(overrideMcc)) {
                 mccmnc = overrideMcc;
-                Slog.d(LOG_TAG, "updateMccMncConfiguration overriding mccmnc='" + mccmnc + "'");
+                Rlog.d(LOG_TAG, "updateMccMncConfiguration overriding mccmnc='" + mccmnc + "'");
             }
         }
 
         if (!TextUtils.isEmpty(mccmnc)) {
-            int mcc, mnc;
-
+            int mccInt;
             try {
-                mcc = Integer.parseInt(mccmnc.substring(0, 3));
-                mnc = Integer.parseInt(mccmnc.substring(3));
+                mccInt = Integer.parseInt(mccmnc.substring(0, 3));
             } catch (NumberFormatException | StringIndexOutOfBoundsException ex) {
-                Slog.e(LOG_TAG, "Error parsing IMSI: " + mccmnc + ". ex=" + ex);
+                Rlog.e(LOG_TAG, "Error parsing mccmnc: " + mccmnc + ". ex=" + ex);
                 return;
             }
-
-            Slog.d(LOG_TAG, "updateMccMncConfiguration: mcc=" + mcc + ", mnc=" + mnc);
-            if (mcc != 0) {
-                setTimezoneFromMccIfNeeded(context, mcc);
-            }
-
-            try {
-                Configuration config = new Configuration();
-                boolean updateConfig = false;
-                if (mcc != 0) {
-                    config.mcc = mcc;
-                    config.mnc = mnc == 0 ? Configuration.MNC_ZERO : mnc;
-                    updateConfig = true;
-                }
-
-                if (updateConfig) {
-                    Slog.d(LOG_TAG, "updateMccMncConfiguration updateConfig config=" + config);
-                    ActivityManager.getService().updateConfiguration(config);
+            if (mccInt != 0) {
+                ActivityManager activityManager = (ActivityManager) context.getSystemService(
+                        Context.ACTIVITY_SERVICE);
+                if (!activityManager.updateMccMncConfiguration(
+                        mccmnc.substring(0, 3), mccmnc.substring(3))) {
+                    Rlog.d(LOG_TAG, "updateMccMncConfiguration: update mccmnc="
+                            + mccmnc + " failure");
                 } else {
-                    Slog.d(LOG_TAG, "updateMccMncConfiguration nothing to update");
+                    Rlog.d(LOG_TAG, "updateMccMncConfiguration: update mccmnc="
+                            + mccmnc + " success");
                 }
-            } catch (RemoteException e) {
-                Slog.e(LOG_TAG, "Can't update configuration", e);
+            } else {
+                Rlog.d(LOG_TAG, "updateMccMncConfiguration nothing to update");
             }
         }
     }
@@ -229,433 +310,275 @@
     /**
      * Maps a given locale to a fallback locale that approximates it. This is a hack.
      */
-    private static final Map<Locale, Locale> FALLBACKS = new HashMap<Locale, Locale>();
+    public static final Map<Locale, Locale> FALLBACKS = new HashMap<Locale, Locale>();
 
     static {
         // If we have English (without a country) explicitly prioritize en_US. http://b/28998094
         FALLBACKS.put(Locale.ENGLISH, Locale.US);
     }
 
-    /**
-     * Finds a suitable locale among {@code candidates} to use as the fallback locale for
-     * {@code target}. This looks through the list of {@link #FALLBACKS}, and follows the chain
-     * until a locale in {@code candidates} is found.
-     * This function assumes that {@code target} is not in {@code candidates}.
-     *
-     * TODO: This should really follow the CLDR chain of parent locales! That might be a bit
-     * of a problem because we don't really have an en-001 locale on android.
-     *
-     * @return The fallback locale or {@code null} if there is no suitable fallback defined in the
-     *         lookup.
-     */
-    private static Locale lookupFallback(Locale target, List<Locale> candidates) {
-        Locale fallback = target;
-        while ((fallback = FALLBACKS.get(fallback)) != null) {
-            if (candidates.contains(fallback)) {
-                return fallback;
-            }
-        }
-
-        return null;
-    }
-
-    /**
-     * Return Locale for the language and country or null if no good match.
-     *
-     * @param context Context to act on.
-     * @param language Two character language code desired
-     * @param country Two character country code desired
-     *
-     * @return Locale or null if no appropriate value
-     */
-    @UnsupportedAppUsage
-    private static Locale getLocaleForLanguageCountry(Context context, String language,
-            String country) {
-        if (language == null) {
-            Slog.d(LOG_TAG, "getLocaleForLanguageCountry: skipping no language");
-            return null; // no match possible
-        }
-        if (country == null) {
-            country = ""; // The Locale constructor throws if passed null.
-        }
-
-        final Locale target = new Locale(language, country);
-        try {
-            String[] localeArray = context.getAssets().getLocales();
-            List<String> locales = new ArrayList<>(Arrays.asList(localeArray));
-
-            // Even in developer mode, you don't want the pseudolocales.
-            locales.remove("ar-XB");
-            locales.remove("en-XA");
-
-            List<Locale> languageMatches = new ArrayList<>();
-            for (String locale : locales) {
-                final Locale l = Locale.forLanguageTag(locale.replace('_', '-'));
-
-                // Only consider locales with both language and country.
-                if (l == null || "und".equals(l.getLanguage()) ||
-                        l.getLanguage().isEmpty() || l.getCountry().isEmpty()) {
-                    continue;
-                }
-                if (l.getLanguage().equals(target.getLanguage())) {
-                    // If we got a perfect match, we're done.
-                    if (l.getCountry().equals(target.getCountry())) {
-                        Slog.d(LOG_TAG, "getLocaleForLanguageCountry: got perfect match: " +
-                               l.toLanguageTag());
-                        return l;
-                    }
-
-                    // We've only matched the language, not the country.
-                    languageMatches.add(l);
-                }
-            }
-
-            if (languageMatches.isEmpty()) {
-                Slog.d(LOG_TAG, "getLocaleForLanguageCountry: no locales for language " + language);
-                return null;
-            }
-
-            Locale bestMatch = lookupFallback(target, languageMatches);
-            if (bestMatch != null) {
-                Slog.d(LOG_TAG, "getLocaleForLanguageCountry: got a fallback match: "
-                        + bestMatch.toLanguageTag());
-                return bestMatch;
-            } else {
-                // Ask {@link LocaleStore} whether this locale is considered "translated".
-                // LocaleStore has a broader definition of translated than just the asset locales
-                // above: a locale is "translated" if it has translation assets, or another locale
-                // with the same language and script has translation assets.
-                // If a locale is "translated", it is selectable in setup wizard, and can therefore
-                // be considerd a valid result for this method.
-                if (!TextUtils.isEmpty(target.getCountry())) {
-                    LocaleStore.fillCache(context);
-                    LocaleInfo targetInfo = LocaleStore.getLocaleInfo(target);
-                    if (targetInfo.isTranslated()) {
-                        Slog.d(LOG_TAG, "getLocaleForLanguageCountry: "
-                                + "target locale is translated: " + target);
-                        return target;
-                    }
-                }
-
-                // Somewhat arbitrarily take the first locale for the language,
-                // unless we get a perfect match later. Note that these come back in no
-                // particular order, so there's no reason to think the first match is
-                // a particularly good match.
-                Slog.d(LOG_TAG, "getLocaleForLanguageCountry: got language-only match: "
-                        + language);
-                return languageMatches.get(0);
-            }
-        } catch (Exception e) {
-            Slog.d(LOG_TAG, "getLocaleForLanguageCountry: exception", e);
-        }
-
-        return null;
-    }
-
-    /**
-     * If the timezone is not already set, set it based on the MCC of the SIM.
-     * @param context Context to act on.
-     * @param mcc Mobile Country Code of the SIM or SIM-like entity (build prop on CDMA)
-     */
-    private static void setTimezoneFromMccIfNeeded(Context context, int mcc) {
-        // Switch to use the time service helper associated with the NitzStateMachine impl
-        // being used. This logic will be removed once the old implementation is removed.
-        if (!TimeServiceHelperImpl.isTimeZoneSettingInitializedStatic()) {
-            String zoneId = defaultTimeZoneForMcc(mcc);
-            if (zoneId != null && zoneId.length() > 0) {
-                // Set time zone based on MCC
-                TimeServiceHelperImpl.setDeviceTimeZoneStatic(context, zoneId);
-                Slog.d(LOG_TAG, "timezone set to " + zoneId);
-            }
-        }
-    }
-
-    /**
-     * Get Locale based on the MCC of the SIM.
-     *
-     * @param context Context to act on.
-     * @param mcc Mobile Country Code of the SIM or SIM-like entity (build prop on CDMA)
-     * @param simLanguage (nullable) the language from the SIM records (if present).
-     *
-     * @return locale for the mcc or null if none
-     */
-    public static Locale getLocaleFromMcc(Context context, int mcc, String simLanguage) {
-        boolean hasSimLanguage = !TextUtils.isEmpty(simLanguage);
-        String language = hasSimLanguage ? simLanguage : MccTable.defaultLanguageForMcc(mcc);
-        String country = MccTable.countryCodeForMcc(mcc);
-
-        Slog.d(LOG_TAG, "getLocaleFromMcc(" + language + ", " + country + ", " + mcc);
-        final Locale locale = getLocaleForLanguageCountry(context, language, country);
-
-        // If we couldn't find a locale that matches the SIM language, give it a go again
-        // with the "likely" language for the given country.
-        if (locale == null && hasSimLanguage) {
-            language = MccTable.defaultLanguageForMcc(mcc);
-            Slog.d(LOG_TAG, "[retry ] getLocaleFromMcc(" + language + ", " + country + ", " + mcc);
-            return getLocaleForLanguageCountry(context, language, country);
-        }
-
-        return locale;
-    }
-
     static {
         sTable = new ArrayList<MccEntry>(240);
 
 
         /*
-         * The table below is built from two resources:
+         * The table below is built from two main resources:
          *
-         * 1) ITU "Mobile Network Code (MNC) for the international
-         *   identification plan for mobile terminals and mobile users"
-         *   which is available as an annex to the ITU operational bulletin
-         *   available here: http://www.itu.int/itu-t/bulletin/annex.html
+         * 1) ITU "LIST OF MOBILE COUNTRY OR GEOGRAPHICAL AREA CODES (POSITION ON 1 FEBRUARY 2017)"
+         *    available here: http://handle.itu.int/11.1002/pub/80f1788f-en
+         *
          *
          * 2) The ISO 3166 country codes list, available here:
-         *    http://www.iso.org/iso/en/prods-services/iso3166ma/02iso-3166-code-lists/index.html
+         *    https://www.iso.org/iso-3166-country-codes.html
+         *
+         * There are entries below that are not found in the ITU documentation and have unclear
+         * origin but have been kept from previous Android releases as their entries do not conflict
+         * with ITU values. These are commented with (*).
          *
          * This table has not been verified.
          */
 
-		sTable.add(new MccEntry(202,"gr",2));	//Greece
-		sTable.add(new MccEntry(204,"nl",2));	//Netherlands (Kingdom of the)
-		sTable.add(new MccEntry(206,"be",2));	//Belgium
-		sTable.add(new MccEntry(208,"fr",2));	//France
-		sTable.add(new MccEntry(212,"mc",2));	//Monaco (Principality of)
-		sTable.add(new MccEntry(213,"ad",2));	//Andorra (Principality of)
-		sTable.add(new MccEntry(214,"es",2));	//Spain
-		sTable.add(new MccEntry(216,"hu",2));	//Hungary (Republic of)
-		sTable.add(new MccEntry(218,"ba",2));	//Bosnia and Herzegovina
-		sTable.add(new MccEntry(219,"hr",2));	//Croatia (Republic of)
-		sTable.add(new MccEntry(220,"rs",2));	//Serbia and Montenegro
-		sTable.add(new MccEntry(222,"it",2));	//Italy
-		sTable.add(new MccEntry(225,"va",2));	//Vatican City State
-		sTable.add(new MccEntry(226,"ro",2));	//Romania
-		sTable.add(new MccEntry(228,"ch",2));	//Switzerland (Confederation of)
-		sTable.add(new MccEntry(230,"cz",2));	//Czechia
-		sTable.add(new MccEntry(231,"sk",2));	//Slovak Republic
-		sTable.add(new MccEntry(232,"at",2));	//Austria
-		sTable.add(new MccEntry(234,"gb",2));	//United Kingdom of Great Britain and Northern Ireland
-		sTable.add(new MccEntry(235,"gb",2));	//United Kingdom of Great Britain and Northern Ireland
-		sTable.add(new MccEntry(238,"dk",2));	//Denmark
-		sTable.add(new MccEntry(240,"se",2));	//Sweden
-		sTable.add(new MccEntry(242,"no",2));	//Norway
-		sTable.add(new MccEntry(244,"fi",2));	//Finland
-		sTable.add(new MccEntry(246,"lt",2));	//Lithuania (Republic of)
-		sTable.add(new MccEntry(247,"lv",2));	//Latvia (Republic of)
-		sTable.add(new MccEntry(248,"ee",2));	//Estonia (Republic of)
-		sTable.add(new MccEntry(250,"ru",2));	//Russian Federation
-		sTable.add(new MccEntry(255,"ua",2));	//Ukraine
-		sTable.add(new MccEntry(257,"by",2));	//Belarus (Republic of)
-		sTable.add(new MccEntry(259,"md",2));	//Moldova (Republic of)
-		sTable.add(new MccEntry(260,"pl",2));	//Poland (Republic of)
-		sTable.add(new MccEntry(262,"de",2));	//Germany (Federal Republic of)
-		sTable.add(new MccEntry(266,"gi",2));	//Gibraltar
-		sTable.add(new MccEntry(268,"pt",2));	//Portugal
-		sTable.add(new MccEntry(270,"lu",2));	//Luxembourg
-		sTable.add(new MccEntry(272,"ie",2));	//Ireland
-		sTable.add(new MccEntry(274,"is",2));	//Iceland
-		sTable.add(new MccEntry(276,"al",2));	//Albania (Republic of)
-		sTable.add(new MccEntry(278,"mt",2));	//Malta
-		sTable.add(new MccEntry(280,"cy",2));	//Cyprus (Republic of)
-		sTable.add(new MccEntry(282,"ge",2));	//Georgia
-		sTable.add(new MccEntry(283,"am",2));	//Armenia (Republic of)
-		sTable.add(new MccEntry(284,"bg",2));	//Bulgaria (Republic of)
-		sTable.add(new MccEntry(286,"tr",2));	//Turkey
-		sTable.add(new MccEntry(288,"fo",2));	//Faroe Islands
-                sTable.add(new MccEntry(289,"ge",2));    //Abkhazia (Georgia)
-		sTable.add(new MccEntry(290,"gl",2));	//Greenland (Denmark)
-		sTable.add(new MccEntry(292,"sm",2));	//San Marino (Republic of)
-		sTable.add(new MccEntry(293,"si",2));	//Slovenia (Republic of)
-                sTable.add(new MccEntry(294,"mk",2));   //The Former Yugoslav Republic of Macedonia
-		sTable.add(new MccEntry(295,"li",2));	//Liechtenstein (Principality of)
-                sTable.add(new MccEntry(297,"me",2));    //Montenegro (Republic of)
-		sTable.add(new MccEntry(302,"ca",3));	//Canada
-		sTable.add(new MccEntry(308,"pm",2));	//Saint Pierre and Miquelon (Collectivit territoriale de la Rpublique franaise)
-		sTable.add(new MccEntry(310,"us",3));	//United States of America
-		sTable.add(new MccEntry(311,"us",3));	//United States of America
-		sTable.add(new MccEntry(312,"us",3));	//United States of America
-		sTable.add(new MccEntry(313,"us",3));	//United States of America
-		sTable.add(new MccEntry(314,"us",3));	//United States of America
-		sTable.add(new MccEntry(315,"us",3));	//United States of America
-		sTable.add(new MccEntry(316,"us",3));	//United States of America
-		sTable.add(new MccEntry(330,"pr",2));	//Puerto Rico
-		sTable.add(new MccEntry(332,"vi",2));	//United States Virgin Islands
-		sTable.add(new MccEntry(334,"mx",3));	//Mexico
-		sTable.add(new MccEntry(338,"jm",3));	//Jamaica
-		sTable.add(new MccEntry(340,"gp",2));	//Guadeloupe (French Department of)
-		sTable.add(new MccEntry(342,"bb",3));	//Barbados
-		sTable.add(new MccEntry(344,"ag",3));	//Antigua and Barbuda
-		sTable.add(new MccEntry(346,"ky",3));	//Cayman Islands
-		sTable.add(new MccEntry(348,"vg",3));	//British Virgin Islands
-		sTable.add(new MccEntry(350,"bm",2));	//Bermuda
-		sTable.add(new MccEntry(352,"gd",2));	//Grenada
-		sTable.add(new MccEntry(354,"ms",2));	//Montserrat
-		sTable.add(new MccEntry(356,"kn",2));	//Saint Kitts and Nevis
-		sTable.add(new MccEntry(358,"lc",2));	//Saint Lucia
-		sTable.add(new MccEntry(360,"vc",2));	//Saint Vincent and the Grenadines
-		sTable.add(new MccEntry(362,"ai",2));	//Netherlands Antilles
-		sTable.add(new MccEntry(363,"aw",2));	//Aruba
-		sTable.add(new MccEntry(364,"bs",2));	//Bahamas (Commonwealth of the)
-		sTable.add(new MccEntry(365,"ai",3));	//Anguilla
-		sTable.add(new MccEntry(366,"dm",2));	//Dominica (Commonwealth of)
-		sTable.add(new MccEntry(368,"cu",2));	//Cuba
-		sTable.add(new MccEntry(370,"do",2));	//Dominican Republic
-		sTable.add(new MccEntry(372,"ht",2));	//Haiti (Republic of)
-		sTable.add(new MccEntry(374,"tt",2));	//Trinidad and Tobago
-		sTable.add(new MccEntry(376,"tc",2));	//Turks and Caicos Islands
-		sTable.add(new MccEntry(400,"az",2));	//Azerbaijani Republic
-		sTable.add(new MccEntry(401,"kz",2));	//Kazakhstan (Republic of)
-		sTable.add(new MccEntry(402,"bt",2));	//Bhutan (Kingdom of)
-		sTable.add(new MccEntry(404,"in",2));	//India (Republic of)
-		sTable.add(new MccEntry(405,"in",2));	//India (Republic of)
-		sTable.add(new MccEntry(406,"in",2));	//India (Republic of)
-		sTable.add(new MccEntry(410,"pk",2));	//Pakistan (Islamic Republic of)
-		sTable.add(new MccEntry(412,"af",2));	//Afghanistan
-		sTable.add(new MccEntry(413,"lk",2));	//Sri Lanka (Democratic Socialist Republic of)
-		sTable.add(new MccEntry(414,"mm",2));	//Myanmar (Union of)
-		sTable.add(new MccEntry(415,"lb",2));	//Lebanon
-		sTable.add(new MccEntry(416,"jo",2));	//Jordan (Hashemite Kingdom of)
-		sTable.add(new MccEntry(417,"sy",2));	//Syrian Arab Republic
-		sTable.add(new MccEntry(418,"iq",2));	//Iraq (Republic of)
-		sTable.add(new MccEntry(419,"kw",2));	//Kuwait (State of)
-		sTable.add(new MccEntry(420,"sa",2));	//Saudi Arabia (Kingdom of)
-		sTable.add(new MccEntry(421,"ye",2));	//Yemen (Republic of)
-		sTable.add(new MccEntry(422,"om",2));	//Oman (Sultanate of)
-                sTable.add(new MccEntry(423,"ps",2));    //Palestine
-		sTable.add(new MccEntry(424,"ae",2));	//United Arab Emirates
-		sTable.add(new MccEntry(425,"il",2));	//Israel (State of)
-		sTable.add(new MccEntry(426,"bh",2));	//Bahrain (Kingdom of)
-		sTable.add(new MccEntry(427,"qa",2));	//Qatar (State of)
-		sTable.add(new MccEntry(428,"mn",2));	//Mongolia
-		sTable.add(new MccEntry(429,"np",2));	//Nepal
-		sTable.add(new MccEntry(430,"ae",2));	//United Arab Emirates
-		sTable.add(new MccEntry(431,"ae",2));	//United Arab Emirates
-		sTable.add(new MccEntry(432,"ir",2));	//Iran (Islamic Republic of)
-		sTable.add(new MccEntry(434,"uz",2));	//Uzbekistan (Republic of)
-		sTable.add(new MccEntry(436,"tj",2));	//Tajikistan (Republic of)
-		sTable.add(new MccEntry(437,"kg",2));	//Kyrgyz Republic
-		sTable.add(new MccEntry(438,"tm",2));	//Turkmenistan
-		sTable.add(new MccEntry(440,"jp",2));	//Japan
-		sTable.add(new MccEntry(441,"jp",2));	//Japan
-		sTable.add(new MccEntry(450,"kr",2));	//Korea (Republic of)
-		sTable.add(new MccEntry(452,"vn",2));	//Viet Nam (Socialist Republic of)
-		sTable.add(new MccEntry(454,"hk",2));	//"Hong Kong, China"
-		sTable.add(new MccEntry(455,"mo",2));	//"Macao, China"
-		sTable.add(new MccEntry(456,"kh",2));	//Cambodia (Kingdom of)
-		sTable.add(new MccEntry(457,"la",2));	//Lao People's Democratic Republic
-		sTable.add(new MccEntry(460,"cn",2));	//China (People's Republic of)
-		sTable.add(new MccEntry(461,"cn",2));	//China (People's Republic of)
-		sTable.add(new MccEntry(466,"tw",2));	//Taiwan
-		sTable.add(new MccEntry(467,"kp",2));	//Democratic People's Republic of Korea
-		sTable.add(new MccEntry(470,"bd",2));	//Bangladesh (People's Republic of)
-		sTable.add(new MccEntry(472,"mv",2));	//Maldives (Republic of)
-		sTable.add(new MccEntry(502,"my",2));	//Malaysia
-		sTable.add(new MccEntry(505,"au",2));	//Australia
-		sTable.add(new MccEntry(510,"id",2));	//Indonesia (Republic of)
-		sTable.add(new MccEntry(514,"tl",2));	//Democratic Republic of Timor-Leste
-		sTable.add(new MccEntry(515,"ph",2));	//Philippines (Republic of the)
-		sTable.add(new MccEntry(520,"th",2));	//Thailand
-		sTable.add(new MccEntry(525,"sg",2));	//Singapore (Republic of)
-		sTable.add(new MccEntry(528,"bn",2));	//Brunei Darussalam
-		sTable.add(new MccEntry(530,"nz",2));	//New Zealand
-		sTable.add(new MccEntry(534,"mp",2));	//Northern Mariana Islands (Commonwealth of the)
-		sTable.add(new MccEntry(535,"gu",2));	//Guam
-		sTable.add(new MccEntry(536,"nr",2));	//Nauru (Republic of)
-		sTable.add(new MccEntry(537,"pg",2));	//Papua New Guinea
-		sTable.add(new MccEntry(539,"to",2));	//Tonga (Kingdom of)
-		sTable.add(new MccEntry(540,"sb",2));	//Solomon Islands
-		sTable.add(new MccEntry(541,"vu",2));	//Vanuatu (Republic of)
-		sTable.add(new MccEntry(542,"fj",2));	//Fiji (Republic of)
-		sTable.add(new MccEntry(543,"wf",2));	//Wallis and Futuna (Territoire franais d'outre-mer)
-		sTable.add(new MccEntry(544,"as",2));	//American Samoa
-		sTable.add(new MccEntry(545,"ki",2));	//Kiribati (Republic of)
-		sTable.add(new MccEntry(546,"nc",2));	//New Caledonia (Territoire franais d'outre-mer)
-		sTable.add(new MccEntry(547,"pf",2));	//French Polynesia (Territoire franais d'outre-mer)
-		sTable.add(new MccEntry(548,"ck",2));	//Cook Islands
-		sTable.add(new MccEntry(549,"ws",2));	//Samoa (Independent State of)
-		sTable.add(new MccEntry(550,"fm",2));	//Micronesia (Federated States of)
-		sTable.add(new MccEntry(551,"mh",2));	//Marshall Islands (Republic of the)
-		sTable.add(new MccEntry(552,"pw",2));	//Palau (Republic of)
-		sTable.add(new MccEntry(553,"tv",2));	//Tuvalu
-		sTable.add(new MccEntry(555,"nu",2));	//Niue
-		sTable.add(new MccEntry(602,"eg",2));	//Egypt (Arab Republic of)
-		sTable.add(new MccEntry(603,"dz",2));	//Algeria (People's Democratic Republic of)
-		sTable.add(new MccEntry(604,"ma",2));	//Morocco (Kingdom of)
-		sTable.add(new MccEntry(605,"tn",2));	//Tunisia
-		sTable.add(new MccEntry(606,"ly",2));	//Libya (Socialist People's Libyan Arab Jamahiriya)
-		sTable.add(new MccEntry(607,"gm",2));	//Gambia (Republic of the)
-		sTable.add(new MccEntry(608,"sn",2));	//Senegal (Republic of)
-		sTable.add(new MccEntry(609,"mr",2));	//Mauritania (Islamic Republic of)
-		sTable.add(new MccEntry(610,"ml",2));	//Mali (Republic of)
-		sTable.add(new MccEntry(611,"gn",2));	//Guinea (Republic of)
-		sTable.add(new MccEntry(612,"ci",2));	//Côte d'Ivoire (Republic of)
-		sTable.add(new MccEntry(613,"bf",2));	//Burkina Faso
-		sTable.add(new MccEntry(614,"ne",2));	//Niger (Republic of the)
-		sTable.add(new MccEntry(615,"tg",2));	//Togolese Republic
-		sTable.add(new MccEntry(616,"bj",2));	//Benin (Republic of)
-		sTable.add(new MccEntry(617,"mu",2));	//Mauritius (Republic of)
-		sTable.add(new MccEntry(618,"lr",2));	//Liberia (Republic of)
-		sTable.add(new MccEntry(619,"sl",2));	//Sierra Leone
-		sTable.add(new MccEntry(620,"gh",2));	//Ghana
-		sTable.add(new MccEntry(621,"ng",2));	//Nigeria (Federal Republic of)
-		sTable.add(new MccEntry(622,"td",2));	//Chad (Republic of)
-		sTable.add(new MccEntry(623,"cf",2));	//Central African Republic
-		sTable.add(new MccEntry(624,"cm",2));	//Cameroon (Republic of)
-		sTable.add(new MccEntry(625,"cv",2));	//Cape Verde (Republic of)
-		sTable.add(new MccEntry(626,"st",2));	//Sao Tome and Principe (Democratic Republic of)
-		sTable.add(new MccEntry(627,"gq",2));	//Equatorial Guinea (Republic of)
-		sTable.add(new MccEntry(628,"ga",2));	//Gabonese Republic
-		sTable.add(new MccEntry(629,"cg",2));	//Congo (Republic of the)
-		sTable.add(new MccEntry(630,"cd",2));	//Democratic Republic of the Congo
-		sTable.add(new MccEntry(631,"ao",2));	//Angola (Republic of)
-		sTable.add(new MccEntry(632,"gw",2));	//Guinea-Bissau (Republic of)
-		sTable.add(new MccEntry(633,"sc",2));	//Seychelles (Republic of)
-		sTable.add(new MccEntry(634,"sd",2));	//Sudan (Republic of the)
-		sTable.add(new MccEntry(635,"rw",2));	//Rwanda (Republic of)
-		sTable.add(new MccEntry(636,"et",2));	//Ethiopia (Federal Democratic Republic of)
-		sTable.add(new MccEntry(637,"so",2));	//Somali Democratic Republic
-		sTable.add(new MccEntry(638,"dj",2));	//Djibouti (Republic of)
-		sTable.add(new MccEntry(639,"ke",2));	//Kenya (Republic of)
-		sTable.add(new MccEntry(640,"tz",2));	//Tanzania (United Republic of)
-		sTable.add(new MccEntry(641,"ug",2));	//Uganda (Republic of)
-		sTable.add(new MccEntry(642,"bi",2));	//Burundi (Republic of)
-		sTable.add(new MccEntry(643,"mz",2));	//Mozambique (Republic of)
-		sTable.add(new MccEntry(645,"zm",2));	//Zambia (Republic of)
-		sTable.add(new MccEntry(646,"mg",2));	//Madagascar (Republic of)
-		sTable.add(new MccEntry(647,"re",2));	//Reunion (French Department of)
-		sTable.add(new MccEntry(648,"zw",2));	//Zimbabwe (Republic of)
-		sTable.add(new MccEntry(649,"na",2));	//Namibia (Republic of)
-		sTable.add(new MccEntry(650,"mw",2));	//Malawi
-		sTable.add(new MccEntry(651,"ls",2));	//Lesotho (Kingdom of)
-		sTable.add(new MccEntry(652,"bw",2));	//Botswana (Republic of)
-		sTable.add(new MccEntry(653,"sz",2));	//Swaziland (Kingdom of)
-		sTable.add(new MccEntry(654,"km",2));	//Comoros (Union of the)
-		sTable.add(new MccEntry(655,"za",2));	//South Africa (Republic of)
-		sTable.add(new MccEntry(657,"er",2));	//Eritrea
-		sTable.add(new MccEntry(658,"sh",2));	//Saint Helena, Ascension and Tristan da Cunha
-		sTable.add(new MccEntry(659,"ss",2));	//South Sudan (Republic of)
-		sTable.add(new MccEntry(702,"bz",2));	//Belize
-		sTable.add(new MccEntry(704,"gt",2));	//Guatemala (Republic of)
-		sTable.add(new MccEntry(706,"sv",2));	//El Salvador (Republic of)
-		sTable.add(new MccEntry(708,"hn",3));	//Honduras (Republic of)
-		sTable.add(new MccEntry(710,"ni",2));	//Nicaragua
-		sTable.add(new MccEntry(712,"cr",2));	//Costa Rica
-		sTable.add(new MccEntry(714,"pa",2));	//Panama (Republic of)
-		sTable.add(new MccEntry(716,"pe",2));	//Peru
-		sTable.add(new MccEntry(722,"ar",3));	//Argentine Republic
-		sTable.add(new MccEntry(724,"br",2));	//Brazil (Federative Republic of)
-		sTable.add(new MccEntry(730,"cl",2));	//Chile
-		sTable.add(new MccEntry(732,"co",3));	//Colombia (Republic of)
-		sTable.add(new MccEntry(734,"ve",2));	//Venezuela (Bolivarian Republic of)
-		sTable.add(new MccEntry(736,"bo",2));	//Bolivia (Republic of)
-		sTable.add(new MccEntry(738,"gy",2));	//Guyana
-		sTable.add(new MccEntry(740,"ec",2));	//Ecuador
-		sTable.add(new MccEntry(742,"gf",2));	//French Guiana (French Department of)
-		sTable.add(new MccEntry(744,"py",2));	//Paraguay (Republic of)
-		sTable.add(new MccEntry(746,"sr",2));	//Suriname (Republic of)
-		sTable.add(new MccEntry(748,"uy",2));	//Uruguay (Eastern Republic of)
-		sTable.add(new MccEntry(750,"fk",2));	//Falkland Islands (Malvinas)
-        //table.add(new MccEntry(901,"",2));	//"International Mobile, shared code"
+        sTable.add(new MccEntry(202, "gr", 2)); // Greece
+        sTable.add(new MccEntry(204, "nl", 2)); // Netherlands (Kingdom of the)
+        sTable.add(new MccEntry(206, "be", 2)); // Belgium
+        sTable.add(new MccEntry(208, "fr", 2)); // France
+        sTable.add(new MccEntry(212, "mc", 2)); // Monaco (Principality of)
+        sTable.add(new MccEntry(213, "ad", 2)); // Andorra (Principality of)
+        sTable.add(new MccEntry(214, "es", 2)); // Spain
+        sTable.add(new MccEntry(216, "hu", 2)); // Hungary (Republic of)
+        sTable.add(new MccEntry(218, "ba", 2)); // Bosnia and Herzegovina
+        sTable.add(new MccEntry(219, "hr", 2)); // Croatia (Republic of)
+        sTable.add(new MccEntry(220, "rs", 2)); // Serbia (Republic of)
+        sTable.add(new MccEntry(221, "xk", 2)); // Kosovo
+        sTable.add(new MccEntry(222, "it", 2)); // Italy
+        sTable.add(new MccEntry(225, "va", 2)); // Vatican City State
+        sTable.add(new MccEntry(226, "ro", 2)); // Romania
+        sTable.add(new MccEntry(228, "ch", 2)); // Switzerland (Confederation of)
+        sTable.add(new MccEntry(230, "cz", 2)); // Czechia
+        sTable.add(new MccEntry(231, "sk", 2)); // Slovak Republic
+        sTable.add(new MccEntry(232, "at", 2)); // Austria
+        sTable.add(new MccEntry(234, "gb", 2)); // United Kingdom of Great Britain and Northern Ireland
+        sTable.add(new MccEntry(235, "gb", 2)); // United Kingdom of Great Britain and Northern Ireland
+        sTable.add(new MccEntry(238, "dk", 2)); // Denmark
+        sTable.add(new MccEntry(240, "se", 2)); // Sweden
+        sTable.add(new MccEntry(242, "no", 2)); // Norway
+        sTable.add(new MccEntry(244, "fi", 2)); // Finland
+        sTable.add(new MccEntry(246, "lt", 2)); // Lithuania (Republic of)
+        sTable.add(new MccEntry(247, "lv", 2)); // Latvia (Republic of)
+        sTable.add(new MccEntry(248, "ee", 2)); // Estonia (Republic of)
+        sTable.add(new MccEntry(250, "ru", 2)); // Russian Federation
+        sTable.add(new MccEntry(255, "ua", 2)); // Ukraine
+        sTable.add(new MccEntry(257, "by", 2)); // Belarus (Republic of)
+        sTable.add(new MccEntry(259, "md", 2)); // Moldova (Republic of)
+        sTable.add(new MccEntry(260, "pl", 2)); // Poland (Republic of)
+        sTable.add(new MccEntry(262, "de", 2)); // Germany (Federal Republic of)
+        sTable.add(new MccEntry(266, "gi", 2)); // Gibraltar
+        sTable.add(new MccEntry(268, "pt", 2)); // Portugal
+        sTable.add(new MccEntry(270, "lu", 2)); // Luxembourg
+        sTable.add(new MccEntry(272, "ie", 2)); // Ireland
+        sTable.add(new MccEntry(274, "is", 2)); // Iceland
+        sTable.add(new MccEntry(276, "al", 2)); // Albania (Republic of)
+        sTable.add(new MccEntry(278, "mt", 2)); // Malta
+        sTable.add(new MccEntry(280, "cy", 2)); // Cyprus (Republic of)
+        sTable.add(new MccEntry(282, "ge", 2)); // Georgia
+        sTable.add(new MccEntry(283, "am", 2)); // Armenia (Republic of)
+        sTable.add(new MccEntry(284, "bg", 2)); // Bulgaria (Republic of)
+        sTable.add(new MccEntry(286, "tr", 2)); // Turkey
+        sTable.add(new MccEntry(288, "fo", 2)); // Faroe Islands
+        sTable.add(new MccEntry(289, "ge", 2)); // Abkhazia (Georgia) (*)
+        sTable.add(new MccEntry(290, "gl", 2)); // Greenland (Denmark)
+        sTable.add(new MccEntry(292, "sm", 2)); // San Marino (Republic of)
+        sTable.add(new MccEntry(293, "si", 2)); // Slovenia (Republic of)
+        sTable.add(new MccEntry(294, "mk", 2)); // The Former Yugoslav Republic of Macedonia
+        sTable.add(new MccEntry(295, "li", 2)); // Liechtenstein (Principality of)
+        sTable.add(new MccEntry(297, "me", 2)); // Montenegro
+        sTable.add(new MccEntry(302, "ca", 3)); // Canada
+        sTable.add(new MccEntry(308, "pm", 2)); // Saint Pierre and Miquelon (Collectivité territoriale de la République française)
+        sTable.add(new MccEntry(310, "us", 3)); // United States of America
+        sTable.add(new MccEntry(311, "us", 3)); // United States of America
+        sTable.add(new MccEntry(312, "us", 3)); // United States of America
+        sTable.add(new MccEntry(313, "us", 3)); // United States of America
+        sTable.add(new MccEntry(314, "us", 3)); // United States of America
+        sTable.add(new MccEntry(315, "us", 3)); // United States of America
+        sTable.add(new MccEntry(316, "us", 3)); // United States of America
+        sTable.add(new MccEntry(330, "pr", 2)); // Puerto Rico
+        sTable.add(new MccEntry(332, "vi", 2)); // United States Virgin Islands
+        sTable.add(new MccEntry(334, "mx", 3)); // Mexico
+        sTable.add(new MccEntry(338, "jm", 3)); // Jamaica
+        sTable.add(new MccEntry(340, "gp", 2)); // Guadeloupe (French Department of)
+        sTable.add(new MccEntry(342, "bb", 3)); // Barbados
+        sTable.add(new MccEntry(344, "ag", 3)); // Antigua and Barbuda
+        sTable.add(new MccEntry(346, "ky", 3)); // Cayman Islands
+        sTable.add(new MccEntry(348, "vg", 3)); // British Virgin Islands
+        sTable.add(new MccEntry(350, "bm", 2)); // Bermuda
+        sTable.add(new MccEntry(352, "gd", 2)); // Grenada
+        sTable.add(new MccEntry(354, "ms", 2)); // Montserrat
+        sTable.add(new MccEntry(356, "kn", 2)); // Saint Kitts and Nevis
+        sTable.add(new MccEntry(358, "lc", 2)); // Saint Lucia
+        sTable.add(new MccEntry(360, "vc", 2)); // Saint Vincent and the Grenadines
+        sTable.add(new MccEntry(362, "cw", 2)); // Curaçao
+        sTable.add(new MccEntry(363, "aw", 2)); // Aruba
+        sTable.add(new MccEntry(364, "bs", 2)); // Bahamas (Commonwealth of the)
+        sTable.add(new MccEntry(365, "ai", 3)); // Anguilla
+        sTable.add(new MccEntry(366, "dm", 2)); // Dominica (Commonwealth of)
+        sTable.add(new MccEntry(368, "cu", 2)); // Cuba
+        sTable.add(new MccEntry(370, "do", 2)); // Dominican Republic
+        sTable.add(new MccEntry(372, "ht", 2)); // Haiti (Republic of)
+        sTable.add(new MccEntry(374, "tt", 2)); // Trinidad and Tobago
+        sTable.add(new MccEntry(376, "tc", 2)); // Turks and Caicos Islands
+        sTable.add(new MccEntry(400, "az", 2)); // Azerbaijani Republic
+        sTable.add(new MccEntry(401, "kz", 2)); // Kazakhstan (Republic of)
+        sTable.add(new MccEntry(402, "bt", 2)); // Bhutan (Kingdom of)
+        sTable.add(new MccEntry(404, "in", 2)); // India (Republic of)
+        sTable.add(new MccEntry(405, "in", 2)); // India (Republic of)
+        sTable.add(new MccEntry(406, "in", 2)); // India (Republic of)
+        sTable.add(new MccEntry(410, "pk", 2)); // Pakistan (Islamic Republic of)
+        sTable.add(new MccEntry(412, "af", 2)); // Afghanistan
+        sTable.add(new MccEntry(413, "lk", 2)); // Sri Lanka (Democratic Socialist Republic of)
+        sTable.add(new MccEntry(414, "mm", 2)); // Myanmar (the Republic of the Union of)
+        sTable.add(new MccEntry(415, "lb", 2)); // Lebanon
+        sTable.add(new MccEntry(416, "jo", 2)); // Jordan (Hashemite Kingdom of)
+        sTable.add(new MccEntry(417, "sy", 2)); // Syrian Arab Republic
+        sTable.add(new MccEntry(418, "iq", 2)); // Iraq (Republic of)
+        sTable.add(new MccEntry(419, "kw", 2)); // Kuwait (State of)
+        sTable.add(new MccEntry(420, "sa", 2)); // Saudi Arabia (Kingdom of)
+        sTable.add(new MccEntry(421, "ye", 2)); // Yemen (Republic of)
+        sTable.add(new MccEntry(422, "om", 2)); // Oman (Sultanate of)
+        sTable.add(new MccEntry(423, "ps", 2)); // Palestine (*)
+        sTable.add(new MccEntry(424, "ae", 2)); // United Arab Emirates
+        sTable.add(new MccEntry(425, "il", 2)); // Israel (State of)
+        sTable.add(new MccEntry(426, "bh", 2)); // Bahrain (Kingdom of)
+        sTable.add(new MccEntry(427, "qa", 2)); // Qatar (State of)
+        sTable.add(new MccEntry(428, "mn", 2)); // Mongolia
+        sTable.add(new MccEntry(429, "np", 2)); // Nepal (Federal Democratic Republic of)
+        sTable.add(new MccEntry(430, "ae", 2)); // United Arab Emirates
+        sTable.add(new MccEntry(431, "ae", 2)); // United Arab Emirates
+        sTable.add(new MccEntry(432, "ir", 2)); // Iran (Islamic Republic of)
+        sTable.add(new MccEntry(434, "uz", 2)); // Uzbekistan (Republic of)
+        sTable.add(new MccEntry(436, "tj", 2)); // Tajikistan (Republic of)
+        sTable.add(new MccEntry(437, "kg", 2)); // Kyrgyz Republic
+        sTable.add(new MccEntry(438, "tm", 2)); // Turkmenistan
+        sTable.add(new MccEntry(440, "jp", 2)); // Japan
+        sTable.add(new MccEntry(441, "jp", 2)); // Japan
+        sTable.add(new MccEntry(450, "kr", 2)); // Korea (Republic of)
+        sTable.add(new MccEntry(452, "vn", 2)); // Viet Nam (Socialist Republic of)
+        sTable.add(new MccEntry(454, "hk", 2)); // Hong Kong, China
+        sTable.add(new MccEntry(455, "mo", 2)); // Macao, China
+        sTable.add(new MccEntry(456, "kh", 2)); // Cambodia (Kingdom of)
+        sTable.add(new MccEntry(457, "la", 2)); // Lao People's Democratic Republic
+        sTable.add(new MccEntry(460, "cn", 2)); // China (People's Republic of)
+        sTable.add(new MccEntry(461, "cn", 2)); // China (People's Republic of)
+        sTable.add(new MccEntry(466, "tw", 2)); // Taiwan, China
+        sTable.add(new MccEntry(467, "kp", 2)); // Democratic People's Republic of Korea
+        sTable.add(new MccEntry(470, "bd", 2)); // Bangladesh (People's Republic of)
+        sTable.add(new MccEntry(472, "mv", 2)); // Maldives (Republic of)
+        sTable.add(new MccEntry(502, "my", 2)); // Malaysia
+        sTable.add(new MccEntry(505, "au", 2)); // Australia
+        sTable.add(new MccEntry(510, "id", 2)); // Indonesia (Republic of)
+        sTable.add(new MccEntry(514, "tl", 2)); // Timor-Leste (Democratic Republic of)
+        sTable.add(new MccEntry(515, "ph", 2)); // Philippines (Republic of the)
+        sTable.add(new MccEntry(520, "th", 2)); // Thailand
+        sTable.add(new MccEntry(525, "sg", 2)); // Singapore (Republic of)
+        sTable.add(new MccEntry(528, "bn", 2)); // Brunei Darussalam
+        sTable.add(new MccEntry(530, "nz", 2)); // New Zealand
+        sTable.add(new MccEntry(534, "mp", 2)); // Northern Mariana Islands (Commonwealth of the) (*)
+        sTable.add(new MccEntry(535, "gu", 2)); // Guam (*)
+        sTable.add(new MccEntry(536, "nr", 2)); // Nauru (Republic of)
+        sTable.add(new MccEntry(537, "pg", 2)); // Papua New Guinea
+        sTable.add(new MccEntry(539, "to", 2)); // Tonga (Kingdom of)
+        sTable.add(new MccEntry(540, "sb", 2)); // Solomon Islands
+        sTable.add(new MccEntry(541, "vu", 2)); // Vanuatu (Republic of)
+        sTable.add(new MccEntry(542, "fj", 2)); // Fiji (Republic of)
+        sTable.add(new MccEntry(543, "wf", 2)); // Wallis and Futuna (Territoire français d'outre-mer)
+        sTable.add(new MccEntry(544, "as", 2)); // American Samoa
+        sTable.add(new MccEntry(545, "ki", 2)); // Kiribati (Republic of)
+        sTable.add(new MccEntry(546, "nc", 2)); // New Caledonia (Territoire français d'outre-mer)
+        sTable.add(new MccEntry(547, "pf", 2)); // French Polynesia (Territoire français d'outre-mer)
+        sTable.add(new MccEntry(548, "ck", 2)); // Cook Islands
+        sTable.add(new MccEntry(549, "ws", 2)); // Samoa (Independent State of)
+        sTable.add(new MccEntry(550, "fm", 2)); // Micronesia (Federated States of)
+        sTable.add(new MccEntry(551, "mh", 2)); // Marshall Islands (Republic of the)
+        sTable.add(new MccEntry(552, "pw", 2)); // Palau (Republic of)
+        sTable.add(new MccEntry(553, "tv", 2)); // Tuvalu
+        sTable.add(new MccEntry(554, "tk", 2)); // Tokelau
+        sTable.add(new MccEntry(555, "nu", 2)); // Niue
+        sTable.add(new MccEntry(602, "eg", 2)); // Egypt (Arab Republic of)
+        sTable.add(new MccEntry(603, "dz", 2)); // Algeria (People's Democratic Republic of)
+        sTable.add(new MccEntry(604, "ma", 2)); // Morocco (Kingdom of)
+        sTable.add(new MccEntry(605, "tn", 2)); // Tunisia
+        sTable.add(new MccEntry(606, "ly", 2)); // Libya
+        sTable.add(new MccEntry(607, "gm", 2)); // Gambia (Republic of the)
+        sTable.add(new MccEntry(608, "sn", 2)); // Senegal (Republic of)
+        sTable.add(new MccEntry(609, "mr", 2)); // Mauritania (Islamic Republic of)
+        sTable.add(new MccEntry(610, "ml", 2)); // Mali (Republic of)
+        sTable.add(new MccEntry(611, "gn", 2)); // Guinea (Republic of)
+        sTable.add(new MccEntry(612, "ci", 2)); // Côte d'Ivoire (Republic of)
+        sTable.add(new MccEntry(613, "bf", 2)); // Burkina Faso
+        sTable.add(new MccEntry(614, "ne", 2)); // Niger (Republic of the)
+        sTable.add(new MccEntry(615, "tg", 2)); // Togolese Republic
+        sTable.add(new MccEntry(616, "bj", 2)); // Benin (Republic of)
+        sTable.add(new MccEntry(617, "mu", 2)); // Mauritius (Republic of)
+        sTable.add(new MccEntry(618, "lr", 2)); // Liberia (Republic of)
+        sTable.add(new MccEntry(619, "sl", 2)); // Sierra Leone
+        sTable.add(new MccEntry(620, "gh", 2)); // Ghana
+        sTable.add(new MccEntry(621, "ng", 2)); // Nigeria (Federal Republic of)
+        sTable.add(new MccEntry(622, "td", 2)); // Chad (Republic of)
+        sTable.add(new MccEntry(623, "cf", 2)); // Central African Republic
+        sTable.add(new MccEntry(624, "cm", 2)); // Cameroon (Republic of)
+        sTable.add(new MccEntry(625, "cv", 2)); // Cape Verde (Republic of)
+        sTable.add(new MccEntry(626, "st", 2)); // Sao Tome and Principe (Democratic Republic of)
+        sTable.add(new MccEntry(627, "gq", 2)); // Equatorial Guinea (Republic of)
+        sTable.add(new MccEntry(628, "ga", 2)); // Gabonese Republic
+        sTable.add(new MccEntry(629, "cg", 2)); // Congo (Republic of the)
+        sTable.add(new MccEntry(630, "cd", 2)); // Democratic Republic of the Congo
+        sTable.add(new MccEntry(631, "ao", 2)); // Angola (Republic of)
+        sTable.add(new MccEntry(632, "gw", 2)); // Guinea-Bissau (Republic of)
+        sTable.add(new MccEntry(633, "sc", 2)); // Seychelles (Republic of)
+        sTable.add(new MccEntry(634, "sd", 2)); // Sudan (Republic of the)
+        sTable.add(new MccEntry(635, "rw", 2)); // Rwanda (Republic of)
+        sTable.add(new MccEntry(636, "et", 2)); // Ethiopia (Federal Democratic Republic of)
+        sTable.add(new MccEntry(637, "so", 2)); // Somali Democratic Republic
+        sTable.add(new MccEntry(638, "dj", 2)); // Djibouti (Republic of)
+        sTable.add(new MccEntry(639, "ke", 2)); // Kenya (Republic of)
+        sTable.add(new MccEntry(640, "tz", 2)); // Tanzania (United Republic of)
+        sTable.add(new MccEntry(641, "ug", 2)); // Uganda (Republic of)
+        sTable.add(new MccEntry(642, "bi", 2)); // Burundi (Republic of)
+        sTable.add(new MccEntry(643, "mz", 2)); // Mozambique (Republic of)
+        sTable.add(new MccEntry(645, "zm", 2)); // Zambia (Republic of)
+        sTable.add(new MccEntry(646, "mg", 2)); // Madagascar (Republic of)
+        sTable.add(new MccEntry(647, "re", 2)); // French Departments and Territories in the Indian Ocean
+        sTable.add(new MccEntry(648, "zw", 2)); // Zimbabwe (Republic of)
+        sTable.add(new MccEntry(649, "na", 2)); // Namibia (Republic of)
+        sTable.add(new MccEntry(650, "mw", 2)); // Malawi
+        sTable.add(new MccEntry(651, "ls", 2)); // Lesotho (Kingdom of)
+        sTable.add(new MccEntry(652, "bw", 2)); // Botswana (Republic of)
+        sTable.add(new MccEntry(653, "sz", 2)); // Swaziland (Kingdom of)
+        sTable.add(new MccEntry(654, "km", 2)); // Comoros (Union of the)
+        sTable.add(new MccEntry(655, "za", 2)); // South Africa (Republic of)
+        sTable.add(new MccEntry(657, "er", 2)); // Eritrea
+        sTable.add(new MccEntry(658, "sh", 2)); // Saint Helena, Ascension and Tristan da Cunha
+        sTable.add(new MccEntry(659, "ss", 2)); // South Sudan (Republic of)
+        sTable.add(new MccEntry(702, "bz", 2)); // Belize
+        sTable.add(new MccEntry(704, "gt", 2)); // Guatemala (Republic of)
+        sTable.add(new MccEntry(706, "sv", 2)); // El Salvador (Republic of)
+        sTable.add(new MccEntry(708, "hn", 3)); // Honduras (Republic of)
+        sTable.add(new MccEntry(710, "ni", 2)); // Nicaragua
+        sTable.add(new MccEntry(712, "cr", 2)); // Costa Rica
+        sTable.add(new MccEntry(714, "pa", 2)); // Panama (Republic of)
+        sTable.add(new MccEntry(716, "pe", 2)); // Peru
+        sTable.add(new MccEntry(722, "ar", 3)); // Argentine Republic
+        sTable.add(new MccEntry(724, "br", 2)); // Brazil (Federative Republic of)
+        sTable.add(new MccEntry(730, "cl", 2)); // Chile
+        sTable.add(new MccEntry(732, "co", 3)); // Colombia (Republic of)
+        sTable.add(new MccEntry(734, "ve", 2)); // Venezuela (Bolivarian Republic of)
+        sTable.add(new MccEntry(736, "bo", 2)); // Bolivia (Republic of)
+        sTable.add(new MccEntry(738, "gy", 2)); // Guyana
+        sTable.add(new MccEntry(740, "ec", 2)); // Ecuador
+        sTable.add(new MccEntry(742, "gf", 2)); // French Guiana (French Department of)
+        sTable.add(new MccEntry(744, "py", 2)); // Paraguay (Republic of)
+        sTable.add(new MccEntry(746, "sr", 2)); // Suriname (Republic of)
+        sTable.add(new MccEntry(748, "uy", 2)); // Uruguay (Eastern Republic of)
+        sTable.add(new MccEntry(750, "fk", 2)); // Falkland Islands (Malvinas)
 
         Collections.sort(sTable);
     }
diff --git a/src/java/com/android/internal/telephony/MissedIncomingCallSmsFilter.java b/src/java/com/android/internal/telephony/MissedIncomingCallSmsFilter.java
new file mode 100644
index 0000000..5932f9e
--- /dev/null
+++ b/src/java/com/android/internal/telephony/MissedIncomingCallSmsFilter.java
@@ -0,0 +1,263 @@
+/*
+ * Copyright (C) 2020 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;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.ComponentName;
+import android.content.Context;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.PersistableBundle;
+import android.telecom.PhoneAccount;
+import android.telecom.PhoneAccountHandle;
+import android.telecom.TelecomManager;
+import android.telephony.CarrierConfigManager;
+import android.telephony.Rlog;
+import android.telephony.SmsMessage;
+import android.text.TextUtils;
+
+import java.time.Instant;
+import java.time.LocalDateTime;
+import java.time.ZoneId;
+import java.time.format.DateTimeFormatter;
+import java.util.Arrays;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import java.util.regex.PatternSyntaxException;
+
+/**
+ * The SMS filter for parsing SMS from carrier to notify users about the missed incoming call.
+ */
+public class MissedIncomingCallSmsFilter {
+    private static final String TAG = MissedIncomingCallSmsFilter.class.getSimpleName();
+
+    private static final boolean VDBG = false;    // STOPSHIP if true
+
+    private static final String SMS_YEAR_TAG = "year";
+
+    private static final String SMS_MONTH_TAG = "month";
+
+    private static final String SMS_DAY_TAG = "day";
+
+    private static final String SMS_HOUR_TAG = "hour";
+
+    private static final String SMS_MINUTE_TAG = "minute";
+
+    private static final String SMS_CALLER_ID_TAG = "callerId";
+
+    private static final ComponentName PSTN_CONNECTION_SERVICE_COMPONENT =
+            new ComponentName("com.android.phone",
+                    "com.android.services.telephony.TelephonyConnectionService");
+
+    private final Phone mPhone;
+
+    private PersistableBundle mCarrierConfig;
+
+    /**
+     * Constructor
+     *
+     * @param phone The phone instance
+     */
+    public MissedIncomingCallSmsFilter(Phone phone) {
+        mPhone = phone;
+
+        CarrierConfigManager configManager = (CarrierConfigManager) mPhone.getContext()
+                .getSystemService(Context.CARRIER_CONFIG_SERVICE);
+        if (configManager != null) {
+            mCarrierConfig = configManager.getConfigForSubId(mPhone.getSubId());
+        }
+    }
+
+    /**
+     * Check if the message is missed incoming call SMS, which is sent from the carrier to notify
+     * the user about the missed incoming call earlier.
+     *
+     * @param pdus SMS pdu binary
+     * @param format Either {@link SmsConstants#FORMAT_3GPP} or {@link SmsConstants#FORMAT_3GPP2}
+     * @return {@code true} if this is an SMS for notifying the user about missed incoming call.
+     */
+    public boolean filter(byte[][] pdus, String format) {
+        // The missed incoming call SMS must be one page only, and if not we should ignore it.
+        if (pdus.length != 1) {
+            return false;
+        }
+
+        if (mCarrierConfig != null) {
+            String[] originators = mCarrierConfig.getStringArray(CarrierConfigManager
+                    .KEY_MISSED_INCOMING_CALL_SMS_ORIGINATOR_STRING_ARRAY);
+            if (originators != null) {
+                SmsMessage message = SmsMessage.createFromPdu(pdus[0], format);
+                if (message != null
+                        && !TextUtils.isEmpty(message.getOriginatingAddress())
+                        && Arrays.asList(originators).contains(message.getOriginatingAddress())) {
+                    return processSms(message);
+                }
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Get the Epoch time.
+     *
+     * @param year Year in string format. If this param is null or empty, a guessed year will be
+     * used. Some carriers do not provide this information in the SMS.
+     * @param month Month in string format.
+     * @param day Day in string format.
+     * @param hour Hour in string format.
+     * @param minute Minute in string format.
+     * @return The Epoch time in milliseconds.
+     */
+    private long getEpochTime(String year, String month, String day, String hour, String minute) {
+        LocalDateTime now = LocalDateTime.now();
+        if (TextUtils.isEmpty(year)) {
+            // If year is not provided, guess the year from current time.
+            year = Integer.toString(now.getYear());
+        }
+
+        LocalDateTime time;
+        // Check if the guessed year is reasonable. If it's the future, then the year must be
+        // the previous year. For example, the missed call's month and day is 12/31, but current
+        // date is 1/1/2020, then the year of missed call must be 2019.
+        do {
+            DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyyMMddHHmm");
+            time = LocalDateTime.parse(year + month + day + hour + minute, formatter);
+            year = Integer.toString(Integer.parseInt(year) - 1);
+        } while (time.isAfter(now));
+
+        Instant instant = time.atZone(ZoneId.systemDefault()).toInstant();
+        return instant.toEpochMilli();
+    }
+
+    /**
+     * Process the SMS message
+     *
+     * @param message SMS message
+     *
+     * @return {@code true} if the SMS message has been processed as a missed incoming call SMS.
+     */
+    private boolean processSms(@NonNull SmsMessage message) {
+        long missedCallTime = 0;
+        String callerId = null;
+
+        String[] smsPatterns = mCarrierConfig.getStringArray(CarrierConfigManager
+                .KEY_MISSED_INCOMING_CALL_SMS_PATTERN_STRING_ARRAY);
+        if (smsPatterns == null || smsPatterns.length == 0) {
+            Rlog.w(TAG, "Missed incoming call SMS pattern is not configured!");
+            return false;
+        }
+
+        for (String smsPattern : smsPatterns) {
+            Pattern pattern;
+            try {
+                pattern = Pattern.compile(smsPattern, Pattern.DOTALL | Pattern.UNIX_LINES);
+            } catch (PatternSyntaxException e) {
+                Rlog.w(TAG, "Configuration error. Unexpected missed incoming call sms "
+                        + "pattern: " + smsPattern + ", e=" + e);
+                continue;
+            }
+
+            Matcher matcher = pattern.matcher(message.getMessageBody());
+            String year = null, month = null, day = null, hour = null, minute = null;
+            if (matcher.find()) {
+                try {
+                    month = matcher.group(SMS_MONTH_TAG);
+                    day = matcher.group(SMS_DAY_TAG);
+                    hour = matcher.group(SMS_HOUR_TAG);
+                    minute = matcher.group(SMS_MINUTE_TAG);
+                    if (VDBG) {
+                        Rlog.v(TAG, "month=" + month + ", day=" + day + ", hour=" + hour
+                                + ", minute=" + minute);
+                    }
+                } catch (IllegalArgumentException e) {
+                    if (VDBG) {
+                        Rlog.v(TAG, "One of the critical date field is missing. Using the "
+                                + "current time for missed incoming call.");
+                    }
+                    missedCallTime = System.currentTimeMillis();
+                }
+
+                // Year is an optional field.
+                try {
+                    year = matcher.group(SMS_YEAR_TAG);
+                } catch (IllegalArgumentException e) {
+                    if (VDBG) Rlog.v(TAG, "Year is missing.");
+                }
+
+                try {
+                    if (missedCallTime == 0) {
+                        missedCallTime = getEpochTime(year, month, day, hour, minute);
+                        if (missedCallTime == 0) {
+                            Rlog.e(TAG, "Can't get the time. Use the current time.");
+                            missedCallTime = System.currentTimeMillis();
+                        }
+                    }
+
+                    if (VDBG) Rlog.v(TAG, "missedCallTime=" + missedCallTime);
+                } catch (Exception e) {
+                    Rlog.e(TAG, "Can't get the time for missed incoming call");
+                }
+
+                try {
+                    callerId = matcher.group(SMS_CALLER_ID_TAG);
+                    if (VDBG) Rlog.v(TAG, "caller id=" + callerId);
+                } catch (IllegalArgumentException e) {
+                    Rlog.d(TAG, "Caller id is not provided or can't be parsed.");
+                }
+                createMissedIncomingCallEvent(missedCallTime, callerId);
+                return true;
+            }
+        }
+
+        Rlog.d(TAG, "SMS did not match any missed incoming call SMS pattern.");
+        return false;
+    }
+
+    // Create phone account. The logic is copied from PhoneUtils.makePstnPhoneAccountHandle.
+    private static PhoneAccountHandle makePstnPhoneAccountHandle(Phone phone) {
+        return new PhoneAccountHandle(PSTN_CONNECTION_SERVICE_COMPONENT,
+                String.valueOf(phone.getFullIccSerialNumber()));
+    }
+
+    /**
+     * Create the missed incoming call through TelecomManager.
+     *
+     * @param missedCallTime the time of missed incoming call in. This is the EPOCH time in
+     * milliseconds.
+     * @param callerId The caller id of the missed incoming call.
+     */
+    private void createMissedIncomingCallEvent(long missedCallTime, @Nullable String callerId) {
+        TelecomManager tm = (TelecomManager) mPhone.getContext()
+                .getSystemService(Context.TELECOM_SERVICE);
+
+        if (tm != null) {
+            Bundle bundle = new Bundle();
+
+            if (callerId != null) {
+                final Uri phoneUri = Uri.fromParts(
+                        PhoneAccount.SCHEME_TEL, callerId, null);
+                bundle.putParcelable(TelecomManager.EXTRA_INCOMING_CALL_ADDRESS, phoneUri);
+            }
+
+            // Need to use the Epoch time instead of the elapsed time because it's possible
+            // the missed incoming call occurred before the phone boots up.
+            bundle.putLong(TelecomManager.EXTRA_CALL_CREATED_EPOCH_TIME_MILLIS, missedCallTime);
+            tm.addNewIncomingCall(makePstnPhoneAccountHandle(mPhone), bundle);
+        }
+    }
+}
diff --git a/src/java/com/android/internal/telephony/MmiCode.java b/src/java/com/android/internal/telephony/MmiCode.java
index eb09621..b47e17b 100644
--- a/src/java/com/android/internal/telephony/MmiCode.java
+++ b/src/java/com/android/internal/telephony/MmiCode.java
@@ -16,7 +16,7 @@
 
 package com.android.internal.telephony;
 
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
 import android.os.ResultReceiver;
 
 import java.util.regex.Pattern;
@@ -70,6 +70,11 @@
     public boolean isUssdRequest();
 
     /**
+     * @return true if the request was initiated USSD by the network.
+     */
+    boolean isNetworkInitiatedUssd();
+
+    /**
      * @return true if an outstanding request can be canceled.
      */
     public boolean isCancelable();
diff --git a/src/java/com/android/internal/telephony/MultiSimSettingController.java b/src/java/com/android/internal/telephony/MultiSimSettingController.java
index ee817c6..a90daf9 100644
--- a/src/java/com/android/internal/telephony/MultiSimSettingController.java
+++ b/src/java/com/android/internal/telephony/MultiSimSettingController.java
@@ -35,6 +35,7 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
+import android.os.AsyncResult;
 import android.os.Handler;
 import android.os.Message;
 import android.os.ParcelUuid;
@@ -49,7 +50,7 @@
 import android.util.Log;
 
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.util.ArrayUtils;
+import com.android.internal.telephony.util.ArrayUtils;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
@@ -78,6 +79,7 @@
     private static final int EVENT_SUBSCRIPTION_GROUP_CHANGED        = 5;
     private static final int EVENT_DEFAULT_DATA_SUBSCRIPTION_CHANGED = 6;
     private static final int EVENT_CARRIER_CONFIG_CHANGED            = 7;
+    private static final int EVENT_MULTI_SIM_CONFIG_CHANGED          = 8;
 
     @Retention(RetentionPolicy.SOURCE)
     @IntDef(prefix = {"PRIMARY_SUB_"},
@@ -107,13 +109,13 @@
     // Subscription information is initially loaded.
     private static final int PRIMARY_SUB_INITIALIZED            = 6;
 
-    private final Context mContext;
-    private final SubscriptionController mSubController;
+    protected final Context mContext;
+    protected final SubscriptionController mSubController;
     // Keep a record of active primary (non-opportunistic) subscription list.
     @NonNull private List<Integer> mPrimarySubList = new ArrayList<>();
 
     /** The singleton instance. */
-    private static MultiSimSettingController sInstance = null;
+    protected static MultiSimSettingController sInstance = null;
 
     // This will be set true when handling EVENT_ALL_SUBSCRIPTIONS_LOADED. The reason of keeping
     // a local variable instead of calling SubscriptionInfoUpdater#isSubInfoInitialized is, there
@@ -153,7 +155,7 @@
      * Return the singleton or create one if not existed.
      */
     public static MultiSimSettingController getInstance() {
-        synchronized (SubscriptionController.class) {
+        synchronized (MultiSimSettingController.class) {
             if (sInstance == null) {
                 Log.wtf(LOG_TAG, "getInstance null");
             }
@@ -166,7 +168,7 @@
      * Init instance of MultiSimSettingController.
      */
     public static MultiSimSettingController init(Context context, SubscriptionController sc) {
-        synchronized (SubscriptionController.class) {
+        synchronized (MultiSimSettingController.class) {
             if (sInstance == null) {
                 sInstance = new MultiSimSettingController(context, sc);
             } else {
@@ -183,10 +185,13 @@
 
         // Initialize mCarrierConfigLoadedSubIds and register to listen to carrier config change.
         final int phoneCount = ((TelephonyManager) mContext.getSystemService(
-                Context.TELEPHONY_SERVICE)).getPhoneCount();
+                Context.TELEPHONY_SERVICE)).getSupportedModemCount();
         mCarrierConfigLoadedSubIds = new int[phoneCount];
         Arrays.fill(mCarrierConfigLoadedSubIds, SubscriptionManager.INVALID_SUBSCRIPTION_ID);
 
+        PhoneConfigurationManager.registerForMultiSimConfigChange(
+                this, EVENT_MULTI_SIM_CONFIG_CHANGED, null);
+
         context.registerReceiver(mIntentReceiver, new IntentFilter(
                 CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED));
     }
@@ -195,14 +200,18 @@
      * Notify MOBILE_DATA of a subscription is changed.
      */
     public void notifyUserDataEnabled(int subId, boolean enable) {
-        obtainMessage(EVENT_USER_DATA_ENABLED, subId, enable ? 1 : 0).sendToTarget();
+        if (SubscriptionManager.isValidSubscriptionId(subId)) {
+            obtainMessage(EVENT_USER_DATA_ENABLED, subId, enable ? 1 : 0).sendToTarget();
+        }
     }
 
     /**
      * Notify DATA_ROAMING of a subscription is changed.
      */
     public void notifyRoamingDataEnabled(int subId, boolean enable) {
-        obtainMessage(EVENT_ROAMING_DATA_ENABLED, subId, enable ? 1 : 0).sendToTarget();
+        if (SubscriptionManager.isValidSubscriptionId(subId)) {
+            obtainMessage(EVENT_ROAMING_DATA_ENABLED, subId, enable ? 1 : 0).sendToTarget();
+        }
     }
 
     /**
@@ -267,6 +276,9 @@
                 int subId = msg.arg2;
                 onCarrierConfigChanged(phoneId, subId);
                 break;
+            case EVENT_MULTI_SIM_CONFIG_CHANGED:
+                int activeModems = (int) ((AsyncResult) msg.obj).result;
+                onMultiSimConfigChanged(activeModems);
         }
     }
 
@@ -276,7 +288,7 @@
      * If user is enabling a non-default non-opportunistic subscription, make it default
      * data subscription.
      */
-    private void onUserDataEnabled(int subId, boolean enable) {
+    protected void onUserDataEnabled(int subId, boolean enable) {
         if (DBG) log("onUserDataEnabled");
         // Make sure MOBILE_DATA of subscriptions in same group are synced.
         setUserDataEnabledForGroup(subId, enable);
@@ -334,6 +346,22 @@
             return;
         }
 
+        // b/153860050 Occasionally we receive carrier config change broadcast without subId
+        // being specified in it. So here we do additional check to make sur we don't miss the
+        // subId.
+        if (subId == SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
+            int[] subIds = mSubController.getSubId(phoneId);
+            if (!ArrayUtils.isEmpty(subIds)) {
+                CarrierConfigManager cm = (CarrierConfigManager) mContext.getSystemService(
+                        mContext.CARRIER_CONFIG_SERVICE);
+                if (cm != null && cm.getConfigForSubId(subIds[0]) != null) {
+                    loge("onCarrierConfigChanged with invalid subId while subd "
+                            + subIds[0] + " is active and its config is loaded");
+                    subId = subIds[0];
+                }
+            }
+        }
+
         mCarrierConfigLoadedSubIds[phoneId] = subId;
         reEvaluateAll();
     }
@@ -357,6 +385,14 @@
         return true;
     }
 
+    private void onMultiSimConfigChanged(int activeModems) {
+        // Clear mCarrierConfigLoadedSubIds. Other actions will responds to active
+        // subscription change.
+        for (int phoneId = activeModems; phoneId < mCarrierConfigLoadedSubIds.length; phoneId++) {
+            mCarrierConfigLoadedSubIds[phoneId] = INVALID_SUBSCRIPTION_ID;
+        }
+    }
+
     /**
      * Wait for subInfo initialization (after boot up) and carrier config load for all active
      * subscriptions before re-evaluate multi SIM settings.
@@ -390,7 +426,7 @@
         if (DBG) log("onSubscriptionGroupChanged");
 
         List<SubscriptionInfo> infoList = mSubController.getSubscriptionsInGroup(
-                groupUuid, mContext.getOpPackageName());
+                groupUuid, mContext.getOpPackageName(), mContext.getAttributionTag());
         if (infoList == null || infoList.isEmpty()) return;
 
         // Get a reference subscription to copy settings from.
@@ -447,13 +483,14 @@
      * 4) If non above is met, clear the default value to INVALID.
      *
      */
-    private void updateDefaults() {
+    protected void updateDefaults() {
         if (DBG) log("updateDefaults");
 
         if (!isReadyToReevaluate()) return;
 
         List<SubscriptionInfo> activeSubInfos = mSubController
-                .getActiveSubscriptionInfoList(mContext.getOpPackageName());
+                .getActiveSubscriptionInfoList(mContext.getOpPackageName(),
+                        mContext.getAttributionTag());
 
         if (ArrayUtils.isEmpty(activeSubInfos)) {
             mPrimarySubList.clear();
@@ -473,7 +510,9 @@
         // Otherwise, if user just inserted their first SIM, or there's one primary and one
         // opportunistic subscription active (activeSubInfos.size() > 1), we automatically
         // set the primary to be default SIM and return.
-        if (mPrimarySubList.size() == 1 && change != PRIMARY_SUB_REMOVED) {
+        if (mPrimarySubList.size() == 1 && (change != PRIMARY_SUB_REMOVED
+                || ((TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE))
+                .getActiveModemCount() == 1)) {
             int subId = mPrimarySubList.get(0);
             if (DBG) log("[updateDefaultValues] to only primary sub " + subId);
             mSubController.setDefaultDataSubId(subId);
@@ -625,7 +664,8 @@
             if (phone != null && phone.isCdmaSubscriptionAppPresent()) {
                 cdmaPhoneCount++;
                 String simName = mSubController.getActiveSubscriptionInfo(
-                        subId, mContext.getOpPackageName()).getDisplayName().toString();
+                        subId, mContext.getOpPackageName(), mContext.getAttributionTag())
+                        .getDisplayName().toString();
                 if (TextUtils.isEmpty(simName)) {
                     // Fall back to carrier name.
                     simName = phone.getCarrierName();
@@ -647,7 +687,7 @@
                 || change == PRIMARY_SUB_SWAPPED);
     }
 
-    private void disableDataForNonDefaultNonOpportunisticSubscriptions() {
+    protected void disableDataForNonDefaultNonOpportunisticSubscriptions() {
         if (!isReadyToReevaluate()) return;
 
         int defaultDataSub = mSubController.getDefaultDataSubId();
@@ -678,10 +718,11 @@
      * Make sure MOBILE_DATA of subscriptions in the same group with the subId
      * are synced.
      */
-    private void setUserDataEnabledForGroup(int subId, boolean enable) {
+    protected void setUserDataEnabledForGroup(int subId, boolean enable) {
         log("setUserDataEnabledForGroup subId " + subId + " enable " + enable);
         List<SubscriptionInfo> infoList = mSubController.getSubscriptionsInGroup(
-                mSubController.getGroupUuid(subId), mContext.getOpPackageName());
+                mSubController.getGroupUuid(subId), mContext.getOpPackageName(),
+                mContext.getAttributionTag());
 
         if (infoList == null) return;
 
@@ -711,7 +752,8 @@
     private void setRoamingDataEnabledForGroup(int subId, boolean enable) {
         SubscriptionController subController = SubscriptionController.getInstance();
         List<SubscriptionInfo> infoList = subController.getSubscriptionsInGroup(
-                mSubController.getGroupUuid(subId), mContext.getOpPackageName());
+                mSubController.getGroupUuid(subId), mContext.getOpPackageName(),
+                mContext.getAttributionTag());
 
         if (infoList == null) return;
 
@@ -759,7 +801,7 @@
         if (!SubscriptionInfoUpdater.isSubInfoInitialized()) return;
 
         List<SubscriptionInfo> opptSubList = mSubController.getOpportunisticSubscriptions(
-                mContext.getOpPackageName());
+                mContext.getOpPackageName(), mContext.getAttributionTag());
 
         if (ArrayUtils.isEmpty(opptSubList)) return;
 
diff --git a/src/java/com/android/internal/telephony/NetworkRegistrationManager.java b/src/java/com/android/internal/telephony/NetworkRegistrationManager.java
index 83d5432..69f64f1 100644
--- a/src/java/com/android/internal/telephony/NetworkRegistrationManager.java
+++ b/src/java/com/android/internal/telephony/NetworkRegistrationManager.java
@@ -22,6 +22,7 @@
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.content.ServiceConnection;
+import android.content.pm.PackageManager;
 import android.os.AsyncResult;
 import android.os.Handler;
 import android.os.IBinder;
@@ -37,10 +38,11 @@
 import android.telephony.INetworkServiceCallback;
 import android.telephony.NetworkRegistrationInfo;
 import android.telephony.NetworkService;
-import android.telephony.Rlog;
-import android.telephony.TelephonyManager;
+import android.telephony.SubscriptionManager;
 import android.text.TextUtils;
 
+import com.android.telephony.Rlog;
+
 import java.util.Hashtable;
 import java.util.Map;
 
@@ -91,10 +93,7 @@
         mPhone = phone;
 
         String tagSuffix = "-" + ((transportType == AccessNetworkConstants.TRANSPORT_TYPE_WWAN)
-                ? "C" : "I");
-        if (TelephonyManager.getDefault().getPhoneCount() > 1) {
-            tagSuffix += "-" + mPhone.getPhoneId();
-        }
+                ? "C" : "I") + "-" + mPhone.getPhoneId();
         mTag = "NRM" + tagSuffix;
 
         mCarrierConfigManager = (CarrierConfigManager) phone.getContext().getSystemService(
@@ -102,8 +101,17 @@
 
         IntentFilter intentFilter = new IntentFilter();
         intentFilter.addAction(CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED);
-        phone.getContext().registerReceiverAsUser(mBroadcastReceiver, UserHandle.ALL,
-                intentFilter, null, null);
+        try {
+            Context contextAsUser = phone.getContext().createPackageContextAsUser(
+                phone.getContext().getPackageName(), 0, UserHandle.ALL);
+            contextAsUser.registerReceiver(mBroadcastReceiver, intentFilter,
+                null /* broadcastPermission */, null);
+        } catch (PackageManager.NameNotFoundException e) {
+            loge("Package name not found: " + e.getMessage());
+        }
+        PhoneConfigurationManager.registerForMultiSimConfigChange(
+                this, EVENT_BIND_NETWORK_SERVICE, null);
+
         sendEmptyMessage(EVENT_BIND_NETWORK_SERVICE);
     }
 
@@ -116,7 +124,7 @@
     public void handleMessage(Message msg) {
         switch (msg.what) {
             case EVENT_BIND_NETWORK_SERVICE:
-                bindService();
+                rebindService();
                 break;
             default:
                 loge("Unhandled event " + msg.what);
@@ -205,9 +213,6 @@
                     + AccessNetworkConstants.transportTypeToString(mTransportType)
                     + " is now disconnected.");
             mTargetBindingPackageName = null;
-            if (mINetworkService != null) {
-                mINetworkService.asBinder().unlinkToDeath(mDeathRecipient, 0);
-            }
         }
     }
 
@@ -235,31 +240,45 @@
         }
     }
 
-    private void bindService() {
-        String packageName = getPackageName();
-        if (TextUtils.isEmpty(packageName)) {
-            loge("Can't find the binding package");
-            return;
-        }
-
-        if (TextUtils.equals(packageName, mTargetBindingPackageName)) {
-            logd("Service " + packageName + " already bound or being bound.");
-            return;
-        }
-
+    private void unbindService() {
         if (mINetworkService != null && mINetworkService.asBinder().isBinderAlive()) {
+            logd("unbinding service");
             // Remove the network availability updater and then unbind the service.
             try {
                 mINetworkService.removeNetworkServiceProvider(mPhone.getPhoneId());
             } catch (RemoteException e) {
                 loge("Cannot remove data service provider. " + e);
             }
-
-            mPhone.getContext().unbindService(mServiceConnection);
         }
 
-        Intent intent = new Intent(NetworkService.SERVICE_INTERFACE);
-        intent.setPackage(getPackageName());
+        if (mServiceConnection != null) {
+            mPhone.getContext().unbindService(mServiceConnection);
+        }
+        mINetworkService = null;
+        mServiceConnection = null;
+        mTargetBindingPackageName = null;
+    }
+
+    private void bindService(String packageName) {
+        if (mPhone == null || !SubscriptionManager.isValidPhoneId(mPhone.getPhoneId())) {
+            loge("can't bindService with invalid phone or phoneId.");
+            return;
+        }
+
+        if (TextUtils.isEmpty(packageName)) {
+            loge("Can't find the binding package");
+            return;
+        }
+
+        Intent intent = null;
+        String className = getClassName();
+        if (TextUtils.isEmpty(className)) {
+            intent = new Intent(NetworkService.SERVICE_INTERFACE);
+            intent.setPackage(packageName);
+        } else {
+            ComponentName cm = new ComponentName(packageName, className);
+            intent = new Intent(NetworkService.SERVICE_INTERFACE).setComponent(cm);
+        }
 
         try {
             // We bind this as a foreground service because it is operating directly on the SIM,
@@ -278,6 +297,19 @@
         }
     }
 
+    private void rebindService() {
+        String packageName = getPackageName();
+        // Do nothing if no need to rebind.
+        if (SubscriptionManager.isValidPhoneId(mPhone.getPhoneId())
+                && TextUtils.equals(packageName, mTargetBindingPackageName)) {
+            logd("Service " + packageName + " already bound or being bound.");
+            return;
+        }
+
+        unbindService();
+        bindService(packageName);
+    }
+
     private String getPackageName() {
         String packageName;
         int resourceId;
@@ -312,6 +344,39 @@
         return packageName;
     }
 
+    private String getClassName() {
+        String className;
+        int resourceId;
+        String carrierConfig;
+
+        switch (mTransportType) {
+            case AccessNetworkConstants.TRANSPORT_TYPE_WWAN:
+                resourceId = com.android.internal.R.string.config_wwan_network_service_class;
+                carrierConfig = CarrierConfigManager
+                        .KEY_CARRIER_NETWORK_SERVICE_WWAN_CLASS_OVERRIDE_STRING;
+                break;
+            case AccessNetworkConstants.TRANSPORT_TYPE_WLAN:
+                resourceId = com.android.internal.R.string.config_wlan_network_service_class;
+                carrierConfig = CarrierConfigManager
+                        .KEY_CARRIER_NETWORK_SERVICE_WLAN_CLASS_OVERRIDE_STRING;
+                break;
+            default:
+                throw new IllegalStateException("Transport type not WWAN or WLAN. type="
+                        + mTransportType);
+        }
+
+        // Read class name from resource overlay
+        className = mPhone.getContext().getResources().getString(resourceId);
+
+        PersistableBundle b = mCarrierConfigManager.getConfigForSubId(mPhone.getSubId());
+
+        if (b != null && !TextUtils.isEmpty(b.getString(carrierConfig))) {
+            // If carrier config overrides it, use the one from carrier config
+            className = b.getString(carrierConfig, className);
+        }
+
+        return className;
+    }
     private void logd(String msg) {
         Rlog.d(mTag, msg);
     }
diff --git a/src/java/com/android/internal/telephony/NetworkScanRequestTracker.java b/src/java/com/android/internal/telephony/NetworkScanRequestTracker.java
index ba1081b..6d10a86 100644
--- a/src/java/com/android/internal/telephony/NetworkScanRequestTracker.java
+++ b/src/java/com/android/internal/telephony/NetworkScanRequestTracker.java
@@ -16,14 +16,15 @@
 
 package com.android.internal.telephony;
 
-import static android.os.Binder.withCleanCallingIdentity;
 import static android.telephony.AccessNetworkConstants.AccessNetworkType.EUTRAN;
 import static android.telephony.AccessNetworkConstants.AccessNetworkType.GERAN;
+import static android.telephony.AccessNetworkConstants.AccessNetworkType.NGRAN;
 import static android.telephony.AccessNetworkConstants.AccessNetworkType.UTRAN;
 
 import android.content.Context;
 import android.hardware.radio.V1_0.RadioError;
 import android.os.AsyncResult;
+import android.os.Binder;
 import android.os.Build;
 import android.os.Bundle;
 import android.os.Handler;
@@ -66,10 +67,12 @@
     private static final int EVENT_STOP_NETWORK_SCAN_DONE = 5;
     private static final int CMD_INTERRUPT_NETWORK_SCAN = 6;
     private static final int EVENT_INTERRUPT_NETWORK_SCAN_DONE = 7;
+    private static final int EVENT_MODEM_RESET = 8;
 
     private final Handler mHandler = new Handler() {
         @Override
         public void handleMessage(Message msg) {
+            Log.d(TAG, "Received Event :" + msg.what);
             switch (msg.what) {
                 case CMD_START_NETWORK_SCAN:
                     mScheduler.doStartScan((NetworkScanRequestInfo) msg.obj);
@@ -98,6 +101,14 @@
                 case EVENT_INTERRUPT_NETWORK_SCAN_DONE:
                     mScheduler.interruptScanDone((AsyncResult) msg.obj);
                     break;
+
+                case EVENT_MODEM_RESET:
+                    AsyncResult ar = (AsyncResult) msg.obj;
+                    mScheduler.deleteScanAndMayNotify(
+                            (NetworkScanRequestInfo) ar.userObj,
+                            NetworkScan.ERROR_MODEM_ERROR,
+                            true);
+                    break;
             }
         }
     };
@@ -123,7 +134,8 @@
         }
         for (RadioAccessSpecifier ras : nsri.mRequest.getSpecifiers()) {
             if (ras.getRadioAccessNetwork() != GERAN && ras.getRadioAccessNetwork() != UTRAN
-                    && ras.getRadioAccessNetwork() != EUTRAN) {
+                    && ras.getRadioAccessNetwork() != EUTRAN
+                    && ras.getRadioAccessNetwork() != NGRAN) {
                 return false;
             }
             if (ras.getBands() != null && ras.getBands().length > NetworkScanRequest.MAX_BANDS) {
@@ -178,10 +190,16 @@
      * scan when scan results are restricted due to location privacy.
      */
     public static Set<String> getAllowedMccMncsForLocationRestrictedScan(Context context) {
-        return withCleanCallingIdentity(() -> SubscriptionController.getInstance()
-            .getAvailableSubscriptionInfoList(context.getOpPackageName()).stream()
-            .flatMap(NetworkScanRequestTracker::getAllowableMccMncsFromSubscriptionInfo)
-            .collect(Collectors.toSet()));
+        final long token = Binder.clearCallingIdentity();
+        try {
+            return SubscriptionController.getInstance()
+                    .getAvailableSubscriptionInfoList(context.getOpPackageName(),
+                            context.getAttributionTag()).stream()
+                    .flatMap(NetworkScanRequestTracker::getAllowableMccMncsFromSubscriptionInfo)
+                    .collect(Collectors.toSet());
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
     }
 
     private static Stream<String> getAllowableMccMncsFromSubscriptionInfo(SubscriptionInfo info) {
@@ -525,12 +543,12 @@
         // stopped, a new scan will automatically start with nsri.
         // The new scan can interrupt the live scan only when all the below requirements are met:
         //   1. There is 1 live scan and no other pending scan
-        //   2. The new scan is requested by mobile network setting menu (owned by PHONE process)
+        //   2. The new scan is requested by mobile network setting menu (owned by SYSTEM process)
         //   3. The live scan is not requested by mobile network setting menu
         private synchronized boolean interruptLiveScan(NetworkScanRequestInfo nsri) {
             if (mLiveRequestInfo != null && mPendingRequestInfo == null
-                    && nsri.mUid == Process.PHONE_UID
-                            && mLiveRequestInfo.mUid != Process.PHONE_UID) {
+                    && nsri.mUid == Process.SYSTEM_UID
+                            && mLiveRequestInfo.mUid != Process.SYSTEM_UID) {
                 doInterruptScan(mLiveRequestInfo.mScanId);
                 mPendingRequestInfo = nsri;
                 notifyMessenger(mLiveRequestInfo, TelephonyScanManager.CALLBACK_SCAN_ERROR,
@@ -551,6 +569,7 @@
                 mLiveRequestInfo = nsri;
                 nsri.mPhone.startNetworkScan(nsri.getRequest(),
                         mHandler.obtainMessage(EVENT_START_NETWORK_SCAN_DONE, nsri));
+                nsri.mPhone.mCi.registerForModemReset(mHandler, EVENT_MODEM_RESET, nsri);
                 return true;
             }
             return false;
@@ -570,6 +589,7 @@
                                 null);
                     }
                 }
+                mLiveRequestInfo.mPhone.mCi.unregisterForModemReset(mHandler);
                 mLiveRequestInfo = null;
                 if (mPendingRequestInfo != null) {
                     startNewScan(mPendingRequestInfo);
diff --git a/src/java/com/android/internal/telephony/NetworkTypeController.java b/src/java/com/android/internal/telephony/NetworkTypeController.java
new file mode 100644
index 0000000..c9022c4
--- /dev/null
+++ b/src/java/com/android/internal/telephony/NetworkTypeController.java
@@ -0,0 +1,1041 @@
+/*
+ * Copyright (C) 2020 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;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.AsyncResult;
+import android.os.Message;
+import android.os.PersistableBundle;
+import android.telephony.AccessNetworkConstants;
+import android.telephony.Annotation;
+import android.telephony.CarrierConfigManager;
+import android.telephony.NetworkRegistrationInfo;
+import android.telephony.RadioAccessFamily;
+import android.telephony.ServiceState;
+import android.telephony.SubscriptionManager;
+import android.telephony.TelephonyDisplayInfo;
+import android.telephony.TelephonyManager;
+import android.text.TextUtils;
+
+import com.android.internal.telephony.dataconnection.DcController;
+import com.android.internal.telephony.dataconnection.DcController.PhysicalLinkState;
+import com.android.internal.util.IState;
+import com.android.internal.util.IndentingPrintWriter;
+import com.android.internal.util.State;
+import com.android.internal.util.StateMachine;
+import com.android.telephony.Rlog;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * The NetworkTypeController evaluates the override network type of {@link TelephonyDisplayInfo}
+ * and sends it to {@link DisplayInfoController}. The override network type can replace the signal
+ * icon displayed on the status bar. It is affected by changes in data RAT, NR state, NR frequency,
+ * data activity, physical channel config, and carrier configurations. Based on carrier configs,
+ * NetworkTypeController also allows timers between various 5G states to prevent flickering.
+ */
+public class NetworkTypeController extends StateMachine {
+    private static final boolean DBG = true;
+    private static final String TAG = "NetworkTypeController";
+    private static final String ICON_5G = "5g";
+    private static final String ICON_5G_PLUS = "5g_plus";
+    private static final String STATE_CONNECTED_MMWAVE = "connected_mmwave";
+    private static final String STATE_CONNECTED = "connected";
+    private static final String STATE_NOT_RESTRICTED_RRC_IDLE = "not_restricted_rrc_idle";
+    private static final String STATE_NOT_RESTRICTED_RRC_CON = "not_restricted_rrc_con";
+    private static final String STATE_RESTRICTED = "restricted";
+    private static final String STATE_ANY = "any";
+    private static final String STATE_LEGACY = "legacy";
+    private static final String[] ALL_STATES = { STATE_CONNECTED_MMWAVE, STATE_CONNECTED,
+            STATE_NOT_RESTRICTED_RRC_IDLE, STATE_NOT_RESTRICTED_RRC_CON, STATE_RESTRICTED,
+            STATE_LEGACY };
+
+    /** Stop all timers and go to current state. */
+    public static final int EVENT_UPDATE = 0;
+    /** Quit after processing all existing messages. */
+    public static final int EVENT_QUIT = 1;
+    private static final int EVENT_DATA_RAT_CHANGED = 2;
+    private static final int EVENT_NR_STATE_CHANGED = 3;
+    private static final int EVENT_NR_FREQUENCY_CHANGED = 4;
+    private static final int EVENT_PHYSICAL_LINK_STATE_CHANGED = 5;
+    private static final int EVENT_PHYSICAL_CHANNEL_CONFIG_NOTIF_CHANGED = 6;
+    private static final int EVENT_CARRIER_CONFIG_CHANGED = 7;
+    private static final int EVENT_PRIMARY_TIMER_EXPIRED = 8;
+    private static final int EVENT_SECONDARY_TIMER_EXPIRED = 9;
+    private static final int EVENT_RADIO_OFF_OR_UNAVAILABLE = 10;
+    private static final int EVENT_PREFERRED_NETWORK_MODE_CHANGED = 11;
+    private static final int EVENT_INITIALIZE = 12;
+    // events that don't reset the timer
+    private static final int[] ALL_EVENTS = { EVENT_DATA_RAT_CHANGED, EVENT_NR_STATE_CHANGED,
+            EVENT_NR_FREQUENCY_CHANGED, EVENT_PHYSICAL_LINK_STATE_CHANGED,
+            EVENT_PHYSICAL_CHANNEL_CONFIG_NOTIF_CHANGED, EVENT_PRIMARY_TIMER_EXPIRED,
+            EVENT_SECONDARY_TIMER_EXPIRED};
+
+    private static final String[] sEvents = new String[EVENT_INITIALIZE + 1];
+    static {
+        sEvents[EVENT_UPDATE] = "EVENT_UPDATE";
+        sEvents[EVENT_QUIT] = "EVENT_QUIT";
+        sEvents[EVENT_DATA_RAT_CHANGED] = "EVENT_DATA_RAT_CHANGED";
+        sEvents[EVENT_NR_STATE_CHANGED] = "EVENT_NR_STATE_CHANGED";
+        sEvents[EVENT_NR_FREQUENCY_CHANGED] = "EVENT_NR_FREQUENCY_CHANGED";
+        sEvents[EVENT_PHYSICAL_LINK_STATE_CHANGED] = "EVENT_PHYSICAL_LINK_STATE_CHANGED";
+        sEvents[EVENT_PHYSICAL_CHANNEL_CONFIG_NOTIF_CHANGED] =
+                "EVENT_PHYSICAL_CHANNEL_CONFIG_NOTIF_CHANGED";
+        sEvents[EVENT_CARRIER_CONFIG_CHANGED] = "EVENT_CARRIER_CONFIG_CHANGED";
+        sEvents[EVENT_PRIMARY_TIMER_EXPIRED] = "EVENT_PRIMARY_TIMER_EXPIRED";
+        sEvents[EVENT_SECONDARY_TIMER_EXPIRED] = "EVENT_SECONDARY_TIMER_EXPIRED";
+        sEvents[EVENT_RADIO_OFF_OR_UNAVAILABLE] = "EVENT_RADIO_OFF_OR_UNAVAILABLE";
+        sEvents[EVENT_PREFERRED_NETWORK_MODE_CHANGED] = "EVENT_PREFERRED_NETWORK_MODE_CHANGED";
+        sEvents[EVENT_INITIALIZE] = "EVENT_INITIALIZE";
+    }
+
+    private final Phone mPhone;
+    private final DisplayInfoController mDisplayInfoController;
+    private final BroadcastReceiver mIntentReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            if (intent.getAction().equals(CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED)
+                    && intent.getIntExtra(SubscriptionManager.EXTRA_SLOT_INDEX,
+                    SubscriptionManager.INVALID_PHONE_INDEX) == mPhone.getPhoneId()
+                    && !intent.getBooleanExtra(CarrierConfigManager.EXTRA_REBROADCAST_ON_UNLOCK,
+                    false)) {
+                sendMessage(EVENT_CARRIER_CONFIG_CHANGED);
+            }
+        }
+    };
+
+    private Map<String, OverrideTimerRule> mOverrideTimerRules = new HashMap<>();
+    private String mLteEnhancedPattern = "";
+    private int mOverrideNetworkType;
+    private boolean mIsPhysicalChannelConfigOn;
+    private boolean mIsPrimaryTimerActive;
+    private boolean mIsSecondaryTimerActive;
+    private String mPrimaryTimerState;
+    private String mSecondaryTimerState;
+    private String mPreviousState;
+    private @PhysicalLinkState int mPhysicalLinkState;
+
+    /**
+     * NetworkTypeController constructor.
+     *
+     * @param phone Phone object.
+     * @param displayInfoController DisplayInfoController to send override network types to.
+     */
+    public NetworkTypeController(Phone phone, DisplayInfoController displayInfoController) {
+        super(TAG, displayInfoController);
+        mPhone = phone;
+        mDisplayInfoController = displayInfoController;
+        mOverrideNetworkType = TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NONE;
+        mIsPhysicalChannelConfigOn = true;
+        addState(mDefaultState);
+        addState(mLegacyState, mDefaultState);
+        addState(mIdleState, mDefaultState);
+        addState(mLteConnectedState, mDefaultState);
+        addState(mNrConnectedState, mDefaultState);
+        setInitialState(mDefaultState);
+        start();
+        sendMessage(EVENT_INITIALIZE);
+    }
+
+    /**
+     * @return The current override network type, used to create TelephonyDisplayInfo in
+     * DisplayInfoController.
+     */
+    public @Annotation.OverrideNetworkType int getOverrideNetworkType() {
+        return mOverrideNetworkType;
+    }
+
+    private void registerForAllEvents() {
+        mPhone.registerForRadioOffOrNotAvailable(getHandler(),
+                EVENT_RADIO_OFF_OR_UNAVAILABLE, null);
+        mPhone.registerForPreferredNetworkTypeChanged(getHandler(),
+                EVENT_PREFERRED_NETWORK_MODE_CHANGED, null);
+        mPhone.getServiceStateTracker().registerForDataRegStateOrRatChanged(
+                AccessNetworkConstants.TRANSPORT_TYPE_WWAN, getHandler(),
+                EVENT_DATA_RAT_CHANGED, null);
+        mPhone.getDcTracker(AccessNetworkConstants.TRANSPORT_TYPE_WWAN)
+                .registerForPhysicalLinkStateChanged(getHandler(),
+                        EVENT_PHYSICAL_LINK_STATE_CHANGED);
+        mPhone.getServiceStateTracker().registerForNrStateChanged(getHandler(),
+                EVENT_NR_STATE_CHANGED, null);
+        mPhone.getServiceStateTracker().registerForNrFrequencyChanged(getHandler(),
+                EVENT_NR_FREQUENCY_CHANGED, null);
+        mPhone.getDeviceStateMonitor().registerForPhysicalChannelConfigNotifChanged(getHandler(),
+                EVENT_PHYSICAL_CHANNEL_CONFIG_NOTIF_CHANGED, null);
+        IntentFilter filter = new IntentFilter();
+        filter.addAction(CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED);
+        mPhone.getContext().registerReceiver(mIntentReceiver, filter, null, mPhone);
+    }
+
+    private void unRegisterForAllEvents() {
+        mPhone.unregisterForRadioOffOrNotAvailable(getHandler());
+        mPhone.unregisterForPreferredNetworkTypeChanged(getHandler());
+        mPhone.getServiceStateTracker().unregisterForDataRegStateOrRatChanged(
+                AccessNetworkConstants.TRANSPORT_TYPE_WWAN, getHandler());
+        mPhone.getServiceStateTracker().unregisterForNrStateChanged(getHandler());
+        mPhone.getServiceStateTracker().unregisterForNrFrequencyChanged(getHandler());
+        mPhone.getDeviceStateMonitor().unregisterForPhysicalChannelConfigNotifChanged(getHandler());
+        mPhone.getContext().unregisterReceiver(mIntentReceiver);
+    }
+
+    private void parseCarrierConfigs() {
+        String nrIconConfiguration = CarrierConfigManager.getDefaultConfig().getString(
+                CarrierConfigManager.KEY_5G_ICON_CONFIGURATION_STRING);
+        String overrideTimerRule = CarrierConfigManager.getDefaultConfig().getString(
+                CarrierConfigManager.KEY_5G_ICON_DISPLAY_GRACE_PERIOD_STRING);
+        String overrideSecondaryTimerRule = CarrierConfigManager.getDefaultConfig().getString(
+                CarrierConfigManager.KEY_5G_ICON_DISPLAY_SECONDARY_GRACE_PERIOD_STRING);
+        mLteEnhancedPattern = CarrierConfigManager.getDefaultConfig().getString(
+                CarrierConfigManager.KEY_SHOW_CARRIER_DATA_ICON_PATTERN_STRING);
+
+        CarrierConfigManager configManager = (CarrierConfigManager) mPhone.getContext()
+                .getSystemService(Context.CARRIER_CONFIG_SERVICE);
+        if (configManager != null) {
+            PersistableBundle b = configManager.getConfigForSubId(mPhone.getSubId());
+            if (b != null) {
+                if (b.getString(CarrierConfigManager.KEY_5G_ICON_CONFIGURATION_STRING) != null) {
+                    nrIconConfiguration = b.getString(
+                            CarrierConfigManager.KEY_5G_ICON_CONFIGURATION_STRING);
+                }
+                if (b.getString(CarrierConfigManager
+                        .KEY_5G_ICON_DISPLAY_GRACE_PERIOD_STRING) != null) {
+                    overrideTimerRule = b.getString(
+                            CarrierConfigManager.KEY_5G_ICON_DISPLAY_GRACE_PERIOD_STRING);
+                }
+                if (b.getString(CarrierConfigManager
+                        .KEY_5G_ICON_DISPLAY_SECONDARY_GRACE_PERIOD_STRING) != null) {
+                    overrideSecondaryTimerRule = b.getString(
+                            CarrierConfigManager.KEY_5G_ICON_DISPLAY_SECONDARY_GRACE_PERIOD_STRING);
+                }
+                if (b.getString(CarrierConfigManager
+                        .KEY_SHOW_CARRIER_DATA_ICON_PATTERN_STRING) != null) {
+                    mLteEnhancedPattern = b.getString(
+                            CarrierConfigManager.KEY_SHOW_CARRIER_DATA_ICON_PATTERN_STRING);
+                }
+            }
+        }
+        createTimerRules(nrIconConfiguration, overrideTimerRule, overrideSecondaryTimerRule);
+    }
+
+    private void createTimerRules(String icons, String timers, String secondaryTimers) {
+        Map<String, OverrideTimerRule> tempRules = new HashMap<>();
+        if (!TextUtils.isEmpty(icons)) {
+            // Format: "STATE:ICON,STATE2:ICON2"
+            for (String pair : icons.trim().split(",")) {
+                String[] kv = (pair.trim().toLowerCase()).split(":");
+                if (kv.length != 2) {
+                    if (DBG) loge("Invalid 5G icon configuration, config = " + pair);
+                    continue;
+                }
+                int icon = TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NONE;
+                if (kv[1].equals(ICON_5G)) {
+                    icon = TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_NSA;
+                } else if (kv[1].equals(ICON_5G_PLUS)) {
+                    icon = TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_NSA_MMWAVE;
+                } else {
+                    if (DBG) loge("Invalid 5G icon = " + kv[1]);
+                }
+                tempRules.put(kv[0], new OverrideTimerRule(kv[0], icon));
+            }
+        }
+        // Ensure all states have an associated OverrideTimerRule and icon
+        for (String state : ALL_STATES) {
+            if (!tempRules.containsKey(state)) {
+                tempRules.put(state, new OverrideTimerRule(
+                        state, TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NONE));
+            }
+        }
+
+        if (!TextUtils.isEmpty(timers)) {
+            // Format: "FROM_STATE,TO_STATE,DURATION;FROM_STATE_2,TO_STATE_2,DURATION_2"
+            for (String triple : timers.trim().split(";")) {
+                String[] kv = (triple.trim().toLowerCase()).split(",");
+                if (kv.length != 3) {
+                    if (DBG) loge("Invalid 5G icon timer configuration, config = " + triple);
+                    continue;
+                }
+                int duration;
+                try {
+                    duration = Integer.parseInt(kv[2]);
+                } catch (NumberFormatException e) {
+                    continue;
+                }
+                if (kv[0].equals(STATE_ANY)) {
+                    for (String state : ALL_STATES) {
+                        OverrideTimerRule node = tempRules.get(state);
+                        node.addTimer(kv[1], duration);
+                    }
+                } else {
+                    OverrideTimerRule node = tempRules.get(kv[0]);
+                    node.addTimer(kv[1], duration);
+                }
+            }
+        }
+
+        if (!TextUtils.isEmpty(secondaryTimers)) {
+            // Format: "PRIMARY_STATE,TO_STATE,DURATION;PRIMARY_STATE_2,TO_STATE_2,DURATION_2"
+            for (String triple : secondaryTimers.trim().split(";")) {
+                String[] kv = (triple.trim().toLowerCase()).split(",");
+                if (kv.length != 3) {
+                    if (DBG) {
+                        loge("Invalid 5G icon secondary timer configuration, config = " + triple);
+                    }
+                    continue;
+                }
+                int duration;
+                try {
+                    duration = Integer.parseInt(kv[2]);
+                } catch (NumberFormatException e) {
+                    continue;
+                }
+                if (kv[0].equals(STATE_ANY)) {
+                    for (String state : ALL_STATES) {
+                        OverrideTimerRule node = tempRules.get(state);
+                        node.addSecondaryTimer(kv[1], duration);
+                    }
+                } else {
+                    OverrideTimerRule node = tempRules.get(kv[0]);
+                    node.addSecondaryTimer(kv[1], duration);
+                }
+            }
+        }
+
+        mOverrideTimerRules = tempRules;
+        if (DBG) log("mOverrideTimerRules: " + mOverrideTimerRules);
+    }
+
+    private void updateOverrideNetworkType() {
+        if (mIsPrimaryTimerActive || mIsSecondaryTimerActive) {
+            if (DBG) log("Skip updating override network type since timer is active.");
+            return;
+        }
+        mOverrideNetworkType = getCurrentOverrideNetworkType();
+        mDisplayInfoController.updateTelephonyDisplayInfo();
+    }
+
+    private @Annotation.OverrideNetworkType int getCurrentOverrideNetworkType() {
+        int displayNetworkType = TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NONE;
+        int dataNetworkType = mPhone.getServiceState().getDataNetworkType();
+        // NR display is not accurate when physical channel config notifications are off
+        if (mIsPhysicalChannelConfigOn
+                && (mPhone.getServiceState().getNrState() != NetworkRegistrationInfo.NR_STATE_NONE
+                || dataNetworkType == TelephonyManager.NETWORK_TYPE_NR)) {
+            // Process NR display network type
+            displayNetworkType = getNrDisplayType();
+            if (displayNetworkType == TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NONE) {
+                // Use LTE values if 5G values aren't defined
+                displayNetworkType = getLteDisplayType();
+            }
+        } else if (isLte(dataNetworkType)) {
+            // Process LTE display network type
+            displayNetworkType = getLteDisplayType();
+        }
+        return displayNetworkType;
+    }
+
+    private @Annotation.OverrideNetworkType int getNrDisplayType() {
+        // Don't show 5G icon if preferred network type does not include 5G
+        if ((RadioAccessFamily.getRafFromNetworkType(mPhone.getCachedPreferredNetworkType())
+                & TelephonyManager.NETWORK_TYPE_BITMASK_NR) == 0) {
+            return TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NONE;
+        }
+        // Icon display keys in order of priority
+        List<String> keys = new ArrayList<>();
+        // TODO: Update for NR SA
+        switch (mPhone.getServiceState().getNrState()) {
+            case NetworkRegistrationInfo.NR_STATE_CONNECTED:
+                if (isNrMmwave()) {
+                    keys.add(STATE_CONNECTED_MMWAVE);
+                }
+                keys.add(STATE_CONNECTED);
+                break;
+            case NetworkRegistrationInfo.NR_STATE_NOT_RESTRICTED:
+                keys.add(isPhysicalLinkActive() ? STATE_NOT_RESTRICTED_RRC_CON
+                        : STATE_NOT_RESTRICTED_RRC_IDLE);
+                break;
+            case NetworkRegistrationInfo.NR_STATE_RESTRICTED:
+                keys.add(STATE_RESTRICTED);
+                break;
+        }
+
+        for (String key : keys) {
+            OverrideTimerRule rule = mOverrideTimerRules.get(key);
+            if (rule != null && rule.mOverrideType
+                    != TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NONE) {
+                return rule.mOverrideType;
+            }
+        }
+        return TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NONE;
+    }
+
+    private @Annotation.OverrideNetworkType int getLteDisplayType() {
+        int value = TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NONE;
+        if (mPhone.getServiceState().getDataNetworkType() == TelephonyManager.NETWORK_TYPE_LTE_CA
+                || mPhone.getServiceState().isUsingCarrierAggregation()) {
+            value = TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_LTE_CA;
+        }
+        if (isLteEnhancedAvailable()) {
+            value = TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_LTE_ADVANCED_PRO;
+        }
+        return value;
+    }
+
+    private boolean isLteEnhancedAvailable() {
+        if (TextUtils.isEmpty(mLteEnhancedPattern)) {
+            return false;
+        }
+        Pattern stringPattern = Pattern.compile(mLteEnhancedPattern);
+        for (String opName : new String[] {mPhone.getServiceState().getOperatorAlphaLongRaw(),
+                mPhone.getServiceState().getOperatorAlphaShortRaw()}) {
+            if (!TextUtils.isEmpty(opName)) {
+                Matcher matcher = stringPattern.matcher(opName);
+                if (matcher.find()) {
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
+
+    /**
+     * The parent state for all other states.
+     */
+    private final class DefaultState extends State {
+        @Override
+        public boolean processMessage(Message msg) {
+            if (DBG) log("DefaultState: process " + getEventName(msg.what));
+            switch (msg.what) {
+                case EVENT_UPDATE:
+                    resetAllTimers();
+                    transitionToCurrentState();
+                    break;
+                case EVENT_QUIT:
+                    resetAllTimers();
+                    unRegisterForAllEvents();
+                    quit();
+                    break;
+                case EVENT_INITIALIZE:
+                    // The reason that we do it here is because some of the works below requires
+                    // other modules (e.g. DcTracker, ServiceStateTracker), which is not created
+                    // yet when NetworkTypeController is created.
+                    registerForAllEvents();
+                    parseCarrierConfigs();
+                    break;
+                case EVENT_DATA_RAT_CHANGED:
+                case EVENT_NR_STATE_CHANGED:
+                case EVENT_NR_FREQUENCY_CHANGED:
+                    // ignored
+                    break;
+                case EVENT_PHYSICAL_LINK_STATE_CHANGED:
+                    AsyncResult ar = (AsyncResult) msg.obj;
+                    mPhysicalLinkState = (int) ar.result;
+                    break;
+                case EVENT_PHYSICAL_CHANNEL_CONFIG_NOTIF_CHANGED:
+                    AsyncResult result = (AsyncResult) msg.obj;
+                    mIsPhysicalChannelConfigOn = (boolean) result.result;
+                    if (DBG) {
+                        log("mIsPhysicalChannelConfigOn changed to: " + mIsPhysicalChannelConfigOn);
+                    }
+                    for (int event : ALL_EVENTS) {
+                        removeMessages(event);
+                    }
+                    if (!mIsPhysicalChannelConfigOn) {
+                        resetAllTimers();
+                    }
+                    transitionToCurrentState();
+                    break;
+                case EVENT_CARRIER_CONFIG_CHANGED:
+                    for (int event : ALL_EVENTS) {
+                        removeMessages(event);
+                    }
+                    parseCarrierConfigs();
+                    resetAllTimers();
+                    transitionToCurrentState();
+                    break;
+                case EVENT_PRIMARY_TIMER_EXPIRED:
+                    transitionWithSecondaryTimerTo((IState) msg.obj);
+                    break;
+                case EVENT_SECONDARY_TIMER_EXPIRED:
+                    mIsSecondaryTimerActive = false;
+                    mSecondaryTimerState = "";
+                    updateTimers();
+                    updateOverrideNetworkType();
+                    break;
+                case EVENT_RADIO_OFF_OR_UNAVAILABLE:
+                    resetAllTimers();
+                    transitionTo(mLegacyState);
+                    break;
+                case EVENT_PREFERRED_NETWORK_MODE_CHANGED:
+                    resetAllTimers();
+                    transitionToCurrentState();
+                    break;
+                default:
+                    throw new RuntimeException("Received invalid event: " + msg.what);
+            }
+            return HANDLED;
+        }
+    }
+
+    private final DefaultState mDefaultState = new DefaultState();
+
+    /**
+     * Device does not have NR available, due to any of the below reasons:
+     * <ul>
+     *   <li> LTE cell does not support EN-DC
+     *   <li> LTE cell supports EN-DC, but the use of NR is restricted
+     *   <li> Data network type is not LTE, NR NSA, or NR SA
+     * </ul>
+     * This is the initial state.
+     */
+    private final class LegacyState extends State {
+        private Boolean mIsNrRestricted = false;
+
+        @Override
+        public void enter() {
+            if (DBG) log("Entering LegacyState");
+            updateTimers();
+            updateOverrideNetworkType();
+            if (!mIsPrimaryTimerActive && !mIsSecondaryTimerActive) {
+                mIsNrRestricted = isNrRestricted();
+                mPreviousState = getName();
+            }
+        }
+
+        @Override
+        public boolean processMessage(Message msg) {
+            if (DBG) log("LegacyState: process " + getEventName(msg.what));
+            updateTimers();
+            int rat = mPhone.getServiceState().getDataNetworkType();
+            switch (msg.what) {
+                case EVENT_DATA_RAT_CHANGED:
+                    if (rat == TelephonyManager.NETWORK_TYPE_NR || isLte(rat) && isNrConnected()) {
+                        transitionTo(mNrConnectedState);
+                    } else if (isLte(rat) && isNrNotRestricted()) {
+                        transitionWithTimerTo(isPhysicalLinkActive()
+                                ? mLteConnectedState : mIdleState);
+                    } else {
+                        updateOverrideNetworkType();
+                    }
+                    mIsNrRestricted = isNrRestricted();
+                    break;
+                case EVENT_NR_STATE_CHANGED:
+                    if (isNrConnected()) {
+                        transitionTo(mNrConnectedState);
+                    } else if (isLte(rat) && isNrNotRestricted()) {
+                        transitionWithTimerTo(isPhysicalLinkActive()
+                                ? mLteConnectedState : mIdleState);
+                    } else if (isLte(rat) && isNrRestricted()) {
+                        updateOverrideNetworkType();
+                    }
+                    mIsNrRestricted = isNrRestricted();
+                    break;
+                case EVENT_NR_FREQUENCY_CHANGED:
+                    // ignored
+                    break;
+                default:
+                    return NOT_HANDLED;
+            }
+            if (!mIsPrimaryTimerActive && !mIsSecondaryTimerActive) {
+                mPreviousState = getName();
+            }
+            return HANDLED;
+        }
+
+        @Override
+        public String getName() {
+            return mIsNrRestricted  ? STATE_RESTRICTED : STATE_LEGACY;
+        }
+    }
+
+    private final LegacyState mLegacyState = new LegacyState();
+
+    /**
+     * Device does not have any physical connection with the cell (RRC idle).
+     */
+    private final class IdleState extends State {
+        @Override
+        public void enter() {
+            if (DBG) log("Entering IdleState");
+            updateTimers();
+            updateOverrideNetworkType();
+            if (!mIsPrimaryTimerActive && !mIsSecondaryTimerActive) {
+                mPreviousState = getName();
+            }
+        }
+
+        @Override
+        public boolean processMessage(Message msg) {
+            if (DBG) log("IdleState: process " + getEventName(msg.what));
+            updateTimers();
+            switch (msg.what) {
+                case EVENT_DATA_RAT_CHANGED:
+                    int rat = mPhone.getServiceState().getDataNetworkType();
+                    if (rat == TelephonyManager.NETWORK_TYPE_NR) {
+                        transitionTo(mNrConnectedState);
+                    } else if (!isLte(rat) || !isNrNotRestricted()) {
+                        transitionWithTimerTo(mLegacyState);
+                    }
+                    break;
+                case EVENT_NR_STATE_CHANGED:
+                    if (isNrConnected()) {
+                        transitionTo(mNrConnectedState);
+                    } else if (!isNrNotRestricted()) {
+                        transitionWithTimerTo(mLegacyState);
+                    }
+                    break;
+                case EVENT_NR_FREQUENCY_CHANGED:
+                    // ignore
+                    break;
+                case EVENT_PHYSICAL_LINK_STATE_CHANGED:
+                    AsyncResult ar = (AsyncResult) msg.obj;
+                    mPhysicalLinkState = (int) ar.result;
+                    if (isNrNotRestricted()) {
+                        // NOT_RESTRICTED_RRC_IDLE -> NOT_RESTRICTED_RRC_CON
+                        if (isPhysicalLinkActive()) {
+                            transitionWithTimerTo(mLteConnectedState);
+                        }
+                    } else {
+                        log("NR state changed. Sending EVENT_NR_STATE_CHANGED");
+                        sendMessage(EVENT_NR_STATE_CHANGED);
+                    }
+                    break;
+                default:
+                    return NOT_HANDLED;
+            }
+            if (!mIsPrimaryTimerActive && !mIsSecondaryTimerActive) {
+                mPreviousState = getName();
+            }
+            return HANDLED;
+        }
+
+        @Override
+        public String getName() {
+            return STATE_NOT_RESTRICTED_RRC_IDLE;
+        }
+    }
+
+    private final IdleState mIdleState = new IdleState();
+
+    /**
+     * Device is connected to LTE as the primary cell (RRC connected).
+     */
+    private final class LteConnectedState extends State {
+        @Override
+        public void enter() {
+            if (DBG) log("Entering LteConnectedState");
+            updateTimers();
+            updateOverrideNetworkType();
+            if (!mIsPrimaryTimerActive && !mIsSecondaryTimerActive) {
+                mPreviousState = getName();
+            }
+        }
+
+        @Override
+        public boolean processMessage(Message msg) {
+            if (DBG) log("LteConnectedState: process " + getEventName(msg.what));
+            updateTimers();
+            switch (msg.what) {
+                case EVENT_DATA_RAT_CHANGED:
+                    int rat = mPhone.getServiceState().getDataNetworkType();
+                    if (rat == TelephonyManager.NETWORK_TYPE_NR) {
+                        transitionTo(mNrConnectedState);
+                    } else if (!isLte(rat) || !isNrNotRestricted()) {
+                        transitionWithTimerTo(mLegacyState);
+                    }
+                    break;
+                case EVENT_NR_STATE_CHANGED:
+                    if (isNrConnected()) {
+                        transitionTo(mNrConnectedState);
+                    } else if (!isNrNotRestricted()) {
+                        transitionWithTimerTo(mLegacyState);
+                    }
+                    break;
+                case EVENT_NR_FREQUENCY_CHANGED:
+                    // ignore
+                    break;
+                case EVENT_PHYSICAL_LINK_STATE_CHANGED:
+                    AsyncResult ar = (AsyncResult) msg.obj;
+                    mPhysicalLinkState = (int) ar.result;
+                    if (isNrNotRestricted()) {
+                        // NOT_RESTRICTED_RRC_CON -> NOT_RESTRICTED_RRC_IDLE
+                        if (!isPhysicalLinkActive()) {
+                            transitionWithTimerTo(mIdleState);
+                        }
+                    } else {
+                        log("NR state changed. Sending EVENT_NR_STATE_CHANGED");
+                        sendMessage(EVENT_NR_STATE_CHANGED);
+                    }
+                    break;
+                default:
+                    return NOT_HANDLED;
+            }
+            if (!mIsPrimaryTimerActive && !mIsSecondaryTimerActive) {
+                mPreviousState = getName();
+            }
+            return HANDLED;
+        }
+
+        @Override
+        public String getName() {
+            return STATE_NOT_RESTRICTED_RRC_CON;
+        }
+    }
+
+    private final LteConnectedState mLteConnectedState = new LteConnectedState();
+
+    /**
+     * Device is connected to 5G NR as the secondary cell.
+     */
+    private final class NrConnectedState extends State {
+        private Boolean mIsNrMmwave = false;
+
+        @Override
+        public void enter() {
+            if (DBG) log("Entering NrConnectedState");
+            updateTimers();
+            updateOverrideNetworkType();
+            if (!mIsPrimaryTimerActive && !mIsSecondaryTimerActive) {
+                mIsNrMmwave = isNrMmwave();
+                mPreviousState = getName();
+            }
+        }
+
+        @Override
+        public boolean processMessage(Message msg) {
+            if (DBG) log("NrConnectedState: process " + getEventName(msg.what));
+            updateTimers();
+            int rat = mPhone.getServiceState().getDataNetworkType();
+            switch (msg.what) {
+                case EVENT_DATA_RAT_CHANGED:
+                    if (rat == TelephonyManager.NETWORK_TYPE_NR || isLte(rat) && isNrConnected()) {
+                        updateOverrideNetworkType();
+                    } else if (isLte(rat) && isNrNotRestricted()) {
+                        transitionWithTimerTo(isPhysicalLinkActive()
+                                ? mLteConnectedState : mIdleState);
+                    } else {
+                        transitionWithTimerTo(mLegacyState);
+                    }
+                    break;
+                case EVENT_NR_STATE_CHANGED:
+                    if (isLte(rat) && isNrNotRestricted()) {
+                        transitionWithTimerTo(isPhysicalLinkActive()
+                                ? mLteConnectedState : mIdleState);
+                    } else if (rat != TelephonyManager.NETWORK_TYPE_NR && !isNrConnected()) {
+                        transitionWithTimerTo(mLegacyState);
+                    }
+                    break;
+                case EVENT_NR_FREQUENCY_CHANGED:
+                    if (!isNrConnected()) {
+                        log("NR state changed. Sending EVENT_NR_STATE_CHANGED");
+                        sendMessage(EVENT_NR_STATE_CHANGED);
+                        break;
+                    }
+                    if (!isNrMmwave()) {
+                        // STATE_CONNECTED_MMWAVE -> STATE_CONNECTED
+                        transitionWithTimerTo(mNrConnectedState);
+                    } else {
+                        // STATE_CONNECTED -> STATE_CONNECTED_MMWAVE
+                        transitionTo(mNrConnectedState);
+                    }
+                    mIsNrMmwave = isNrMmwave();
+                    break;
+                case EVENT_PHYSICAL_LINK_STATE_CHANGED:
+                    AsyncResult ar = (AsyncResult) msg.obj;
+                    mPhysicalLinkState = (int) ar.result;
+                    if (!isNrConnected()) {
+                        log("NR state changed. Sending EVENT_NR_STATE_CHANGED");
+                        sendMessage(EVENT_NR_STATE_CHANGED);
+                    }
+                    break;
+                default:
+                    return NOT_HANDLED;
+            }
+            if (!mIsPrimaryTimerActive && !mIsSecondaryTimerActive) {
+                mPreviousState = getName();
+            }
+            return HANDLED;
+        }
+
+        @Override
+        public String getName() {
+            return mIsNrMmwave ? STATE_CONNECTED_MMWAVE : STATE_CONNECTED;
+        }
+    }
+
+    private final NrConnectedState mNrConnectedState = new NrConnectedState();
+
+    private void transitionWithTimerTo(IState destState) {
+        String destName = destState.getName();
+        OverrideTimerRule rule = mOverrideTimerRules.get(mPreviousState);
+        if (rule != null && rule.getTimer(destName) > 0) {
+            if (DBG) log("Primary timer started for state: " + mPreviousState);
+            mPrimaryTimerState = mPreviousState;
+            mPreviousState = getCurrentState().getName();
+            mIsPrimaryTimerActive = true;
+            sendMessageDelayed(EVENT_PRIMARY_TIMER_EXPIRED, destState,
+                    rule.getTimer(destName) * 1000);
+        }
+        transitionTo(destState);
+    }
+
+    private void transitionWithSecondaryTimerTo(IState destState) {
+        String currentName = getCurrentState().getName();
+        OverrideTimerRule rule = mOverrideTimerRules.get(mPrimaryTimerState);
+        if (rule != null && rule.getSecondaryTimer(currentName) > 0) {
+            if (DBG) log("Secondary timer started for state: " + currentName);
+            mSecondaryTimerState = currentName;
+            mPreviousState = currentName;
+            mIsSecondaryTimerActive = true;
+            sendMessageDelayed(EVENT_SECONDARY_TIMER_EXPIRED, destState,
+                    rule.getSecondaryTimer(currentName) * 1000);
+        }
+        mIsPrimaryTimerActive = false;
+        transitionTo(getCurrentState());
+    }
+
+    private void transitionToCurrentState() {
+        int dataRat = mPhone.getServiceState().getDataNetworkType();
+        IState transitionState;
+        if (dataRat == TelephonyManager.NETWORK_TYPE_NR || isNrConnected()) {
+            transitionState = mNrConnectedState;
+            mPreviousState = isNrMmwave() ? STATE_CONNECTED_MMWAVE : STATE_CONNECTED;
+        } else if (isLte(dataRat) && isNrNotRestricted()) {
+            if (isPhysicalLinkActive()) {
+                transitionState = mLteConnectedState;
+                mPreviousState = STATE_NOT_RESTRICTED_RRC_CON;
+            } else {
+                transitionState = mIdleState;
+                mPreviousState = STATE_NOT_RESTRICTED_RRC_IDLE;
+            }
+        } else {
+            transitionState = mLegacyState;
+            mPreviousState = isNrRestricted() ? STATE_RESTRICTED : STATE_LEGACY;
+        }
+        if (!transitionState.equals(getCurrentState())) {
+            transitionTo(transitionState);
+        } else {
+            updateOverrideNetworkType();
+        }
+    }
+
+    private void updateTimers() {
+        String currentState = getCurrentState().getName();
+
+        if (mIsPrimaryTimerActive && getOverrideNetworkType() == getCurrentOverrideNetworkType()) {
+            // remove primary timer if device goes back to the original icon
+            if (DBG) {
+                log("Remove primary timer since icon of primary state and current icon equal: "
+                        + mPrimaryTimerState);
+            }
+            removeMessages(EVENT_PRIMARY_TIMER_EXPIRED);
+            mIsPrimaryTimerActive = false;
+            mPrimaryTimerState = "";
+        }
+
+        if (mIsSecondaryTimerActive && !mSecondaryTimerState.equals(currentState)) {
+            // remove secondary timer if devices is no longer in secondary timer state
+            if (DBG) {
+                log("Remove secondary timer since current state (" +  currentState
+                        + ") is no longer secondary timer state (" + mSecondaryTimerState + ").");
+            }
+            removeMessages(EVENT_SECONDARY_TIMER_EXPIRED);
+            mIsSecondaryTimerActive = false;
+            mSecondaryTimerState = "";
+        }
+
+        if (currentState.equals(STATE_CONNECTED_MMWAVE)) {
+            resetAllTimers();
+        }
+    }
+
+    private void resetAllTimers() {
+        removeMessages(EVENT_PRIMARY_TIMER_EXPIRED);
+        removeMessages(EVENT_SECONDARY_TIMER_EXPIRED);
+        mIsPrimaryTimerActive = false;
+        mIsSecondaryTimerActive = false;
+        mPrimaryTimerState = "";
+        mSecondaryTimerState = "";
+    }
+
+    /**
+     * Private class defining timer rules between states to prevent flickering. These rules are
+     * created in {@link #parseCarrierConfigs()} based on various carrier configs.
+     */
+    private class OverrideTimerRule {
+        /** The 5G state this timer rule applies for. See {@link #ALL_STATES}. */
+        final String mState;
+
+        /**
+         * The override network type associated with this 5G state. This is the icon that will be
+         * displayed on the status bar. An override type of NONE will display the LTE value instead.
+         */
+        final int mOverrideType;
+
+        /**
+         * A map of destination states and associated timers. If the 5G state changes from mState
+         * to the destination state, keep the override type until either the primary timer expires
+         * or mState is regained.
+         */
+        final Map<String, Integer> mPrimaryTimers;
+
+        /**
+         * A map of secondary states and associated timers. After the primary timer expires, keep
+         * the override type until either the secondary timer expires or the device is no longer in
+         * the secondary state.
+         */
+        final Map<String, Integer> mSecondaryTimers;
+
+        OverrideTimerRule(String state, int overrideType) {
+            mState = state;
+            mOverrideType = overrideType;
+            mPrimaryTimers = new HashMap<>();
+            mSecondaryTimers = new HashMap<>();
+        }
+
+        /**
+         * Add a primary timer.
+         * @param destination Transitions from mState to the destination state.
+         * @param duration How long to keep the override type after transition to destination state.
+         */
+        public void addTimer(String destination, int duration) {
+            mPrimaryTimers.put(destination, duration);
+        }
+
+        /**
+         * Add a secondary timer
+         * @param secondaryState Stays in secondaryState after primary timer expires.
+         * @param duration How long to keep the override type while in secondaryState.
+         */
+        public void addSecondaryTimer(String secondaryState, int duration) {
+            mSecondaryTimers.put(secondaryState, duration);
+        }
+
+        /**
+         * @return Primary timer duration from mState to destination state, or 0 if not defined.
+         */
+        public int getTimer(String destination) {
+            Integer timer = mPrimaryTimers.get(destination);
+            timer = timer == null ? mPrimaryTimers.get(STATE_ANY) : timer;
+            return timer == null ? 0 : timer;
+        }
+
+        /**
+         * @return Secondary timer duration for secondaryState, or 0 if not defined.
+         */
+        public int getSecondaryTimer(String secondaryState) {
+            Integer secondaryTimer = mSecondaryTimers.get(secondaryState);
+            secondaryTimer = secondaryTimer == null
+                    ? mSecondaryTimers.get(STATE_ANY) : secondaryTimer;
+            return secondaryTimer == null ? 0 : secondaryTimer;
+        }
+
+        @Override
+        public String toString() {
+            return "{mState=" + mState
+                    + ", mOverrideType="
+                    + TelephonyDisplayInfo.overrideNetworkTypeToString(mOverrideType)
+                    + ", mPrimaryTimers=" + mPrimaryTimers
+                    + ", mSecondaryTimers=" + mSecondaryTimers + "}";
+        }
+    }
+
+    private boolean isNrConnected() {
+        return mPhone.getServiceState().getNrState() == NetworkRegistrationInfo.NR_STATE_CONNECTED;
+    }
+
+    private boolean isNrNotRestricted() {
+        return mPhone.getServiceState().getNrState()
+                == NetworkRegistrationInfo.NR_STATE_NOT_RESTRICTED;
+    }
+
+    private boolean isNrRestricted() {
+        return mPhone.getServiceState().getNrState()
+                == NetworkRegistrationInfo.NR_STATE_RESTRICTED;
+    }
+
+    private boolean isNrMmwave() {
+        return mPhone.getServiceState().getNrFrequencyRange()
+                == ServiceState.FREQUENCY_RANGE_MMWAVE;
+    }
+
+    private boolean isLte(int rat) {
+        return rat == TelephonyManager.NETWORK_TYPE_LTE
+                || rat == TelephonyManager.NETWORK_TYPE_LTE_CA;
+    }
+
+    private boolean isPhysicalLinkActive() {
+        return mPhysicalLinkState == DcController.PHYSICAL_LINK_ACTIVE;
+    }
+
+    private String getEventName(int event) {
+        try {
+            return sEvents[event];
+        } catch (ArrayIndexOutOfBoundsException e) {
+            return "EVENT_NOT_DEFINED";
+        }
+    }
+
+    protected void log(String s) {
+        Rlog.d(TAG, "[" + mPhone.getPhoneId() + "] " + s);
+    }
+
+    protected void loge(String s) {
+        Rlog.e(TAG, "[" + mPhone.getPhoneId() + "] " + s);
+    }
+
+    @Override
+    public String toString() {
+        return "mOverrideTimerRules=" + mOverrideTimerRules.toString()
+                + ", mLteEnhancedPattern=" + mLteEnhancedPattern
+                + ", mIsPhysicalChannelConfigOn=" + mIsPhysicalChannelConfigOn
+                + ", mIsPrimaryTimerActive=" + mIsPrimaryTimerActive
+                + ", mIsSecondaryTimerActive=" + mIsSecondaryTimerActive
+                + ", mPrimaryTimerState=" + mPrimaryTimerState
+                + ", mSecondaryTimerState=" + mSecondaryTimerState
+                + ", mPreviousState=" + mPreviousState;
+    }
+
+    @Override
+    public void dump(FileDescriptor fd, PrintWriter printWriter, String[] args) {
+        IndentingPrintWriter pw = new IndentingPrintWriter(printWriter, " ");
+        pw.print("NetworkTypeController: ");
+        super.dump(fd, pw, args);
+        pw.flush();
+        pw.increaseIndent();
+        pw.println("mSubId=" + mPhone.getSubId());
+        pw.println("mOverrideTimerRules=" + mOverrideTimerRules.toString());
+        pw.println("mLteEnhancedPattern=" + mLteEnhancedPattern);
+        pw.println("mIsPhysicalChannelConfigOn=" + mIsPhysicalChannelConfigOn);
+        pw.println("mIsPrimaryTimerActive=" + mIsPrimaryTimerActive);
+        pw.println("mIsSecondaryTimerActive=" + mIsSecondaryTimerActive);
+        pw.println("mPrimaryTimerState=" + mPrimaryTimerState);
+        pw.println("mSecondaryTimerState=" + mSecondaryTimerState);
+        pw.println("mPreviousState=" + mPreviousState);
+        pw.decreaseIndent();
+        pw.flush();
+    }
+}
diff --git a/src/java/com/android/internal/telephony/NitzData.java b/src/java/com/android/internal/telephony/NitzData.java
index 6c310f6..f508d9e 100644
--- a/src/java/com/android/internal/telephony/NitzData.java
+++ b/src/java/com/android/internal/telephony/NitzData.java
@@ -18,15 +18,14 @@
 
 import static com.android.internal.annotations.VisibleForTesting.Visibility.PACKAGE;
 
-import android.telephony.Rlog;
-
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.telephony.Rlog;
 
 import java.util.Calendar;
 import java.util.TimeZone;
 
 /**
- * Represents NITZ data. Various static methods are provided to help with parsing and intepretation
+ * Represents NITZ data. Various static methods are provided to help with parsing and interpretation
  * of NITZ data.
  *
  * {@hide}
@@ -153,7 +152,11 @@
 
     /**
      * Returns the total offset to apply to the {@link #getCurrentTimeInMillis()} to arrive at a
-     * local time.
+     * local time. NITZ is limited in only being able to express total offsets in multiples of 15
+     * minutes.
+     *
+     * <p>Note that some time zones change offset during the year for reasons other than "daylight
+     * savings", e.g. for Ramadan. This is not well handled by most date / time APIs.
      */
     public int getLocalOffsetMillis() {
         return mZoneOffset;
@@ -162,7 +165,25 @@
     /**
      * Returns the offset (already included in {@link #getLocalOffsetMillis()}) associated with
      * Daylight Savings Time (DST). This field is optional: {@code null} means the DST offset is
-     * unknown.
+     * unknown. NITZ is limited in only being able to express DST offsets in positive multiples of
+     * one or two hours.
+     *
+     * <p>Callers should remember that standard time / DST is a matter of convention: it has
+     * historically been assumed by NITZ and many date/time APIs that DST happens in the summer and
+     * the "raw" offset will increase during this time, usually by one hour. However, the tzdb
+     * maintainers have moved to different conventions on a country-by-country basis so that some
+     * summer times are considered the "standard" time (i.e. in this model winter time is the "DST"
+     * and a negative adjustment, usually of (negative) one hour.
+     *
+     * <p>There is nothing that says NITZ and tzdb need to treat DST conventions the same.
+     *
+     * <p>At the time of writing Android date/time APIs are sticking with the historic tzdb
+     * convention that DST is used in summer time and is <em>always</em> a positive offset but this
+     * could change in future. If Android or carriers change the conventions used then it might make
+     * NITZ comparisons with tzdb information more error-prone.
+     *
+     * <p>See also {@link #getLocalOffsetMillis()} for other reasons besides DST that a local offset
+     * may change.
      */
     public Integer getDstAdjustmentMillis() {
         return mDstOffset;
diff --git a/src/java/com/android/internal/telephony/NitzStateMachine.java b/src/java/com/android/internal/telephony/NitzStateMachine.java
index 8aefb5c..e1f854c 100644
--- a/src/java/com/android/internal/telephony/NitzStateMachine.java
+++ b/src/java/com/android/internal/telephony/NitzStateMachine.java
@@ -16,12 +16,13 @@
 
 package com.android.internal.telephony;
 
+import android.annotation.NonNull;
 import android.content.ContentResolver;
 import android.content.Context;
+import android.os.SystemClock;
 import android.os.SystemProperties;
+import android.os.TimestampedValue;
 import android.provider.Settings;
-import android.telephony.TelephonyManager;
-import android.util.TimestampedValue;
 
 import com.android.internal.util.IndentingPrintWriter;
 
@@ -29,18 +30,20 @@
 import java.io.PrintWriter;
 
 /**
+ * An interface for the Android component that handles NITZ and related signals for time and time
+ * zone detection.
+ *
  * {@hide}
  */
 public interface NitzStateMachine {
 
     /**
-     * Called when the network country is set on the Phone. Although set, the network country code
-     * may be invalid.
+     * Called when the country suitable for time zone detection is detected.
      *
-     * @param countryChanged true when the country code is known to have changed, false if it
-     *     probably hasn't
+     * @param countryIsoCode the countryIsoCode to use for time zone detection, may be "" for test
+     *     cells only, otherwise {@link #handleCountryUnavailable()} should be called
      */
-    void handleNetworkCountryCodeSet(boolean countryChanged);
+    void handleCountryDetected(@NonNull String countryIsoCode);
 
     /**
      * Informs the {@link NitzStateMachine} that the network has become available.
@@ -48,15 +51,27 @@
     void handleNetworkAvailable();
 
     /**
-     * Informs the {@link NitzStateMachine} that the country code from network has become
-     * unavailable.
+     * Informs the {@link NitzStateMachine} that the network has become unavailable. Any network
+     * state, i.e. NITZ, should be cleared.
      */
-    void handleNetworkCountryCodeUnavailable();
+    void handleNetworkUnavailable();
+
+    /**
+     * Informs the {@link NitzStateMachine} that any previously detected country supplied via
+     * {@link #handleCountryDetected(String)} is no longer valid.
+     */
+    void handleCountryUnavailable();
 
     /**
      * Handle a new NITZ signal being received.
      */
-    void handleNitzReceived(TimestampedValue<NitzData> nitzSignal);
+    void handleNitzReceived(@NonNull TimestampedValue<NitzData> nitzSignal);
+
+    /**
+     * Handle the user putting the device into or out of airplane mode
+     * @param on true if airplane mode has been turned on, false if it's been turned off.
+     */
+    void handleAirplaneModeChanged(boolean on);
 
     /**
      * Dumps the current in-memory state to the supplied PrintWriter.
@@ -69,19 +84,8 @@
     void dumpLogs(FileDescriptor fd, IndentingPrintWriter ipw, String[] args);
 
     /**
-     * Returns the last NITZ data that was cached.
-     */
-    NitzData getCachedNitzData();
-
-    /**
-     * Returns the time zone ID from the most recent time that a time zone could be determined by
-     * this state machine.
-     */
-    String getSavedTimeZoneId();
-
-    /**
-     * A proxy over device state that allows things like system properties, system clock
-     * to be faked for tests.
+     * A proxy over read-only device state that allows things like system properties, elapsed
+     * realtime clock to be faked for tests.
      */
     interface DeviceState {
 
@@ -102,7 +106,15 @@
          */
         boolean getIgnoreNitz();
 
-        String getNetworkCountryIsoForPhone();
+        /**
+         * Returns the same value as {@link SystemClock#elapsedRealtime()}.
+         */
+        long elapsedRealtime();
+
+        /**
+         * Returns the same value as {@link System#currentTimeMillis()}.
+         */
+        long currentTimeMillis();
     }
 
     /**
@@ -117,16 +129,10 @@
         private static final int NITZ_UPDATE_DIFF_DEFAULT = 2000;
         private final int mNitzUpdateDiff;
 
-        private final GsmCdmaPhone mPhone;
-        private final TelephonyManager mTelephonyManager;
         private final ContentResolver mCr;
 
-        public DeviceStateImpl(GsmCdmaPhone phone) {
-            mPhone = phone;
-
+        public DeviceStateImpl(Phone phone) {
             Context context = phone.getContext();
-            mTelephonyManager =
-                    (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
             mCr = context.getContentResolver();
             mNitzUpdateSpacing =
                     SystemProperties.getInt("ro.nitz_update_spacing", NITZ_UPDATE_SPACING_DEFAULT);
@@ -152,8 +158,13 @@
         }
 
         @Override
-        public String getNetworkCountryIsoForPhone() {
-            return mTelephonyManager.getNetworkCountryIsoForPhone(mPhone.getPhoneId());
+        public long elapsedRealtime() {
+            return SystemClock.elapsedRealtime();
+        }
+
+        @Override
+        public long currentTimeMillis() {
+            return System.currentTimeMillis();
         }
     }
 }
diff --git a/src/java/com/android/internal/telephony/NitzStateMachineImpl.java b/src/java/com/android/internal/telephony/NitzStateMachineImpl.java
deleted file mode 100644
index 8fbefac..0000000
--- a/src/java/com/android/internal/telephony/NitzStateMachineImpl.java
+++ /dev/null
@@ -1,491 +0,0 @@
-/*
- * Copyright 2017 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;
-
-import android.content.Context;
-import android.os.PowerManager;
-import android.telephony.Rlog;
-import android.text.TextUtils;
-import android.util.LocalLog;
-import android.util.TimestampedValue;
-
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.telephony.TimeZoneLookupHelper.CountryResult;
-import com.android.internal.telephony.TimeZoneLookupHelper.OffsetResult;
-import com.android.internal.telephony.metrics.TelephonyMetrics;
-import com.android.internal.util.IndentingPrintWriter;
-
-import java.io.FileDescriptor;
-import java.io.PrintWriter;
-
-/**
- * {@hide}
- */
-public final class NitzStateMachineImpl implements NitzStateMachine {
-
-    private static final String LOG_TAG = ServiceStateTracker.LOG_TAG;
-    private static final boolean DBG = ServiceStateTracker.DBG;
-
-    // Time detection state.
-
-    /**
-     * The last NITZ-sourced time considered sent to the time detector service. Used to rate-limit
-     * calls to the time detector.
-     */
-    private TimestampedValue<Long> mSavedNitzTime;
-
-    // Time Zone detection state.
-
-    /** We always keep the last NITZ signal received in mLatestNitzSignal. */
-    private TimestampedValue<NitzData> mLatestNitzSignal;
-
-    /**
-     * Records whether the device should have a country code available via
-     * {@link DeviceState#getNetworkCountryIsoForPhone()}. Before this an NITZ signal
-     * received is (almost always) not enough to determine time zone. On test networks the country
-     * code should be available but can still be an empty string but this flag indicates that the
-     * information available is unlikely to improve.
-     */
-    private boolean mGotCountryCode = false;
-
-    /**
-     * The last time zone ID that has been determined. It may not have been set as the device time
-     * zone if automatic time zone detection is disabled but may later be used to set the time zone
-     * if the user enables automatic time zone detection.
-     */
-    private String mSavedTimeZoneId;
-
-    /**
-     * Boolean is {@code true} if NITZ has been used to determine a time zone (which may not
-     * ultimately have been used due to user settings). Cleared by {@link #handleNetworkAvailable()}
-     * and {@link #handleNetworkCountryCodeUnavailable()}. The flag can be used when historic NITZ
-     * data may no longer be valid. {@code false} indicates it is reasonable to try to set the time
-     * zone using less reliable algorithms than NITZ-based detection such as by just using network
-     * country code.
-     */
-    private boolean mNitzTimeZoneDetectionSuccessful = false;
-
-    // Miscellaneous dependencies and helpers not related to detection state.
-    private final LocalLog mTimeLog = new LocalLog(15);
-    private final LocalLog mTimeZoneLog = new LocalLog(15);
-    private final GsmCdmaPhone mPhone;
-    private final DeviceState mDeviceState;
-    private final TimeServiceHelper mTimeServiceHelper;
-    private final TimeZoneLookupHelper mTimeZoneLookupHelper;
-    /** Wake lock used while setting time of day. */
-    private final PowerManager.WakeLock mWakeLock;
-    private static final String WAKELOCK_TAG = "NitzStateMachine";
-
-    public NitzStateMachineImpl(GsmCdmaPhone phone) {
-        this(phone,
-                new TimeServiceHelperImpl(phone.getContext()),
-                new DeviceStateImpl(phone),
-                new TimeZoneLookupHelper());
-    }
-
-    @VisibleForTesting
-    public NitzStateMachineImpl(GsmCdmaPhone phone, TimeServiceHelper timeServiceHelper,
-            DeviceState deviceState, TimeZoneLookupHelper timeZoneLookupHelper) {
-        mPhone = phone;
-
-        Context context = phone.getContext();
-        PowerManager powerManager =
-                (PowerManager) context.getSystemService(Context.POWER_SERVICE);
-        mWakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, WAKELOCK_TAG);
-
-        mDeviceState = deviceState;
-        mTimeZoneLookupHelper = timeZoneLookupHelper;
-        mTimeServiceHelper = timeServiceHelper;
-        mTimeServiceHelper.setListener(new TimeServiceHelper.Listener() {
-            @Override
-            public void onTimeZoneDetectionChange(boolean enabled) {
-                if (enabled) {
-                    handleAutoTimeZoneEnabled();
-                }
-            }
-        });
-    }
-
-    @Override
-    public void handleNetworkCountryCodeSet(boolean countryChanged) {
-        boolean hadCountryCode = mGotCountryCode;
-        mGotCountryCode = true;
-
-        String isoCountryCode = mDeviceState.getNetworkCountryIsoForPhone();
-        if (!TextUtils.isEmpty(isoCountryCode) && !mNitzTimeZoneDetectionSuccessful) {
-            updateTimeZoneFromNetworkCountryCode(isoCountryCode);
-        }
-
-        if (mLatestNitzSignal != null && (countryChanged || !hadCountryCode)) {
-            updateTimeZoneFromCountryAndNitz();
-        }
-    }
-
-    private void updateTimeZoneFromCountryAndNitz() {
-        String isoCountryCode = mDeviceState.getNetworkCountryIsoForPhone();
-        TimestampedValue<NitzData> nitzSignal = mLatestNitzSignal;
-
-        // TimeZone.getDefault() returns a default zone (GMT) even when time zone have never
-        // been set which makes it difficult to tell if it's what the user / time zone detection
-        // has chosen. isTimeZoneSettingInitialized() tells us whether the time zone of the
-        // device has ever been explicit set by the user or code.
-        final boolean isTimeZoneSettingInitialized =
-                mTimeServiceHelper.isTimeZoneSettingInitialized();
-
-        if (DBG) {
-            Rlog.d(LOG_TAG, "updateTimeZoneFromCountryAndNitz:"
-                    + " isTimeZoneSettingInitialized=" + isTimeZoneSettingInitialized
-                    + " nitzSignal=" + nitzSignal
-                    + " isoCountryCode=" + isoCountryCode);
-        }
-
-        try {
-            NitzData nitzData = nitzSignal.getValue();
-
-            String zoneId;
-            if (nitzData.getEmulatorHostTimeZone() != null) {
-                zoneId = nitzData.getEmulatorHostTimeZone().getID();
-            } else if (!mGotCountryCode) {
-                // We don't have a country code so we won't try to look up the time zone.
-                zoneId = null;
-            } else if (TextUtils.isEmpty(isoCountryCode)) {
-                // We have a country code but it's empty. This is most likely because we're on a
-                // test network that's using a bogus MCC (eg, "001"). Obtain a TimeZone based only
-                // on the NITZ parameters: it's only going to be correct in a few cases but it
-                // should at least have the correct offset.
-                OffsetResult lookupResult = mTimeZoneLookupHelper.lookupByNitz(nitzData);
-                String logMsg = "updateTimeZoneFromCountryAndNitz: lookupByNitz returned"
-                        + " lookupResult=" + lookupResult;
-                if (DBG) {
-                    Rlog.d(LOG_TAG, logMsg);
-                }
-                // We log this in the time zone log because it has been a source of bugs.
-                mTimeZoneLog.log(logMsg);
-
-                zoneId = lookupResult != null ? lookupResult.zoneId : null;
-            } else if (mLatestNitzSignal == null) {
-                if (DBG) {
-                    Rlog.d(LOG_TAG,
-                            "updateTimeZoneFromCountryAndNitz: No cached NITZ data available,"
-                                    + " not setting zone");
-                }
-                zoneId = null;
-            } else if (isNitzSignalOffsetInfoBogus(nitzSignal, isoCountryCode)) {
-                String logMsg = "updateTimeZoneFromCountryAndNitz: Received NITZ looks bogus, "
-                        + " isoCountryCode=" + isoCountryCode
-                        + " nitzSignal=" + nitzSignal;
-                if (DBG) {
-                    Rlog.d(LOG_TAG, logMsg);
-                }
-                // We log this in the time zone log because it has been a source of bugs.
-                mTimeZoneLog.log(logMsg);
-
-                zoneId = null;
-            } else {
-                OffsetResult lookupResult = mTimeZoneLookupHelper.lookupByNitzCountry(
-                        nitzData, isoCountryCode);
-                if (DBG) {
-                    Rlog.d(LOG_TAG, "updateTimeZoneFromCountryAndNitz: using"
-                            + " lookupByNitzCountry(nitzData, isoCountryCode),"
-                            + " nitzData=" + nitzData
-                            + " isoCountryCode=" + isoCountryCode
-                            + " lookupResult=" + lookupResult);
-                }
-                zoneId = lookupResult != null ? lookupResult.zoneId : null;
-            }
-
-            // Log the action taken to the dedicated time zone log.
-            final String tmpLog = "updateTimeZoneFromCountryAndNitz:"
-                    + " isTimeZoneSettingInitialized=" + isTimeZoneSettingInitialized
-                    + " isoCountryCode=" + isoCountryCode
-                    + " nitzSignal=" + nitzSignal
-                    + " zoneId=" + zoneId
-                    + " isTimeZoneDetectionEnabled()="
-                    + mTimeServiceHelper.isTimeZoneDetectionEnabled();
-            mTimeZoneLog.log(tmpLog);
-
-            // Set state as needed.
-            if (zoneId != null) {
-                if (DBG) {
-                    Rlog.d(LOG_TAG, "updateTimeZoneFromCountryAndNitz: zoneId=" + zoneId);
-                }
-                if (mTimeServiceHelper.isTimeZoneDetectionEnabled()) {
-                    setAndBroadcastNetworkSetTimeZone(zoneId);
-                } else {
-                    if (DBG) {
-                        Rlog.d(LOG_TAG, "updateTimeZoneFromCountryAndNitz: skip changing zone"
-                                + " as isTimeZoneDetectionEnabled() is false");
-                    }
-                }
-                mSavedTimeZoneId = zoneId;
-                mNitzTimeZoneDetectionSuccessful = true;
-            } else {
-                if (DBG) {
-                    Rlog.d(LOG_TAG,
-                            "updateTimeZoneFromCountryAndNitz: zoneId == null, do nothing");
-                }
-            }
-        } catch (RuntimeException ex) {
-            Rlog.e(LOG_TAG, "updateTimeZoneFromCountryAndNitz: Processing NITZ data"
-                    + " nitzSignal=" + nitzSignal
-                    + " isoCountryCode=" + isoCountryCode
-                    + " isTimeZoneSettingInitialized=" + isTimeZoneSettingInitialized
-                    + " ex=" + ex);
-        }
-    }
-
-    /**
-     * Returns true if the NITZ signal is definitely bogus, assuming that the country is correct.
-     */
-    private boolean isNitzSignalOffsetInfoBogus(
-            TimestampedValue<NitzData> nitzSignal, String isoCountryCode) {
-
-        if (TextUtils.isEmpty(isoCountryCode)) {
-            // We cannot say for sure.
-            return false;
-        }
-
-        NitzData newNitzData = nitzSignal.getValue();
-        boolean zeroOffsetNitz = newNitzData.getLocalOffsetMillis() == 0 && !newNitzData.isDst();
-        return zeroOffsetNitz && !countryUsesUtc(isoCountryCode, nitzSignal);
-    }
-
-    private boolean countryUsesUtc(
-            String isoCountryCode, TimestampedValue<NitzData> nitzSignal) {
-        return mTimeZoneLookupHelper.countryUsesUtc(
-                isoCountryCode,
-                nitzSignal.getValue().getCurrentTimeInMillis());
-    }
-
-    @Override
-    public void handleNetworkAvailable() {
-        if (DBG) {
-            Rlog.d(LOG_TAG, "handleNetworkAvailable: mNitzTimeZoneDetectionSuccessful="
-                    + mNitzTimeZoneDetectionSuccessful
-                    + ", Setting mNitzTimeZoneDetectionSuccessful=false");
-        }
-        mNitzTimeZoneDetectionSuccessful = false;
-    }
-
-    @Override
-    public void handleNetworkCountryCodeUnavailable() {
-        if (DBG) {
-            Rlog.d(LOG_TAG, "handleNetworkCountryCodeUnavailable");
-        }
-
-        mGotCountryCode = false;
-        mNitzTimeZoneDetectionSuccessful = false;
-    }
-
-    @Override
-    public void handleNitzReceived(TimestampedValue<NitzData> nitzSignal) {
-        // Always store the latest NITZ signal received.
-        mLatestNitzSignal = nitzSignal;
-
-        updateTimeZoneFromCountryAndNitz();
-        updateTimeFromNitz();
-    }
-
-    private void updateTimeFromNitz() {
-        TimestampedValue<NitzData> nitzSignal = mLatestNitzSignal;
-        try {
-            boolean ignoreNitz = mDeviceState.getIgnoreNitz();
-            if (ignoreNitz) {
-                if (DBG) {
-                    Rlog.d(LOG_TAG, "updateTimeFromNitz: Not suggesting system clock because"
-                            + " gsm.ignore-nitz is set");
-                }
-                return;
-            }
-
-            // Validate the nitzTimeSignal to reject obviously bogus elapsedRealtime values.
-            try {
-                // Acquire the wake lock as we are reading the elapsed realtime clock below.
-                mWakeLock.acquire();
-
-                long elapsedRealtime = mTimeServiceHelper.elapsedRealtime();
-                long millisSinceNitzReceived =
-                        elapsedRealtime - nitzSignal.getReferenceTimeMillis();
-                if (millisSinceNitzReceived < 0 || millisSinceNitzReceived > Integer.MAX_VALUE) {
-                    if (DBG) {
-                        Rlog.d(LOG_TAG, "updateTimeFromNitz: not setting time, unexpected"
-                                + " elapsedRealtime=" + elapsedRealtime
-                                + " nitzSignal=" + nitzSignal);
-                    }
-                    return;
-                }
-            } finally {
-                mWakeLock.release();
-            }
-
-            TimestampedValue<Long> newNitzTime = new TimestampedValue<>(
-                    nitzSignal.getReferenceTimeMillis(),
-                    nitzSignal.getValue().getCurrentTimeInMillis());
-
-            // Perform rate limiting: a NITZ signal received too close to a previous
-            // one will be disregarded unless there is a significant difference between the
-            // UTC times they represent.
-            if (mSavedNitzTime != null) {
-                int nitzUpdateSpacing = mDeviceState.getNitzUpdateSpacingMillis();
-                int nitzUpdateDiff = mDeviceState.getNitzUpdateDiffMillis();
-
-                // Calculate the elapsed time between the new signal and the last signal.
-                long elapsedRealtimeSinceLastSaved = newNitzTime.getReferenceTimeMillis()
-                        - mSavedNitzTime.getReferenceTimeMillis();
-
-                // Calculate the UTC difference between the time the two signals hold.
-                long utcTimeDifferenceMillis =
-                        newNitzTime.getValue() - mSavedNitzTime.getValue();
-
-                // Ideally the difference between elapsedRealtimeSinceLastSaved and
-                // utcTimeDifferenceMillis would be zero.
-                long millisGained = utcTimeDifferenceMillis - elapsedRealtimeSinceLastSaved;
-
-                if (elapsedRealtimeSinceLastSaved <= nitzUpdateSpacing
-                        && Math.abs(millisGained) <= nitzUpdateDiff) {
-                    if (DBG) {
-                        Rlog.d(LOG_TAG, "updateTimeFromNitz: not setting time. NITZ signal is"
-                                + " too similar to previous value received "
-                                + " mSavedNitzTime=" + mSavedNitzTime
-                                + ", nitzSignal=" + nitzSignal
-                                + ", nitzUpdateSpacing=" + nitzUpdateSpacing
-                                + ", nitzUpdateDiff=" + nitzUpdateDiff);
-                    }
-                    return;
-                }
-            }
-
-            String logMsg = "updateTimeFromNitz: suggesting system clock update"
-                    + " nitzSignal=" + nitzSignal
-                    + ", newNitzTime=" + newNitzTime
-                    + ", mSavedNitzTime= " + mSavedNitzTime;
-            if (DBG) {
-                Rlog.d(LOG_TAG, logMsg);
-            }
-            mTimeLog.log(logMsg);
-            mTimeServiceHelper.suggestDeviceTime(newNitzTime);
-            TelephonyMetrics.getInstance().writeNITZEvent(
-                    mPhone.getPhoneId(), newNitzTime.getValue());
-
-            // Save the last NITZ time signal that was suggested to enable rate limiting.
-            mSavedNitzTime = newNitzTime;
-        } catch (RuntimeException ex) {
-            Rlog.e(LOG_TAG, "updateTimeFromNitz: Processing NITZ data"
-                    + " nitzSignal=" + nitzSignal
-                    + " ex=" + ex);
-        }
-    }
-
-    private void setAndBroadcastNetworkSetTimeZone(String zoneId) {
-        if (DBG) {
-            Rlog.d(LOG_TAG, "setAndBroadcastNetworkSetTimeZone: zoneId=" + zoneId);
-        }
-        mTimeServiceHelper.setDeviceTimeZone(zoneId);
-        if (DBG) {
-            Rlog.d(LOG_TAG,
-                    "setAndBroadcastNetworkSetTimeZone: called setDeviceTimeZone()"
-                            + " zoneId=" + zoneId);
-        }
-    }
-
-    private void handleAutoTimeZoneEnabled() {
-        String tmpLog = "handleAutoTimeZoneEnabled: Reverting to NITZ TimeZone:"
-                + " mSavedTimeZoneId=" + mSavedTimeZoneId;
-        if (DBG) {
-            Rlog.d(LOG_TAG, tmpLog);
-        }
-        mTimeZoneLog.log(tmpLog);
-        if (mSavedTimeZoneId != null) {
-            setAndBroadcastNetworkSetTimeZone(mSavedTimeZoneId);
-        }
-    }
-
-    @Override
-    public void dumpState(PrintWriter pw) {
-        // Time Detection State
-        pw.println(" mSavedTime=" + mSavedNitzTime);
-
-        // Time Zone Detection State
-        pw.println(" mLatestNitzSignal=" + mLatestNitzSignal);
-        pw.println(" mGotCountryCode=" + mGotCountryCode);
-        pw.println(" mSavedTimeZoneId=" + mSavedTimeZoneId);
-        pw.println(" mNitzTimeZoneDetectionSuccessful=" + mNitzTimeZoneDetectionSuccessful);
-
-        // Miscellaneous
-        pw.println(" mWakeLock=" + mWakeLock);
-        pw.flush();
-    }
-
-    @Override
-    public void dumpLogs(FileDescriptor fd, IndentingPrintWriter ipw, String[] args) {
-        ipw.println(" Time Logs:");
-        ipw.increaseIndent();
-        mTimeLog.dump(fd, ipw, args);
-        ipw.decreaseIndent();
-
-        ipw.println(" Time zone Logs:");
-        ipw.increaseIndent();
-        mTimeZoneLog.dump(fd, ipw, args);
-        ipw.decreaseIndent();
-    }
-
-    /**
-     * Update time zone by network country code, works well on countries which only have one time
-     * zone or multiple zones with the same offset.
-     *
-     * @param iso Country code from network MCC
-     */
-    private void updateTimeZoneFromNetworkCountryCode(String iso) {
-        CountryResult lookupResult = mTimeZoneLookupHelper.lookupByCountry(
-                iso, mTimeServiceHelper.currentTimeMillis());
-        if (lookupResult != null && lookupResult.allZonesHaveSameOffset) {
-            String logMsg = "updateTimeZoneFromNetworkCountryCode: tz result found"
-                    + " iso=" + iso
-                    + " lookupResult=" + lookupResult;
-            if (DBG) {
-                Rlog.d(LOG_TAG, logMsg);
-            }
-            mTimeZoneLog.log(logMsg);
-            String zoneId = lookupResult.zoneId;
-            if (mTimeServiceHelper.isTimeZoneDetectionEnabled()) {
-                setAndBroadcastNetworkSetTimeZone(zoneId);
-            }
-            mSavedTimeZoneId = zoneId;
-        } else {
-            if (DBG) {
-                Rlog.d(LOG_TAG, "updateTimeZoneFromNetworkCountryCode: no good zone for"
-                        + " iso=" + iso
-                        + " lookupResult=" + lookupResult);
-            }
-        }
-    }
-
-    public boolean getNitzTimeZoneDetectionSuccessful() {
-        return mNitzTimeZoneDetectionSuccessful;
-    }
-
-    @Override
-    public NitzData getCachedNitzData() {
-        return mLatestNitzSignal != null ? mLatestNitzSignal.getValue() : null;
-    }
-
-    @Override
-    public String getSavedTimeZoneId() {
-        return mSavedTimeZoneId;
-    }
-
-}
diff --git a/src/java/com/android/internal/telephony/Phone.java b/src/java/com/android/internal/telephony/Phone.java
index 96c0b73..ca080c8 100644
--- a/src/java/com/android/internal/telephony/Phone.java
+++ b/src/java/com/android/internal/telephony/Phone.java
@@ -16,21 +16,17 @@
 
 package com.android.internal.telephony;
 
+import android.annotation.NonNull;
 import android.annotation.Nullable;
-import android.annotation.UnsupportedAppUsage;
 import android.app.BroadcastOptions;
-import android.content.BroadcastReceiver;
+import android.compat.annotation.UnsupportedAppUsage;
 import android.content.Context;
 import android.content.Intent;
-import android.content.IntentFilter;
 import android.content.SharedPreferences;
 import android.net.LinkProperties;
 import android.net.NetworkCapabilities;
-import android.net.NetworkStats;
 import android.net.Uri;
-import android.net.wifi.WifiManager;
 import android.os.AsyncResult;
-import android.os.Build;
 import android.os.Handler;
 import android.os.Looper;
 import android.os.Message;
@@ -41,27 +37,30 @@
 import android.os.SystemProperties;
 import android.os.WorkSource;
 import android.preference.PreferenceManager;
-import android.provider.Settings;
+import android.sysprop.TelephonyProperties;
 import android.telecom.VideoProfile;
 import android.telephony.AccessNetworkConstants;
+import android.telephony.Annotation.ApnType;
+import android.telephony.Annotation.DataFailureCause;
 import android.telephony.CarrierConfigManager;
 import android.telephony.CarrierRestrictionRules;
+import android.telephony.CellIdentity;
 import android.telephony.CellInfo;
-import android.telephony.CellLocation;
 import android.telephony.ClientRequestStats;
-import android.telephony.DataFailCause;
 import android.telephony.ImsiEncryptionInfo;
 import android.telephony.PhoneStateListener;
 import android.telephony.PhysicalChannelConfig;
+import android.telephony.PreciseDataConnectionState;
 import android.telephony.RadioAccessFamily;
-import android.telephony.Rlog;
+import android.telephony.RadioAccessSpecifier;
 import android.telephony.ServiceState;
 import android.telephony.SignalStrength;
 import android.telephony.SubscriptionManager;
+import android.telephony.TelephonyDisplayInfo;
 import android.telephony.TelephonyManager;
 import android.telephony.data.ApnSetting;
-import android.telephony.data.ApnSetting.ApnType;
 import android.telephony.emergency.EmergencyNumber;
+import android.telephony.ims.RegistrationManager;
 import android.telephony.ims.stub.ImsRegistrationImplBase;
 import android.text.TextUtils;
 import android.util.LocalLog;
@@ -69,14 +68,17 @@
 
 import com.android.ims.ImsCall;
 import com.android.ims.ImsConfig;
+import com.android.ims.ImsException;
 import com.android.ims.ImsManager;
 import com.android.internal.R;
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.telephony.dataconnection.DataConnectionReasons;
 import com.android.internal.telephony.dataconnection.DataEnabledSettings;
 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.metrics.VoiceCallSessionStats;
 import com.android.internal.telephony.test.SimulatedRadioControl;
 import com.android.internal.telephony.uicc.IccCardApplicationStatus.AppType;
 import com.android.internal.telephony.uicc.IccFileHandler;
@@ -86,16 +88,21 @@
 import com.android.internal.telephony.uicc.UiccCardApplication;
 import com.android.internal.telephony.uicc.UiccController;
 import com.android.internal.telephony.uicc.UsimServiceTable;
+import com.android.internal.telephony.util.TelephonyUtils;
+import com.android.telephony.Rlog;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Locale;
+import java.util.Map;
 import java.util.Set;
 import java.util.concurrent.atomic.AtomicReference;
+import java.util.function.Consumer;
 
 /**
  * (<em>Not for SDK use</em>)
@@ -116,33 +123,6 @@
 
     protected final int USSD_MAX_QUEUE = 10;
 
-    private BroadcastReceiver mImsIntentReceiver = new BroadcastReceiver() {
-        @Override
-        public void onReceive(Context context, Intent intent) {
-            Rlog.d(LOG_TAG, "mImsIntentReceiver: action " + intent.getAction());
-            if (intent.hasExtra(ImsManager.EXTRA_PHONE_ID)) {
-                int extraPhoneId = intent.getIntExtra(ImsManager.EXTRA_PHONE_ID,
-                        SubscriptionManager.INVALID_PHONE_INDEX);
-                Rlog.d(LOG_TAG, "mImsIntentReceiver: extraPhoneId = " + extraPhoneId);
-                if (extraPhoneId == SubscriptionManager.INVALID_PHONE_INDEX ||
-                        extraPhoneId != getPhoneId()) {
-                    return;
-                }
-            }
-
-            synchronized (Phone.lockForRadioTechnologyChange) {
-                if (intent.getAction().equals(ImsManager.ACTION_IMS_SERVICE_UP)) {
-                    mImsServiceReady = true;
-                    updateImsPhone();
-                    ImsManager.getInstance(mContext, mPhoneId).updateImsServiceConfig(false);
-                } else if (intent.getAction().equals(ImsManager.ACTION_IMS_SERVICE_DOWN)) {
-                    mImsServiceReady = false;
-                    updateImsPhone();
-                }
-            }
-        }
-    };
-
     // Key used to read and write the saved network selection numeric value
     public static final String NETWORK_SELECTION_KEY = "network_selection_key";
     // Key used to read and write the saved network selection operator name
@@ -198,9 +178,11 @@
     // other
     protected static final int EVENT_SET_NETWORK_AUTOMATIC          = 28;
     protected static final int EVENT_ICC_RECORD_EVENTS              = 29;
-    private static final int EVENT_ICC_CHANGED                      = 30;
+    @VisibleForTesting
+    protected static final int EVENT_ICC_CHANGED                    = 30;
     // Single Radio Voice Call Continuity
-    private static final int EVENT_SRVCC_STATE_CHANGED              = 31;
+    @VisibleForTesting
+    protected static final int EVENT_SRVCC_STATE_CHANGED             = 31;
     private static final int EVENT_INITIATE_SILENT_REDIAL           = 32;
     private static final int EVENT_RADIO_NOT_AVAILABLE              = 33;
     private static final int EVENT_UNSOL_OEM_HOOK_RAW               = 34;
@@ -224,9 +206,15 @@
     protected static final int EVENT_DEVICE_PROVISIONING_DATA_SETTING_CHANGE = 50;
     protected static final int EVENT_GET_AVAILABLE_NETWORKS_DONE    = 51;
 
-    private static final int EVENT_ALL_DATA_DISCONNECTED         = 52;
+    private static final int EVENT_ALL_DATA_DISCONNECTED                  = 52;
+    protected static final int EVENT_UICC_APPS_ENABLEMENT_STATUS_CHANGED  = 53;
+    protected static final int EVENT_UICC_APPS_ENABLEMENT_SETTING_CHANGED = 54;
+    protected static final int EVENT_GET_UICC_APPS_ENABLEMENT_DONE        = 55;
+    protected static final int EVENT_REAPPLY_UICC_APPS_ENABLEMENT_DONE    = 56;
+    protected static final int EVENT_REGISTRATION_FAILED = 57;
+    protected static final int EVENT_BARRING_INFO_CHANGED = 58;
 
-    protected static final int EVENT_LAST = EVENT_ALL_DATA_DISCONNECTED;
+    protected static final int EVENT_LAST = EVENT_BARRING_INFO_CHANGED;
 
     // For shared prefs.
     private static final String GSM_ROAMING_LIST_OVERRIDE_PREFIX = "gsm_roaming_list_";
@@ -235,7 +223,7 @@
     private static final String CDMA_NON_ROAMING_LIST_OVERRIDE_PREFIX = "cdma_non_roaming_list_";
 
     // Key used to read/write current CLIR setting
-    public static final String CLIR_KEY = "clir_key";
+    public static final String CLIR_KEY = "clir_sub_key";
 
     // Key used for storing voice mail count
     private static final String VM_COUNT = "vm_count_key";
@@ -265,13 +253,25 @@
      * if we are looking for automatic selection. operatorAlphaLong is the
      * corresponding operator name.
      */
-    private static class NetworkSelectMessage {
+    protected static class NetworkSelectMessage {
         public Message message;
         public String operatorNumeric;
         public String operatorAlphaLong;
         public String operatorAlphaShort;
     }
 
+    public static class SilentRedialParam {
+        public String dialString;
+        public int causeCode;
+        public DialArgs dialArgs;
+
+        public SilentRedialParam(String dialString, int causeCode, DialArgs dialArgs) {
+            this.dialString = dialString;
+            this.causeCode = causeCode;
+            this.dialArgs = dialArgs;
+        }
+    }
+
     /* Instance Variables */
     @UnsupportedAppUsage
     public CommandsInterface mCi;
@@ -295,6 +295,9 @@
     // Keep track of whether or not the phone is in Emergency Callback Mode for Phone and
     // subclasses
     protected boolean mIsPhoneInEcmState = false;
+    // Keep track of the case where ECM was cancelled to place another outgoing emergency call.
+    // We will need to restart it after the emergency call ends.
+    protected boolean mEcmCanceledForEmergency = false;
     private volatile long mTimeLastEmergencySmsSentMs = EMERGENCY_SMS_NO_TIME_RECORDED;
 
     // Variable to cache the video capability. When RAT changes, we lose this info and are unable
@@ -315,6 +318,7 @@
     private final String mActionDetached;
     private final String mActionAttached;
     protected DeviceStateMonitor mDeviceStateMonitor;
+    protected DisplayInfoController mDisplayInfoController;
     protected TransportManager mTransportManager;
     protected DataEnabledSettings mDataEnabledSettings;
     // Used for identify the carrier of current subscription
@@ -323,7 +327,6 @@
     @UnsupportedAppUsage
     protected int mPhoneId;
 
-    private boolean mImsServiceReady = false;
     @UnsupportedAppUsage
     protected Phone mImsPhone = null;
 
@@ -342,8 +345,20 @@
      * TODO: Replace this with a proper exception; {@link CallStateException} doesn't make sense.
      */
     public static final String CS_FALLBACK = "cs_fallback";
-    public static final String EXTRA_KEY_ALERT_TITLE = "alertTitle";
-    public static final String EXTRA_KEY_ALERT_MESSAGE = "alertMessage";
+    /**
+     * @deprecated Use {@link android.telephony.ims.ImsManager#EXTRA_WFC_REGISTRATION_FAILURE_TITLE}
+     * instead.
+     */
+    @Deprecated
+    public static final String EXTRA_KEY_ALERT_TITLE =
+            android.telephony.ims.ImsManager.EXTRA_WFC_REGISTRATION_FAILURE_TITLE;
+    /**
+     * @deprecated Use
+     * {@link android.telephony.ims.ImsManager#EXTRA_WFC_REGISTRATION_FAILURE_MESSAGE} instead.
+     */
+    @Deprecated
+    public static final String EXTRA_KEY_ALERT_MESSAGE =
+            android.telephony.ims.ImsManager.EXTRA_WFC_REGISTRATION_FAILURE_MESSAGE;
     public static final String EXTRA_KEY_ALERT_SHOW = "alertShow";
     public static final String EXTRA_KEY_NOTIFICATION_MESSAGE = "notificationMessage";
 
@@ -380,8 +395,14 @@
 
     private final RegistrantList mCellInfoRegistrants = new RegistrantList();
 
+    private final RegistrantList mRedialRegistrants = new RegistrantList();
+
+    private final RegistrantList mPhysicalChannelConfigRegistrants = new RegistrantList();
+
     private final RegistrantList mOtaspRegistrants = new RegistrantList();
 
+    private final RegistrantList mPreferredNetworkTypeRegistrants = new RegistrantList();
+
     protected Registrant mPostDialHandler;
 
     protected final LocalLog mLocalLog;
@@ -402,6 +423,10 @@
     protected SimulatedRadioControl mSimulatedRadioControl;
 
     private boolean mUnitTestMode;
+    private Map<Integer, Long> mAllowedNetworkTypesForReasons = new HashMap<>();
+    private final CarrierPrivilegesTracker mCarrierPrivilegesTracker;
+
+    protected VoiceCallSessionStats mVoiceCallSessionStats;
 
     public IccRecords getIccRecords() {
         return mIccRecords.get();
@@ -452,27 +477,6 @@
     }
 
     /**
-     * Set a system property for the current phone, unless we're in unit test mode
-     */
-    // CAF_MSIM TODO this need to be replated with TelephonyManager API ?
-    public void setSystemProperty(String property, String value) {
-        if (getUnitTestMode()) {
-            return;
-        }
-        TelephonyManager.setTelephonyProperty(mPhoneId, property, value);
-    }
-
-    /**
-     * Set a system property for all phones, unless we're in unit test mode
-     */
-    public void setGlobalSystemProperty(String property, String value) {
-        if (getUnitTestMode()) {
-            return;
-        }
-        TelephonyManager.setTelephonyProperty(property, value);
-    }
-
-    /**
      * Set a system property, unless we're in unit test mode
      */
     // CAF_MSIM TODO this need to be replated with TelephonyManager API ?
@@ -526,7 +530,7 @@
                 .makeAppSmsManager(context);
         mLocalLog = new LocalLog(64);
 
-        if (Build.IS_DEBUGGABLE) {
+        if (TelephonyUtils.IS_DEBUGGABLE) {
             mTelephonyTester = new TelephonyTester(this);
         }
 
@@ -542,8 +546,10 @@
         * This will be false on "data only" devices which can't make voice
         * calls and don't support any in-call UI.
         */
-        mIsVoiceCapable = mContext.getResources().getBoolean(
-                com.android.internal.R.bool.config_voice_capable);
+        mIsVoiceCapable = ((TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE))
+                .isVoiceCapable();
+
+        mCarrierPrivilegesTracker = new CarrierPrivilegesTracker(mLooper, this, mContext);
 
         /**
          *  Some RIL's don't always send RIL_UNSOL_CALL_RING so it needs
@@ -555,35 +561,17 @@
          * the RIL_UNSOL_CALL_RING so the default if there is no property is
          * true.
          */
-        mDoesRilSendMultipleCallRing = SystemProperties.getBoolean(
-                TelephonyProperties.PROPERTY_RIL_SENDS_MULTIPLE_CALL_RING, true);
+        mDoesRilSendMultipleCallRing = TelephonyProperties.ril_sends_multiple_call_ring()
+                .orElse(true);
         Rlog.d(LOG_TAG, "mDoesRilSendMultipleCallRing=" + mDoesRilSendMultipleCallRing);
 
-        mCallRingDelay = SystemProperties.getInt(
-                TelephonyProperties.PROPERTY_CALL_RING_DELAY, 3000);
+        mCallRingDelay = TelephonyProperties.call_ring_delay().orElse(3000);
         Rlog.d(LOG_TAG, "mCallRingDelay=" + mCallRingDelay);
 
         if (getPhoneType() == PhoneConstants.PHONE_TYPE_IMS) {
             return;
         }
 
-        // The locale from the "ro.carrier" system property or R.array.carrier_properties.
-        // This will be overwritten by the Locale from the SIM language settings (EF-PL, EF-LI)
-        // if applicable.
-        final Locale carrierLocale = getLocaleFromCarrierProperties(mContext);
-        if (carrierLocale != null && !TextUtils.isEmpty(carrierLocale.getCountry())) {
-            final String country = carrierLocale.getCountry();
-            try {
-                Settings.Global.getInt(mContext.getContentResolver(),
-                        Settings.Global.WIFI_COUNTRY_CODE);
-            } catch (Settings.SettingNotFoundException e) {
-                // note this is not persisting
-                WifiManager wM = (WifiManager)
-                        mContext.getSystemService(Context.WIFI_SERVICE);
-                wM.setCountryCode(country);
-            }
-        }
-
         // Initialize device storage and outgoing SMS usage monitors for SMSDispatchers.
         mTelephonyComponentFactory = telephonyComponentFactory;
         mSmsStorageMonitor = mTelephonyComponentFactory.inject(SmsStorageMonitor.class.getName())
@@ -598,40 +586,25 @@
         if (getPhoneType() != PhoneConstants.PHONE_TYPE_SIP) {
             mCi.registerForSrvccStateChanged(this, EVENT_SRVCC_STATE_CHANGED, null);
         }
-        mCi.setOnUnsolOemHookRaw(this, EVENT_UNSOL_OEM_HOOK_RAW, null);
         mCi.startLceService(DEFAULT_REPORT_INTERVAL_MS, LCE_PULL_MODE,
                 obtainMessage(EVENT_CONFIG_LCE));
     }
 
     /**
-     * Start listening for IMS service UP/DOWN events. If using the new ImsResolver APIs, we should
-     * always be setting up ImsPhones.
+     * Start setup of ImsPhone, which will start trying to connect to the ImsResolver. Will not be
+     * called if this device does not support FEATURE_IMS_TELEPHONY.
      */
-    public void startMonitoringImsService() {
+    public void createImsPhone() {
         if (getPhoneType() == PhoneConstants.PHONE_TYPE_SIP) {
             return;
         }
 
         synchronized(Phone.lockForRadioTechnologyChange) {
-            IntentFilter filter = new IntentFilter();
-            ImsManager imsManager = ImsManager.getInstance(mContext, getPhoneId());
-            // Don't listen to deprecated intents using the new dynamic binding.
-            if (imsManager != null && !imsManager.isDynamicBinding()) {
-                filter.addAction(ImsManager.ACTION_IMS_SERVICE_UP);
-                filter.addAction(ImsManager.ACTION_IMS_SERVICE_DOWN);
-                mContext.registerReceiver(mImsIntentReceiver, filter);
-            }
-
-            // Monitor IMS service - but first poll to see if already up (could miss
-            // intent). Also, when using new ImsResolver APIs, the service will be available soon,
-            // so start trying to bind.
-            if (imsManager != null) {
-                // If it is dynamic binding, kick off ImsPhone creation now instead of waiting for
-                // the service to be available.
-                if (imsManager.isDynamicBinding() || imsManager.isServiceAvailable()) {
-                    mImsServiceReady = true;
-                    updateImsPhone();
-                }
+            if (mImsPhone == null) {
+                mImsPhone = PhoneFactory.makeImsPhone(mNotifier, this);
+                CallManager.getInstance().registerPhone(mImsPhone);
+                mImsPhone.registerForSilentRedial(
+                        this, EVENT_INITIATE_SILENT_REDIAL, null);
             }
         }
     }
@@ -661,6 +634,24 @@
     }
 
     /**
+     * Check if sending CLIR activation("*31#") and deactivation("#31#") code only without dialing
+     * number is prevented.
+     *
+     * @return {@code true} when carrier config
+     * "KEY_PREVENT_CLIR_ACTIVATION_AND_DEACTIVATION_CODE_BOOL" is set to {@code true}
+     */
+    public boolean isClirActivationAndDeactivationPrevented() {
+        CarrierConfigManager configManager = (CarrierConfigManager)
+                getContext().getSystemService(Context.CARRIER_CONFIG_SERVICE);
+        PersistableBundle b = configManager.getConfigForSubId(getSubId());
+        if (b == null) {
+            b = CarrierConfigManager.getDefaultConfig();
+        }
+        return b.getBoolean(
+                CarrierConfigManager.KEY_PREVENT_CLIR_ACTIVATION_AND_DEACTIVATION_CODE_BOOL);
+    }
+
+    /**
      * When overridden the derived class needs to call
      * super.handleMessage(msg) so this method has a
      * a chance to process the message.
@@ -717,9 +708,17 @@
                     String dialString = (String) ar.result;
                     if (TextUtils.isEmpty(dialString)) return;
                     try {
-                        dialInternal(dialString, new DialArgs.Builder().build());
+                        Connection cn = dialInternal(dialString, new DialArgs.Builder().build());
+                        Rlog.d(LOG_TAG, "Notify redial connection changed cn: " + cn);
+                        if (mImsPhone != null) {
+                            // Don't care it is null or not.
+                            mImsPhone.notifyRedialConnectionChanged(cn);
+                        }
                     } catch (CallStateException e) {
                         Rlog.e(LOG_TAG, "silent redial failed: " + e);
+                        if (mImsPhone != null) {
+                            mImsPhone.notifyRedialConnectionChanged(null);
+                        }
                     }
                 }
                 break;
@@ -734,13 +733,7 @@
                 break;
 
             case EVENT_UNSOL_OEM_HOOK_RAW:
-                ar = (AsyncResult)msg.obj;
-                if (ar.exception == null) {
-                    byte[] data = (byte[])ar.result;
-                    mNotifier.notifyOemHookRawEventForSubscriber(this, data);
-                } else {
-                    Rlog.e(LOG_TAG, "OEM hook raw exception: " + ar.exception);
-                }
+                // deprecated, ignore
                 break;
 
             case EVENT_CONFIG_LCE:
@@ -781,6 +774,12 @@
     public void unregisterForSilentRedial(Handler h) {
     }
 
+    public void registerForVolteSilentRedial(Handler h, int what, Object obj) {
+    }
+
+    public void unregisterForVolteSilentRedial(Handler h) {
+    }
+
     private void handleSrvccStateChanged(int[] ret) {
         Rlog.d(LOG_TAG, "handleSrvccStateChanged");
 
@@ -914,6 +913,30 @@
        mHandoverRegistrants.notifyRegistrants(ar);
     }
 
+    /**
+     * Notifies when a Handover happens due to Silent Redial
+     */
+    public void registerForRedialConnectionChanged(Handler h, int what, Object obj) {
+        checkCorrectThread(h);
+        mRedialRegistrants.addUnique(h, what, obj);
+    }
+
+    /**
+     * Unregisters for redial connection notifications
+     */
+    public void unregisterForRedialConnectionChanged(Handler h) {
+        mRedialRegistrants.remove(h);
+    }
+
+    /**
+     * Subclasses of Phone probably want to replace this with a
+     * version scoped to their packages
+     */
+    public void notifyRedialConnectionChanged(Connection cn) {
+        AsyncResult ar = new AsyncResult(null, cn, null);
+        mRedialRegistrants.notifyRegistrants(ar);
+    }
+
     protected void setIsInEmergencyCall() {
     }
 
@@ -985,14 +1008,20 @@
         migrate(mUnknownConnectionRegistrants, from.mUnknownConnectionRegistrants);
         migrate(mSuppServiceFailedRegistrants, from.mSuppServiceFailedRegistrants);
         migrate(mCellInfoRegistrants, from.mCellInfoRegistrants);
+        migrate(mRedialRegistrants, from.mRedialRegistrants);
         // The emergency state of IMS phone will be cleared in ImsPhone#notifySrvccState after
         // receive SRVCC completed
         if (from.isInEmergencyCall()) {
             setIsInEmergencyCall();
         }
+        setEcmCanceledForEmergency(from.isEcmCanceledForEmergency());
     }
 
     protected void migrate(RegistrantList to, RegistrantList from) {
+        if (from == null) {
+            // May be null in some cases, such as testing.
+            return;
+        }
         from.removeCleared();
         for (int i = 0, n = from.size(); i < n; i++) {
             Registrant r = (Registrant) from.get(i);
@@ -1392,12 +1421,13 @@
         nsm.operatorAlphaShort = network.getOperatorAlphaShort();
 
         Message msg = obtainMessage(EVENT_SET_NETWORK_MANUAL_COMPLETE, nsm);
-        mCi.setNetworkSelectionModeManual(network.getOperatorNumeric(), msg);
+        mCi.setNetworkSelectionModeManual(network.getOperatorNumeric(), network.getRan(), msg);
 
         if (persistSelection) {
             updateSavedNetworkOperator(nsm);
         } else {
             clearSavedNetworkSelection();
+            updateManualNetworkSelection(nsm);
         }
     }
 
@@ -1440,6 +1470,15 @@
     }
 
     /**
+     * Update non-perisited manual network selection.
+     *
+     * @param nsm PLMN info of the selected network
+     */
+    protected void updateManualNetworkSelection(NetworkSelectMessage nsm)  {
+        Rlog.e(LOG_TAG, "updateManualNetworkSelection() should be overridden");
+    }
+
+    /**
      * Used to track the settings upon completion of the network change.
      */
     private void handleSetSelectNetwork(AsyncResult ar) {
@@ -1463,7 +1502,8 @@
     /**
      * Method to retrieve the saved operator from the Shared Preferences
      */
-    private OperatorInfo getSavedNetworkSelection() {
+    @NonNull
+    public OperatorInfo getSavedNetworkSelection() {
         // open the shared preferences and search with our key.
         SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(getContext());
         String numeric = sp.getString(NETWORK_SELECTION_KEY + getSubId(), "");
@@ -1508,9 +1548,9 @@
         // Open the shared preferences editor, and write the value.
         SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(getContext());
         SharedPreferences.Editor editor = sp.edit();
-        editor.putInt(CLIR_KEY + getPhoneId(), commandInterfaceCLIRMode);
-        Rlog.i(LOG_TAG, "saveClirSetting: " + CLIR_KEY + getPhoneId() + "=" +
-                commandInterfaceCLIRMode);
+        editor.putInt(CLIR_KEY + getSubId(), commandInterfaceCLIRMode);
+        Rlog.i(LOG_TAG, "saveClirSetting: " + CLIR_KEY + getSubId() + "="
+                + commandInterfaceCLIRMode);
 
         // Commit and log the result.
         if (!editor.commit()) {
@@ -1679,18 +1719,36 @@
         }
     }
 
+    private @TelephonyManager.NetworkTypeBitMask long getAllowedNetworkTypes() {
+        long allowedNetworkTypes = TelephonyManager.getAllNetworkTypesBitmask();
+        if (SubscriptionController.getInstance() != null) {
+            String result = SubscriptionController.getInstance().getSubscriptionProperty(
+                    getSubId(),
+                    SubscriptionManager.ALLOWED_NETWORK_TYPES);
+
+            if (result != null) {
+                try {
+                    allowedNetworkTypes = Long.parseLong(result);
+                } catch (NumberFormatException err) {
+                    Rlog.e(LOG_TAG, "allowedNetworkTypes NumberFormat exception");
+                }
+            }
+        }
+        return allowedNetworkTypes;
+    }
     /**
      * Set the properties by matching the carrier string in
      * a string-array resource
      */
-    private static Locale getLocaleFromCarrierProperties(Context ctx) {
+    @Nullable Locale getLocaleFromCarrierProperties() {
         String carrier = SystemProperties.get("ro.carrier");
 
         if (null == carrier || 0 == carrier.length() || "unknown".equals(carrier)) {
             return null;
         }
 
-        CharSequence[] carrierLocales = ctx.getResources().getTextArray(R.array.carrier_properties);
+        CharSequence[] carrierLocales = mContext.getResources().getTextArray(
+                R.array.carrier_properties);
 
         for (int i = 0; i < carrierLocales.length; i+=3) {
             String c = carrierLocales[i].toString();
@@ -1780,6 +1838,20 @@
     }
 
     /**
+     * Retrieves the DeviceStateMonitor of the phone instance.
+     */
+    public DeviceStateMonitor getDeviceStateMonitor() {
+        return null;
+    }
+
+    /**
+     * Retrieves the DisplayInfoController of the phone instance.
+     */
+    public DisplayInfoController getDisplayInfoController() {
+        return null;
+    }
+
+    /**
      * Update voice activation state
      */
     public void setVoiceActivationState(int state) {
@@ -1884,17 +1956,16 @@
     /**
      * @return the current cell location if known
      */
-    @UnsupportedAppUsage
-    public CellLocation getCellLocation() {
-        return getServiceStateTracker().getCellLocation();
+    public CellIdentity getCellIdentity() {
+        return getServiceStateTracker().getCellIdentity();
     }
 
     /**
      * @param workSource calling WorkSource
      * @param rspMsg the response message containing the cell location
      */
-    public void getCellLocation(WorkSource workSource, Message rspMsg) {
-        getServiceStateTracker().requestCellLocation(workSource, rspMsg);
+    public void getCellIdentity(WorkSource workSource, Message rspMsg) {
+        getServiceStateTracker().requestCellIdentity(workSource, rspMsg);
     }
 
     /**
@@ -1922,6 +1993,14 @@
         return mVmCount != 0;
     }
 
+    /**
+     *  Retrieves manually selected network info.
+     */
+    public String getManualNetworkSelectionPlmn() {
+        return "";
+    }
+
+
     private int getCallForwardingIndicatorFromSharedPref() {
         int status = IccRecords.CALL_FORWARDING_STATUS_DISABLED;
         int subId = getSubId();
@@ -1974,18 +2053,40 @@
         editor.apply();
     }
 
+    private @TelephonyManager.NetworkTypeBitMask long getAllowedNetworkTypesForAllReasons() {
+        long allowedNetworkTypes = TelephonyManager.getAllNetworkTypesBitmask();
+        synchronized (mAllowedNetworkTypesForReasons) {
+            for (long networkTypes: mAllowedNetworkTypesForReasons.values()) {
+                allowedNetworkTypes = allowedNetworkTypes & networkTypes;
+            }
+        }
+        return allowedNetworkTypes;
+    }
+
     public void setVoiceCallForwardingFlag(int line, boolean enable, String number) {
         setCallForwardingIndicatorInSharedPref(enable);
-        IccRecords r = mIccRecords.get();
+        IccRecords r = getIccRecords();
         if (r != null) {
             r.setVoiceCallForwardingFlag(line, enable, number);
         }
+        notifyCallForwardingIndicator();
     }
 
-    protected void setVoiceCallForwardingFlag(IccRecords r, int line, boolean enable,
+    /**
+     * Set the voice call forwarding flag for GSM/UMTS and the like SIMs
+     *
+     * @param r to enable/disable
+     * @param line to enable/disable
+     * @param enable
+     * @param number to which CFU is enabled
+     */
+    public void setVoiceCallForwardingFlag(IccRecords r, int line, boolean enable,
                                               String number) {
         setCallForwardingIndicatorInSharedPref(enable);
-        r.setVoiceCallForwardingFlag(line, enable, number);
+        if (r != null) {
+            r.setVoiceCallForwardingFlag(line, enable, number);
+        }
+        notifyCallForwardingIndicator();
     }
 
     /**
@@ -1999,7 +2100,7 @@
             Rlog.e(LOG_TAG, "getCallForwardingIndicator: not possible in CDMA");
             return false;
         }
-        IccRecords r = mIccRecords.get();
+        IccRecords r = getIccRecords();
         int callForwardingIndicator = IccRecords.CALL_FORWARDING_STATUS_UNKNOWN;
         if (r != null) {
             callForwardingIndicator = r.getVoiceCallForwardingFlag();
@@ -2077,6 +2178,55 @@
     }
 
     /**
+     * Get the effective allowed network types on the device.
+     * @return effective network type
+     */
+    public @TelephonyManager.NetworkTypeBitMask long getEffectiveAllowedNetworkTypes() {
+        long allowedNetworkTypes = getAllowedNetworkTypes();
+        return allowedNetworkTypes & getAllowedNetworkTypesForAllReasons();
+    }
+
+    /**
+     * Get the allowed network types for a certain reason.
+     * @param reason reason to configure allowed network types
+     * @return the allowed network types.
+     */
+    public @TelephonyManager.NetworkTypeBitMask long getAllowedNetworkTypes(
+            @TelephonyManager.AllowedNetworkTypesReason int reason) {
+        synchronized (mAllowedNetworkTypesForReasons) {
+            switch (reason) {
+                case TelephonyManager.ALLOWED_NETWORK_TYPES_REASON_POWER:
+                    return mAllowedNetworkTypesForReasons.getOrDefault(
+                            TelephonyManager.ALLOWED_NETWORK_TYPES_REASON_POWER,
+                            TelephonyManager.getAllNetworkTypesBitmask());
+                default:
+                    Rlog.e(LOG_TAG, "Invalid allowed network type reason: " + reason);
+                    return TelephonyManager.getAllNetworkTypesBitmask();
+            }
+        }
+    }
+
+    /**
+     * Requests to set the allowed network types for a specific reason
+     * @param reason reason to configure allowed network type
+     * @param networkTypes one of the network types
+     */
+    public void setAllowedNetworkTypes(@TelephonyManager.AllowedNetworkTypesReason int reason,
+            @TelephonyManager.NetworkTypeBitMask long networkTypes) {
+        synchronized (mAllowedNetworkTypesForReasons) {
+            switch (reason) {
+                case TelephonyManager.ALLOWED_NETWORK_TYPES_REASON_POWER:
+                    mAllowedNetworkTypesForReasons.put(
+                            TelephonyManager.ALLOWED_NETWORK_TYPES_REASON_POWER, networkTypes);
+                    break;
+                default:
+                    Rlog.e(LOG_TAG, "Invalid allowed network type reason: " + reason);
+                    break;
+            }
+        }
+    }
+
+    /**
      *  Requests to set the preferred network type for searching and registering
      * (CS/PS domain, RAT, and operation mode)
      * @param networkType one of  NT_*_TYPE
@@ -2088,6 +2238,7 @@
         int modemRaf = getRadioAccessFamily();
         int rafFromType = RadioAccessFamily.getRafFromNetworkType(networkType);
 
+        long allowedNetworkTypes = getAllowedNetworkTypes();
         if (modemRaf == RadioAccessFamily.RAF_UNKNOWN
                 || rafFromType == RadioAccessFamily.RAF_UNKNOWN) {
             Rlog.d(LOG_TAG, "setPreferredNetworkType: Abort, unknown RAF: "
@@ -2102,19 +2253,24 @@
             return;
         }
 
-        int filteredRaf = (rafFromType & modemRaf);
+        int filteredRaf = (int) (rafFromType & modemRaf & allowedNetworkTypes
+                & getAllowedNetworkTypesForAllReasons());
         int filteredType = RadioAccessFamily.getNetworkTypeFromRaf(filteredRaf);
-
+        long powerAllowedNetworkTypes = getAllowedNetworkTypes(
+                TelephonyManager.ALLOWED_NETWORK_TYPES_REASON_POWER);
         Rlog.d(LOG_TAG, "setPreferredNetworkType: networkType = " + networkType
                 + " modemRaf = " + modemRaf
                 + " rafFromType = " + rafFromType
+                + " allowedNetworkTypes = " + allowedNetworkTypes
+                + " power allowedNetworkTypes = " + powerAllowedNetworkTypes
                 + " filteredType = " + filteredType);
 
         mCi.setPreferredNetworkType(filteredType, response);
+        mPreferredNetworkTypeRegistrants.notifyRegistrants();
     }
 
     /**
-     *  Query the preferred network type setting
+     * Query the preferred network type setting
      *
      * @param response is callback message to report one of  NT_*_TYPE
      */
@@ -2123,6 +2279,38 @@
     }
 
     /**
+     * Register for preferred network type changes
+     *
+     * @param h Handler that receives the notification message.
+     * @param what User-defined message code.
+     * @param obj User object.
+     */
+    public void registerForPreferredNetworkTypeChanged(Handler h, int what, Object obj) {
+        checkCorrectThread(h);
+        mPreferredNetworkTypeRegistrants.addUnique(h, what, obj);
+    }
+
+    /**
+     * Unregister for preferred network type changes.
+     *
+     * @param h Handler that should be unregistered.
+     */
+    public void unregisterForPreferredNetworkTypeChanged(Handler h) {
+        mPreferredNetworkTypeRegistrants.remove(h);
+    }
+
+    /**
+     * Get the cached value of the preferred network type setting
+     */
+    public int getCachedPreferredNetworkType() {
+        if (mCi != null && mCi instanceof BaseCommands) {
+            return ((BaseCommands) mCi).mPreferredNetworkType;
+        } else {
+            return RILConstants.PREFERRED_NETWORK_MODE;
+        }
+    }
+
+    /**
      * Gets the default SMSC address.
      *
      * @param result Callback message contains the SMSC address.
@@ -2329,6 +2517,11 @@
         mCi.nvResetConfig(2 /* erase NV */, response);
     }
 
+    public void setSystemSelectionChannels(List<RadioAccessSpecifier> specifiers,
+            Message response) {
+        mCi.setSystemSelectionChannels(specifiers, response);
+    }
+
     public void notifyDataActivity() {
         mNotifier.notifyDataActivity(this);
     }
@@ -2342,17 +2535,16 @@
         mNotifier.notifyMessageWaitingChanged(this);
     }
 
+    /** Send notification with an updated PreciseDataConnectionState to a single data connection */
     public void notifyDataConnection(String apnType) {
-        mNotifier.notifyDataConnection(this, apnType, getDataConnectionState(apnType));
+        mNotifier.notifyDataConnection(this, apnType, getPreciseDataConnectionState(apnType));
     }
 
-    public void notifyDataConnection() {
+    /** Send notification with an updated PreciseDataConnectionState to all data connections */
+    public void notifyAllActiveDataConnections() {
         String types[] = getActiveApnTypes();
-        if (types != null) {
-            for (String apnType : types) {
-                mNotifier.notifyDataConnection(this, apnType,
-                        getDataConnectionState(apnType));
-            }
+        for (String apnType : types) {
+            mNotifier.notifyDataConnection(this, apnType, getPreciseDataConnectionState(apnType));
         }
     }
 
@@ -2373,6 +2565,11 @@
         mNotifier.notifyUserMobileDataStateChanged(this, state);
     }
 
+    /** Send notification that display info has changed. */
+    public void notifyDisplayInfoChanged(TelephonyDisplayInfo telephonyDisplayInfo) {
+        mNotifier.notifyDisplayInfoChanged(this, telephonyDisplayInfo);
+    }
+
     public void notifySignalStrength() {
         mNotifier.notifySignalStrength(this);
     }
@@ -2381,6 +2578,11 @@
         return PhoneConstants.DataState.DISCONNECTED;
     }
 
+    /** Default implementation to get the PreciseDataConnectionState */
+    public @Nullable PreciseDataConnectionState getPreciseDataConnectionState(String apnType) {
+        return null;
+    }
+
     public void notifyCellInfo(List<CellInfo> cellInfo) {
         AsyncResult ar = new AsyncResult(null, cellInfo, null);
         mCellInfoRegistrants.notifyRegistrants(ar);
@@ -2388,9 +2590,34 @@
         mNotifier.notifyCellInfo(this, cellInfo);
     }
 
+    /**
+     * Registration point for PhysicalChannelConfig change.
+     * @param h handler to notify
+     * @param what what code of message when delivered
+     * @param obj placed in Message.obj.userObj
+     */
+    public void registerForPhysicalChannelConfig(Handler h, int what, Object obj) {
+        checkCorrectThread(h);
+        Registrant registrant = new Registrant(h, what, obj);
+        mPhysicalChannelConfigRegistrants.add(registrant);
+        // notify first
+        List<PhysicalChannelConfig> physicalChannelConfigs = getPhysicalChannelConfigList();
+        if (physicalChannelConfigs != null) {
+            registrant.notifyRegistrant(new AsyncResult(null, physicalChannelConfigs, null));
+        }
+    }
+
+    public void unregisterForPhysicalChannelConfig(Handler h) {
+        mPhysicalChannelConfigRegistrants.remove(h);
+    }
+
     /** Notify {@link PhysicalChannelConfig} changes. */
     public void notifyPhysicalChannelConfiguration(List<PhysicalChannelConfig> configs) {
-        mNotifier.notifyPhysicalChannelConfiguration(this, configs);
+        mPhysicalChannelConfigRegistrants.notifyRegistrants(new AsyncResult(null, configs, null));
+    }
+
+    public List<PhysicalChannelConfig> getPhysicalChannelConfigList() {
+        return null;
     }
 
     /**
@@ -2405,6 +2632,16 @@
         mNotifier.notifyEmergencyNumberList(this);
     }
 
+    /** Notify the outgoing call {@link EmergencyNumber} changes. */
+    public void notifyOutgoingEmergencyCall(EmergencyNumber emergencyNumber) {
+        mNotifier.notifyOutgoingEmergencyCall(this, emergencyNumber);
+    }
+
+    /** Notify the outgoing Sms {@link EmergencyNumber} changes. */
+    public void notifyOutgoingEmergencySms(EmergencyNumber emergencyNumber) {
+        mNotifier.notifyOutgoingEmergencySms(this, emergencyNumber);
+    }
+
     /**
      * @return true if a mobile originating emergency call is active
      */
@@ -2415,7 +2652,7 @@
     // This property is used to handle phone process crashes, and is the same for CDMA and IMS
     // phones
     protected static boolean getInEcmMode() {
-        return SystemProperties.getBoolean(TelephonyProperties.PROPERTY_INECM_MODE, false);
+        return TelephonyProperties.in_ecm_mode().orElse(false);
     }
 
     /**
@@ -2427,11 +2664,35 @@
         return mIsPhoneInEcmState;
     }
 
+    public boolean isInImsEcm() {
+        return false;
+    }
+
     public void setIsInEcm(boolean isInEcm) {
-        setGlobalSystemProperty(TelephonyProperties.PROPERTY_INECM_MODE, String.valueOf(isInEcm));
+        if (!getUnitTestMode()) {
+            TelephonyProperties.in_ecm_mode(isInEcm);
+        }
         mIsPhoneInEcmState = isInEcm;
     }
 
+    /**
+     * @return true if this Phone is in an emergency call that caused emergency callback mode to be
+     * canceled, false if not.
+     */
+    public boolean isEcmCanceledForEmergency() {
+        return mEcmCanceledForEmergency;
+    }
+
+    /**
+     * Set whether or not this Phone has an active emergency call that was placed during emergency
+     * callback mode and caused it to be temporarily canceled.
+     * @param isCanceled true if an emergency call was placed that caused ECM to be canceled, false
+     *                   if it is not in this state.
+     */
+    public void setEcmCanceledForEmergency(boolean isCanceled) {
+        mEcmCanceledForEmergency = isCanceled;
+    }
+
     @UnsupportedAppUsage
     private static int getVideoState(Call call) {
         int videoState = VideoProfile.STATE_AUDIO_ONLY;
@@ -2577,7 +2838,7 @@
             options.setBackgroundActivityStartsAllowed(true);
             Intent intent = new Intent(TelephonyIntents.SECRET_CODE_ACTION,
                     Uri.parse("android_secret_code://" + code));
-            intent.addFlags(Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
+            intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
             mContext.sendBroadcast(intent, null, options.toBundle());
 
             // {@link TelephonyManager.ACTION_SECRET_CODE} will replace {@link
@@ -2585,7 +2846,7 @@
             // that both of these two actions will be broadcast.
             Intent secrectCodeIntent = new Intent(TelephonyManager.ACTION_SECRET_CODE,
                     Uri.parse("android_secret_code://" + code));
-            secrectCodeIntent.addFlags(Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
+            secrectCodeIntent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
             mContext.sendBroadcast(secrectCodeIntent, null, options.toBundle());
         }
     }
@@ -3038,26 +3299,26 @@
 
     /**
      * Returns an array of string identifiers for the APN types serviced by the
-     * currently active.
+     * currently active subscription.
      *
      * @return The string array of APN types. Return null if no active APN types.
      */
     @UnsupportedAppUsage
-    @Nullable
+    @NonNull
     public String[] getActiveApnTypes() {
-        if (mTransportManager != null) {
-            List<String> typesList = new ArrayList<>();
-            for (int transportType : mTransportManager.getAvailableTransports()) {
-                if (getDcTracker(transportType) != null) {
-                    typesList.addAll(Arrays.asList(
-                            getDcTracker(transportType).getActiveApnTypes()));
-                }
-            }
-
-            return typesList.toArray(new String[typesList.size()]);
+        if (mTransportManager == null || mDcTrackers == null)  {
+            Rlog.e(LOG_TAG, "Invalid state for Transport/DcTrackers");
+            return new String[0];
         }
 
-        return null;
+        Set<String> activeApnTypes = new HashSet<String>();
+        for (int transportType : mTransportManager.getAvailableTransports()) {
+            DcTracker dct = getDcTracker(transportType);
+            if (dct == null) continue; // TODO: should this ever happen?
+            activeApnTypes.addAll(Arrays.asList(dct.getActiveApnTypes()));
+        }
+
+        return activeApnTypes.toArray(new String[activeApnTypes.size()]);
     }
 
     /**
@@ -3233,6 +3494,17 @@
     }
 
     /**
+     * Enable or disable always reporting signal strength changes from radio.
+     *
+     * @param isEnable {@code true} for enabling; {@code false} for disabling.
+     */
+    public void setAlwaysReportSignalStrength(boolean isEnable) {
+        if (mDeviceStateMonitor != null) {
+            mDeviceStateMonitor.setAlwaysReportSignalStrength(isEnable);
+        }
+    }
+
+    /**
      * TODO: Adding a function for each property is not good.
      * A fucntion of type getPhoneProp(propType) where propType is an
      * enum of GSM+CDMA+LTE props would be a better approach.
@@ -3287,13 +3559,10 @@
     public void notifyCallForwardingIndicator() {
     }
 
-    public void notifyDataConnectionFailed(String apnType) {
-        mNotifier.notifyDataConnectionFailed(this, apnType);
-    }
-
-    public void notifyPreciseDataConnectionFailed(String apnType, String apn,
-            @DataFailCause.FailCause int failCause) {
-        mNotifier.notifyPreciseDataConnectionFailed(this, apnType, apn, failCause);
+    /** Send a notification that a particular data connection has failed with specified cause. */
+    public void notifyDataConnectionFailed(
+            String apnType, String apn, @DataFailureCause int failCause) {
+        mNotifier.notifyDataConnectionFailed(this, apnType, apn, failCause);
     }
 
     /**
@@ -3414,6 +3683,10 @@
         return TelephonyManager.UNKNOWN_CARRIER_ID_LIST_VERSION;
     }
 
+    public int getEmergencyNumberDbVersion() {
+        return TelephonyManager.INVALID_EMERGENCY_NUMBER_DB_VERSION;
+    }
+
     public void resolveSubscriptionCarrierId(String simState) {
     }
 
@@ -3441,28 +3714,6 @@
     public void dispose() {
     }
 
-    private void updateImsPhone() {
-        Rlog.d(LOG_TAG, "updateImsPhone"
-                + " mImsServiceReady=" + mImsServiceReady);
-
-        if (mImsServiceReady && (mImsPhone == null)) {
-            mImsPhone = PhoneFactory.makeImsPhone(mNotifier, this);
-            CallManager.getInstance().registerPhone(mImsPhone);
-            mImsPhone.registerForSilentRedial(
-                    this, EVENT_INITIATE_SILENT_REDIAL, null);
-        } else if (!mImsServiceReady && (mImsPhone != null)) {
-            CallManager.getInstance().unregisterPhone(mImsPhone);
-            mImsPhone.unregisterForSilentRedial(this);
-
-            mImsPhone.dispose();
-            // Potential GC issue if someone keeps a reference to ImsPhone.
-            // However: this change will make sure that such a reference does
-            // not access functions through NULL pointer.
-            //mImsPhone.removeReferences();
-            mImsPhone = null;
-        }
-    }
-
     /**
      * Dials a number.
      *
@@ -3611,7 +3862,7 @@
      * @return true if the IMS capability for the registration technology specified is available,
      * false otherwise.
      */
-    public boolean isImsCapabilityAvailable(int capability, int regTech) {
+    public boolean isImsCapabilityAvailable(int capability, int regTech) throws ImsException {
         Phone imsPhone = mImsPhone;
         boolean isAvailable = false;
         if (imsPhone != null) {
@@ -3649,6 +3900,31 @@
         return regTech;
     }
 
+    /**
+     * Get the IMS MmTel Registration technology for this Phone, defined in
+     * {@link ImsRegistrationImplBase}.
+     */
+    public void getImsRegistrationTech(Consumer<Integer> callback) {
+        Phone imsPhone = mImsPhone;
+        if (imsPhone != null) {
+            imsPhone.getImsRegistrationTech(callback);
+        } else {
+            callback.accept(ImsRegistrationImplBase.REGISTRATION_TECH_NONE);
+        }
+    }
+
+    /**
+     * Asynchronously get the IMS MmTel Registration state for this Phone.
+     */
+    public void getImsRegistrationState(Consumer<Integer> callback) {
+        Phone imsPhone = mImsPhone;
+        if (imsPhone != null) {
+            imsPhone.getImsRegistrationState(callback);
+        }
+        callback.accept(RegistrationManager.REGISTRATION_STATE_NOT_REGISTERED);
+    }
+
+
     private boolean getRoamingOverrideHelper(String prefix, String key) {
         String iccId = getIccSerialNumber();
         if (TextUtils.isEmpty(iccId) || TextUtils.isEmpty(key)) {
@@ -3868,7 +4144,8 @@
     }
 
     /** Sets the SignalStrength reporting criteria. */
-    public void setSignalStrengthReportingCriteria(int[] thresholds, int ran) {
+    public void setSignalStrengthReportingCriteria(
+            int signalStrengthMeasure, int[] thresholds, int ran, boolean isEnabled) {
         // no-op default implementation
     }
 
@@ -3894,17 +4171,7 @@
             return new Locale(records.getSimLanguage());
         }
 
-        return getLocaleFromCarrierProperties(mContext);
-    }
-
-    public void updateDataConnectionTracker() {
-        if (mTransportManager != null) {
-            for (int transport : mTransportManager.getAvailableTransports()) {
-                if (getDcTracker(transport) != null) {
-                    getDcTracker(transport).update();
-                }
-            }
-        }
+        return getLocaleFromCarrierProperties();
     }
 
     public boolean updateCurrentCarrierInProvider() {
@@ -4010,18 +4277,6 @@
     }
 
     /**
-     * Get aggregated video call data usage since boot.
-     * Permissions android.Manifest.permission.READ_NETWORK_USAGE_HISTORY is required.
-     *
-     * @param perUidStats True if requesting data usage per uid, otherwise overall usage.
-     * @return Snapshot of video call data usage
-     */
-    public NetworkStats getVtDataUsage(boolean perUidStats) {
-        if (mImsPhone == null) return null;
-        return mImsPhone.getVtDataUsage(perUidStats);
-    }
-
-    /**
      * SIP URIs aliased to the current subscriber given by the IMS implementation.
      * Applicable only on IMS; used in absence of line1number.
      * @return array of SIP URIs aliased to the current subscriber
@@ -4045,12 +4300,6 @@
         mCi.setSimCardPower(state, null, workSource);
     }
 
-    public void setRadioIndicationUpdateMode(int filters, int mode) {
-        if (mDeviceStateMonitor != null) {
-            mDeviceStateMonitor.setIndicationUpdateMode(filters, mode);
-        }
-    }
-
     public void setCarrierTestOverride(String mccmnc, String imsi, String iccid, String gid1,
             String gid2, String pnn, String spn, String carrierPrivilegeRules, String apn) {
     }
@@ -4069,7 +4318,7 @@
                 ServiceState ss = phone.getServiceStateTracker().getServiceState();
                 // One of the phone is in service, hence the device is not emergency call only.
                 if (ss.getState() == ServiceState.STATE_IN_SERVICE
-                        || ss.getDataRegState() == ServiceState.STATE_IN_SERVICE) {
+                        || ss.getDataRegistrationState() == ServiceState.STATE_IN_SERVICE) {
                     return false;
                 }
                 isEmergencyCallOnly |= ss.isEmergencyOnly();
@@ -4094,6 +4343,20 @@
     }
 
     /**
+     * Enable or disable uicc applications.
+     * @param enable whether to enable or disable uicc applications.
+     * @param onCompleteMessage callback for async operation. Ignored if blockingCall is true.
+     */
+    public void enableUiccApplications(boolean enable, Message onCompleteMessage) {}
+
+    /**
+     * Whether disabling a physical subscription is supported or not.
+     */
+    public boolean canDisablePhysicalSubscription() {
+        return false;
+    }
+
+    /**
      * Get the HAL version.
      *
      * @return the current HalVersion
@@ -4105,6 +4368,32 @@
         return RIL.RADIO_HAL_VERSION_UNKNOWN;
     }
 
+    /**
+     * Get the SIM's MCC/MNC
+     *
+     * @return MCC/MNC in string format, empty string if not available.
+     */
+    @NonNull
+    public String getOperatorNumeric() {
+        return "";
+    }
+
+    /** Returns the {@link VoiceCallSessionStats} for this phone ID. */
+    public VoiceCallSessionStats getVoiceCallSessionStats() {
+        return mVoiceCallSessionStats;
+    }
+
+    /** Sets the {@link VoiceCallSessionStats} mock for this phone ID during unit testing. */
+    @VisibleForTesting
+    public void setVoiceCallSessionStats(VoiceCallSessionStats voiceCallSessionStats) {
+        mVoiceCallSessionStats = voiceCallSessionStats;
+    }
+
+    /** @hide */
+    public CarrierPrivilegesTracker getCarrierPrivilegesTracker() {
+        return mCarrierPrivilegesTracker;
+    }
+
     public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
         pw.println("Phone: subId=" + getSubId());
         pw.println(" mPhoneId=" + mPhoneId);
@@ -4140,6 +4429,7 @@
         pw.println(" getActiveApnTypes()=" + getActiveApnTypes());
         pw.println(" needsOtaServiceProvisioning=" + needsOtaServiceProvisioning());
         pw.println(" isInEmergencySmsMode=" + isInEmergencySmsMode());
+        pw.println(" isEcmCanceledForEmergency=" + isEcmCanceledForEmergency());
         pw.println(" service state=" + getServiceState());
         pw.flush();
         pw.println("++++++++++++++++++++++++++++++++");
@@ -4187,6 +4477,17 @@
             pw.println("++++++++++++++++++++++++++++++++");
         }
 
+        if (getDisplayInfoController() != null) {
+            try {
+                getDisplayInfoController().dump(fd, pw, args);
+            } catch (Exception e) {
+                e.printStackTrace();
+            }
+
+            pw.flush();
+            pw.println("++++++++++++++++++++++++++++++++");
+        }
+
         if (mCarrierResolver != null) {
             try {
                 mCarrierResolver.dump(fd, pw, args);
@@ -4263,6 +4564,12 @@
             pw.println("++++++++++++++++++++++++++++++++");
         }
 
+        if (getCarrierPrivilegesTracker() != null) {
+            pw.println("CarrierPrivilegesTracker:");
+            getCarrierPrivilegesTracker().dump(fd, pw, args);
+            pw.println("++++++++++++++++++++++++++++++++");
+        }
+
         pw.println("Phone Local Log: ");
         if (mLocalLog != null) {
             try {
diff --git a/src/java/com/android/internal/telephony/PhoneConfigurationManager.java b/src/java/com/android/internal/telephony/PhoneConfigurationManager.java
index 197a7f3..02fea56 100644
--- a/src/java/com/android/internal/telephony/PhoneConfigurationManager.java
+++ b/src/java/com/android/internal/telephony/PhoneConfigurationManager.java
@@ -16,18 +16,26 @@
 
 package com.android.internal.telephony;
 
+import static android.telephony.TelephonyManager.ACTION_MULTI_SIM_CONFIG_CHANGED;
+import static android.telephony.TelephonyManager.EXTRA_ACTIVE_SIM_SUPPORTED_COUNT;
+
 import android.content.Context;
+import android.content.Intent;
 import android.os.AsyncResult;
 import android.os.Handler;
 import android.os.Message;
 import android.os.PowerManager;
-import android.os.SystemProperties;
+import android.os.RegistrantList;
 import android.os.storage.StorageManager;
+import android.sysprop.TelephonyProperties;
 import android.telephony.PhoneCapability;
-import android.telephony.Rlog;
+import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyManager;
 import android.util.Log;
 
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.telephony.Rlog;
+
 import java.util.HashMap;
 import java.util.Map;
 import java.util.NoSuchElementException;
@@ -53,9 +61,12 @@
     private final Context mContext;
     private PhoneCapability mStaticCapability;
     private final RadioConfig mRadioConfig;
-    private final MainThreadHandler mHandler;
+    private final Handler mHandler;
     private final Phone[] mPhones;
     private final Map<Integer, Boolean> mPhoneStatusMap;
+    private MockableInterface mMi = new MockableInterface();
+    private TelephonyManager mTelephonyManager;
+    private static final RegistrantList sMultiSimConfigChangeRegistrants = new RegistrantList();
 
     /**
      * Init method to instantiate the object
@@ -79,16 +90,17 @@
     private PhoneConfigurationManager(Context context) {
         mContext = context;
         // TODO: send commands to modem once interface is ready.
-        TelephonyManager telephonyManager = new TelephonyManager(context);
+        mTelephonyManager = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
         //initialize with default, it'll get updated when RADIO is ON/AVAILABLE
         mStaticCapability = getDefaultCapability();
         mRadioConfig = RadioConfig.getInstance(mContext);
-        mHandler = new MainThreadHandler();
+        mHandler = new ConfigManagerHandler();
         mPhoneStatusMap = new HashMap<>();
 
         notifyCapabilityChanged();
 
         mPhones = PhoneFactory.getPhones();
+
         if (!StorageManager.inCryptKeeperBounce()) {
             for (Phone phone : mPhones) {
                 phone.mCi.registerForAvailable(mHandler, Phone.EVENT_RADIO_AVAILABLE, phone);
@@ -122,7 +134,7 @@
     /**
      * Handler class to handle callbacks
      */
-    private final class MainThreadHandler extends Handler {
+    private final class ConfigManagerHandler extends Handler {
         @Override
         public void handleMessage(Message msg) {
             AsyncResult ar;
@@ -146,7 +158,7 @@
                     ar = (AsyncResult) msg.obj;
                     if (ar != null && ar.exception == null) {
                         int numOfLiveModems = msg.arg1;
-                        setMultiSimProperties(numOfLiveModems);
+                        onMultiSimConfigChanged(numOfLiveModems);
                     } else {
                         log(msg.what + " failure. Not switching multi-sim config." + ar.exception);
                     }
@@ -156,7 +168,7 @@
                     if (ar != null && ar.exception == null) {
                         int phoneId = msg.arg1;
                         boolean enabled = (boolean) ar.result;
-                        //update the cache each time getModemStatus is requested
+                        // update the cache each time getModemStatus is requested
                         addToPhoneStatusCache(phoneId, enabled);
                     } else {
                         log(msg.what + " failure. Not updating modem status." + ar.exception);
@@ -270,8 +282,7 @@
      * Returns how many phone objects the device supports.
      */
     public int getPhoneCount() {
-        TelephonyManager tm = new TelephonyManager(mContext);
-        return tm.getPhoneCount();
+        return mTelephonyManager.getActiveModemCount();
     }
 
     /**
@@ -284,6 +295,7 @@
                     mHandler, EVENT_GET_PHONE_CAPABILITY_DONE);
             mRadioConfig.getPhoneCapability(callback);
         }
+        log("getStaticPhoneCapability: mStaticCapability " + mStaticCapability);
         return mStaticCapability;
     }
 
@@ -299,7 +311,7 @@
     }
 
     private void notifyCapabilityChanged() {
-        PhoneNotifier notifier = new DefaultPhoneNotifier();
+        PhoneNotifier notifier = new DefaultPhoneNotifier(mContext);
 
         notifier.notifyPhoneCapabilityChanged(mStaticCapability);
     }
@@ -331,40 +343,124 @@
      * Return value defaults to true
      */
     public boolean isRebootRequiredForModemConfigChange() {
-        String rebootRequired = SystemProperties.get(
-                TelephonyProperties.PROPERTY_REBOOT_REQUIRED_ON_MODEM_CHANGE);
-        log("isRebootRequiredForModemConfigChange: isRebootRequired = " + rebootRequired);
-        return !rebootRequired.equals("false");
+        return mMi.isRebootRequiredForModemConfigChange();
+    }
+
+    private void onMultiSimConfigChanged(int numOfActiveModems) {
+        setMultiSimProperties(numOfActiveModems);
+
+        if (isRebootRequiredForModemConfigChange()) {
+            log("onMultiSimConfigChanged: Rebooting.");
+            PowerManager pm = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
+            pm.reboot("Multi-SIM config changed.");
+        } else {
+            log("onMultiSimConfigChanged: Rebooting is not required.");
+            mMi.notifyPhoneFactoryOnMultiSimConfigChanged(mContext, numOfActiveModems);
+            broadcastMultiSimConfigChange(numOfActiveModems);
+            // Register to RIL service if needed.
+            for (int i = 0; i < mPhones.length; i++) {
+                Phone phone = mPhones[i];
+                phone.mCi.onSlotActiveStatusChange(SubscriptionManager.isValidPhoneId(i));
+            }
+        }
     }
 
     /**
      * Helper method to set system properties for setting multi sim configs,
      * as well as doing the phone reboot
      * NOTE: In order to support more than 3 sims, we need to change this method.
-     * @param numOfSims number of active sims
+     * @param numOfActiveModems number of active sims
      */
-    private void setMultiSimProperties(int numOfSims) {
-        String finalMultiSimConfig;
-        switch(numOfSims) {
-            case 3:
-                finalMultiSimConfig = TSTS;
-                break;
-            case 2:
-                finalMultiSimConfig = DSDS;
-                break;
-            default:
-                finalMultiSimConfig = SSSS;
+    private void setMultiSimProperties(int numOfActiveModems) {
+        mMi.setMultiSimProperties(numOfActiveModems);
+    }
+
+    @VisibleForTesting
+    public static void notifyMultiSimConfigChange(int numOfActiveModems) {
+        sMultiSimConfigChangeRegistrants.notifyResult(numOfActiveModems);
+    }
+
+    /**
+     * Register for multi-SIM configuration change, for example if the devices switched from single
+     * SIM to dual-SIM mode.
+     *
+     * It doesn't trigger callback upon registration as multi-SIM config change is in-frequent.
+     */
+    public static void registerForMultiSimConfigChange(Handler h, int what, Object obj) {
+        sMultiSimConfigChangeRegistrants.addUnique(h, what, obj);
+    }
+
+    /**
+     * Unregister for multi-SIM configuration change.
+     */
+    public static void unregisterForMultiSimConfigChange(Handler h) {
+        sMultiSimConfigChangeRegistrants.remove(h);
+    }
+
+    /**
+     * Unregister for all multi-SIM configuration change events.
+     */
+    public static void unregisterAllMultiSimConfigChangeRegistrants() {
+        sMultiSimConfigChangeRegistrants.removeAll();
+    }
+
+    private void broadcastMultiSimConfigChange(int numOfActiveModems) {
+        log("broadcastSimSlotNumChange numOfActiveModems" + numOfActiveModems);
+        // Notify internal registrants first.
+        notifyMultiSimConfigChange(numOfActiveModems);
+
+        Intent intent = new Intent(ACTION_MULTI_SIM_CONFIG_CHANGED);
+        intent.putExtra(EXTRA_ACTIVE_SIM_SUPPORTED_COUNT, numOfActiveModems);
+        mContext.sendBroadcast(intent);
+    }
+
+    /**
+     * A wrapper class that wraps some methods so that they can be replaced or mocked in unit-tests.
+     *
+     * For example, setting or reading system property are static native methods that can't be
+     * directly mocked. We can mock it by replacing MockableInterface object with a mock instance
+     * in unittest.
+     */
+    @VisibleForTesting
+    public static class MockableInterface {
+        /**
+         * Wrapper function to decide whether reboot is required for modem config change.
+         */
+        @VisibleForTesting
+        public boolean isRebootRequiredForModemConfigChange() {
+            boolean rebootRequired = TelephonyProperties.reboot_on_modem_change().orElse(false);
+            log("isRebootRequiredForModemConfigChange: isRebootRequired = " + rebootRequired);
+            return rebootRequired;
         }
 
-        SystemProperties.set(TelephonyProperties.PROPERTY_MULTI_SIM_CONFIG, finalMultiSimConfig);
-        if (isRebootRequiredForModemConfigChange()) {
-            log("setMultiSimProperties: Rebooting due to switching multi-sim config to "
-                    + finalMultiSimConfig);
-            PowerManager pm = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
-            pm.reboot("Switching to " + finalMultiSimConfig);
-        } else {
-            log("setMultiSimProperties: Rebooting is not required to switch multi-sim config to "
-                    + finalMultiSimConfig);
+        /**
+         * Wrapper function to call setMultiSimProperties.
+         */
+        @VisibleForTesting
+        public void setMultiSimProperties(int numOfActiveModems) {
+            String multiSimConfig;
+            switch(numOfActiveModems) {
+                case 3:
+                    multiSimConfig = TSTS;
+                    break;
+                case 2:
+                    multiSimConfig = DSDS;
+                    break;
+                default:
+                    multiSimConfig = SSSS;
+            }
+
+            log("setMultiSimProperties to " + multiSimConfig);
+            TelephonyProperties.multi_sim_config(multiSimConfig);
+        }
+
+        /**
+         * Wrapper function to call PhoneFactory.onMultiSimConfigChanged.
+         */
+        @VisibleForTesting
+        public void notifyPhoneFactoryOnMultiSimConfigChanged(
+                Context context, int numOfActiveModems) {
+            PhoneFactory.onMultiSimConfigChanged(context, numOfActiveModems);
         }
     }
 
diff --git a/src/java/com/android/internal/telephony/PhoneFactory.java b/src/java/com/android/internal/telephony/PhoneFactory.java
index 452ee25..12a37cd 100644
--- a/src/java/com/android/internal/telephony/PhoneFactory.java
+++ b/src/java/com/android/internal/telephony/PhoneFactory.java
@@ -16,37 +16,42 @@
 
 package com.android.internal.telephony;
 
+import static com.android.internal.telephony.PhoneConstants.PHONE_TYPE_CDMA;
+import static com.android.internal.telephony.PhoneConstants.PHONE_TYPE_CDMA_LTE;
+
+import static java.util.Arrays.copyOf;
+
 import android.annotation.Nullable;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.SharedPreferences;
 import android.content.pm.PackageManager;
 import android.net.LocalServerSocket;
+import android.os.HandlerThread;
 import android.os.Looper;
-import android.os.ServiceManager;
 import android.preference.PreferenceManager;
 import android.provider.Settings;
 import android.provider.Settings.SettingNotFoundException;
 import android.telephony.AnomalyReporter;
-import android.telephony.Rlog;
 import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyManager;
 import android.util.LocalLog;
 
-import com.android.internal.os.BackgroundThread;
 import com.android.internal.telephony.cdma.CdmaSubscriptionSourceManager;
 import com.android.internal.telephony.dataconnection.TelephonyNetworkFactory;
 import com.android.internal.telephony.euicc.EuiccCardController;
 import com.android.internal.telephony.euicc.EuiccController;
-import com.android.internal.telephony.ims.ImsResolver;
 import com.android.internal.telephony.imsphone.ImsPhone;
 import com.android.internal.telephony.imsphone.ImsPhoneFactory;
+import com.android.internal.telephony.metrics.MetricsCollector;
+import com.android.internal.telephony.metrics.TelephonyMetrics;
 import com.android.internal.telephony.sip.SipPhone;
 import com.android.internal.telephony.sip.SipPhoneFactory;
 import com.android.internal.telephony.uicc.UiccController;
 import com.android.internal.telephony.util.NotificationChannelController;
 import com.android.internal.util.IndentingPrintWriter;
+import com.android.telephony.Rlog;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
@@ -77,8 +82,6 @@
     private static @Nullable EuiccController sEuiccController;
     private static @Nullable EuiccCardController sEuiccCardController;
 
-    @UnsupportedAppUsage
-    static private CommandsInterface sCommandsInterface = null;
     static private SubscriptionInfoUpdater sSubInfoRecordUpdater = null;
 
     @UnsupportedAppUsage
@@ -89,13 +92,12 @@
     static private Context sContext;
     static private PhoneConfigurationManager sPhoneConfigurationManager;
     static private PhoneSwitcher sPhoneSwitcher;
-    static private SubscriptionMonitor sSubscriptionMonitor;
     static private TelephonyNetworkFactory[] sTelephonyNetworkFactories;
-    static private ImsResolver sImsResolver;
     static private NotificationChannelController sNotificationChannelController;
     static private CellularNetworkValidator sCellularNetworkValidator;
 
     static private final HashMap<String, LocalLog>sLocalLogs = new HashMap<String, LocalLog>();
+    private static MetricsCollector sMetricsCollector;
 
     //***** Class Methods
 
@@ -115,6 +117,9 @@
                 // create the telephony device controller.
                 TelephonyDevController.create();
 
+                TelephonyMetrics metrics = TelephonyMetrics.getInstance();
+                metrics.setContext(context);
+
                 int retryCount = 0;
                 for(;;) {
                     boolean hasException = false;
@@ -140,7 +145,10 @@
                     }
                 }
 
-                sPhoneNotifier = new DefaultPhoneNotifier();
+                // register statsd pullers.
+                sMetricsCollector = new MetricsCollector(context);
+
+                sPhoneNotifier = new DefaultPhoneNotifier(context);
 
                 int cdmaSubscription = CdmaSubscriptionSourceManager.getDefault(context);
                 Rlog.i(LOG_TAG, "Cdma Subscription set to " + cdmaSubscription);
@@ -148,9 +156,7 @@
                 /* In case of multi SIM mode two instances of Phone, RIL are created,
                    where as in single SIM mode only instance. isMultiSimEnabled() function checks
                    whether it is single SIM or multi SIM mode */
-                TelephonyManager tm = (TelephonyManager) context.getSystemService(
-                        Context.TELEPHONY_SERVICE);
-                int numPhones = tm.getPhoneCount();
+                int numPhones = TelephonyManager.getDefault().getActiveModemCount();
 
                 int[] networkModes = new int[numPhones];
                 sPhones = new Phone[numPhones];
@@ -169,11 +175,14 @@
 
                 // Instantiate UiccController so that all other classes can just
                 // call getInstance()
-                sUiccController = UiccController.make(context, sCommandsInterfaces);
+                sUiccController = UiccController.make(context);
 
                 Rlog.i(LOG_TAG, "Creating SubscriptionController");
-                SubscriptionController.init(context, sCommandsInterfaces);
-                MultiSimSettingController.init(context, SubscriptionController.getInstance());
+                TelephonyComponentFactory.getInstance().inject(SubscriptionController.class.
+                        getName()).initSubscriptionController(context);
+                TelephonyComponentFactory.getInstance().inject(MultiSimSettingController.class.
+                        getName()).initMultiSimSettingController(context,
+                        SubscriptionController.getInstance());
 
                 if (context.getPackageManager().hasSystemFeature(
                         PackageManager.FEATURE_TELEPHONY_EUICC)) {
@@ -182,31 +191,13 @@
                 }
 
                 for (int i = 0; i < numPhones; i++) {
-                    Phone phone = null;
-                    int phoneType = TelephonyManager.getPhoneType(networkModes[i]);
-                    if (phoneType == PhoneConstants.PHONE_TYPE_GSM) {
-                        phone = new GsmCdmaPhone(context,
-                                sCommandsInterfaces[i], sPhoneNotifier, i,
-                                PhoneConstants.PHONE_TYPE_GSM,
-                                TelephonyComponentFactory.getInstance());
-                    } else if (phoneType == PhoneConstants.PHONE_TYPE_CDMA) {
-                        phone = new GsmCdmaPhone(context,
-                                sCommandsInterfaces[i], sPhoneNotifier, i,
-                                PhoneConstants.PHONE_TYPE_CDMA_LTE,
-                                TelephonyComponentFactory.getInstance());
-                    }
-                    Rlog.i(LOG_TAG, "Creating Phone with type = " + phoneType + " sub = " + i);
-
-                    sPhones[i] = phone;
+                    sPhones[i] = createPhone(context, i);
                 }
 
                 // Set the default phone in base class.
                 // FIXME: This is a first best guess at what the defaults will be. It
                 // FIXME: needs to be done in a more controlled manner in the future.
-                if (numPhones > 0) {
-                    sPhone = sPhones[0];
-                    sCommandsInterface = sCommandsInterfaces[0];
-                }
+                if (numPhones > 0) sPhone = sPhones[0];
 
                 // Ensure that we have a default SMS app. Requesting the app with
                 // updateIfNeeded set to true is enough to configure a default SMS app.
@@ -224,43 +215,26 @@
                 sMadeDefaults = true;
 
                 Rlog.i(LOG_TAG, "Creating SubInfoRecordUpdater ");
-                sSubInfoRecordUpdater = new SubscriptionInfoUpdater(
-                        BackgroundThread.get().getLooper(), context, sPhones, sCommandsInterfaces);
-                SubscriptionController.getInstance().updatePhonesAvailability(sPhones);
-
+                HandlerThread pfhandlerThread = new HandlerThread("PhoneFactoryHandlerThread");
+                pfhandlerThread.start();
+                sSubInfoRecordUpdater = TelephonyComponentFactory.getInstance().inject(
+                        SubscriptionInfoUpdater.class.getName()).
+                        makeSubscriptionInfoUpdater(pfhandlerThread.
+                        getLooper(), context, sCommandsInterfaces);
 
                 // Only bring up IMS if the device supports having an IMS stack.
                 if (context.getPackageManager().hasSystemFeature(
                         PackageManager.FEATURE_TELEPHONY_IMS)) {
-                    // Return whether or not the device should use dynamic binding or the static
-                    // implementation (deprecated)
-                    boolean isDynamicBinding = sContext.getResources().getBoolean(
-                            com.android.internal.R.bool.config_dynamic_bind_ims);
-                    // Get the package name of the default IMS implementation.
-                    String defaultImsPackage = sContext.getResources().getString(
-                            com.android.internal.R.string.config_ims_package);
-                    // Start ImsResolver and bind to ImsServices.
-                    Rlog.i(LOG_TAG, "ImsResolver: defaultImsPackage: " + defaultImsPackage);
-                    sImsResolver = new ImsResolver(sContext, defaultImsPackage, numPhones,
-                            isDynamicBinding);
-                    sImsResolver.initPopulateCacheAndStartBind();
                     // Start monitoring after defaults have been made.
                     // Default phone must be ready before ImsPhone is created because ImsService
-                    // might need it when it is being opened. This should initialize multiple
-                    // ImsPhones for ImsResolver implementations of ImsService.
+                    // might need it when it is being opened.
                     for (int i = 0; i < numPhones; i++) {
-                        sPhones[i].startMonitoringImsService();
+                        sPhones[i].createImsPhone();
                     }
                 } else {
                     Rlog.i(LOG_TAG, "IMS is not supported on this device, skipping ImsResolver.");
                 }
 
-                ITelephonyRegistry tr = ITelephonyRegistry.Stub.asInterface(
-                        ServiceManager.getService("telephony.registry"));
-                SubscriptionController sc = SubscriptionController.getInstance();
-
-                sSubscriptionMonitor = new SubscriptionMonitor(tr, sContext, sc, numPhones);
-
                 sPhoneConfigurationManager = PhoneConfigurationManager.init(sContext);
 
                 sCellularNetworkValidator = CellularNetworkValidator.make(sContext);
@@ -268,26 +242,73 @@
                 int maxActivePhones = sPhoneConfigurationManager
                         .getNumberOfModemsWithSimultaneousDataConnections();
 
-                sPhoneSwitcher = PhoneSwitcher.make(maxActivePhones, numPhones,
-                        sContext, sc, Looper.myLooper(), tr, sCommandsInterfaces,
-                        sPhones);
+                sPhoneSwitcher = TelephonyComponentFactory.getInstance().inject(
+                        PhoneSwitcher.class.getName()).
+                        makePhoneSwitcher(maxActivePhones, sContext, Looper.myLooper());
 
-                sProxyController = ProxyController.getInstance(context, sPhones,
-                        sUiccController, sCommandsInterfaces, sPhoneSwitcher);
+                sProxyController = ProxyController.getInstance(context);
 
                 sIntentBroadcaster = IntentBroadcaster.getInstance(context);
 
                 sNotificationChannelController = new NotificationChannelController(context);
 
-                sTelephonyNetworkFactories = new TelephonyNetworkFactory[numPhones];
                 for (int i = 0; i < numPhones; i++) {
                     sTelephonyNetworkFactories[i] = new TelephonyNetworkFactory(
-                            sSubscriptionMonitor, Looper.myLooper(), sPhones[i]);
+                            Looper.myLooper(), sPhones[i]);
                 }
             }
         }
     }
 
+    /**
+     * Upon single SIM to dual SIM switch or vice versa, we dynamically allocate or de-allocate
+     * Phone and CommandInterface objects.
+     * @param context
+     * @param activeModemCount
+     */
+    public static void onMultiSimConfigChanged(Context context, int activeModemCount) {
+        synchronized (sLockProxyPhones) {
+            int prevActiveModemCount = sPhones.length;
+            if (prevActiveModemCount == activeModemCount) return;
+
+            // TODO: clean up sPhones, sCommandsInterfaces and sTelephonyNetworkFactories objects.
+            // Currently we will not clean up the 2nd Phone object, so that it can be re-used if
+            // user switches back.
+            if (prevActiveModemCount > activeModemCount) return;
+
+            sPhones = copyOf(sPhones, activeModemCount);
+            sCommandsInterfaces = copyOf(sCommandsInterfaces, activeModemCount);
+            sTelephonyNetworkFactories = copyOf(sTelephonyNetworkFactories, activeModemCount);
+
+            int cdmaSubscription = CdmaSubscriptionSourceManager.getDefault(context);
+            for (int i = prevActiveModemCount; i < activeModemCount; i++) {
+                sCommandsInterfaces[i] = new RIL(context, RILConstants.PREFERRED_NETWORK_MODE,
+                        cdmaSubscription, i);
+                sPhones[i] = createPhone(context, i);
+                if (context.getPackageManager().hasSystemFeature(
+                        PackageManager.FEATURE_TELEPHONY_IMS)) {
+                    sPhones[i].createImsPhone();
+                }
+                sTelephonyNetworkFactories[i] = new TelephonyNetworkFactory(
+                        Looper.myLooper(), sPhones[i]);
+            }
+        }
+    }
+
+    private static Phone createPhone(Context context, int phoneId) {
+        int phoneType = TelephonyManager.getPhoneType(RILConstants.PREFERRED_NETWORK_MODE);
+        Rlog.i(LOG_TAG, "Creating Phone with type = " + phoneType + " phoneId = " + phoneId);
+
+        // We always use PHONE_TYPE_CDMA_LTE now.
+        if (phoneType == PHONE_TYPE_CDMA) phoneType = PHONE_TYPE_CDMA_LTE;
+        TelephonyComponentFactory injectedComponentFactory =
+                TelephonyComponentFactory.getInstance().inject(GsmCdmaPhone.class.getName());
+
+        return injectedComponentFactory.makePhone(context,
+                sCommandsInterfaces[phoneId], sPhoneNotifier, phoneId, phoneType,
+                TelephonyComponentFactory.getInstance());
+    }
+
     @UnsupportedAppUsage
     public static Phone getDefaultPhone() {
         synchronized (sLockProxyPhones) {
@@ -342,14 +363,6 @@
     }
 
     /**
-     * @return The ImsResolver instance or null if IMS is not supported
-     * (FEATURE_TELEPHONY_IMS is not defined).
-     */
-    public static @Nullable ImsResolver getImsResolver() {
-        return sImsResolver;
-    }
-
-    /**
      * Get the network factory associated with a given phone ID.
      * @param phoneId the phone id
      * @return a factory for this phone ID, or null if none.
@@ -473,6 +486,15 @@
     }
 
     /**
+     * Get Command Interfaces.
+     */
+    public static CommandsInterface[] getCommandsInterfaces() {
+        synchronized (sLockProxyPhones) {
+            return sCommandsInterfaces;
+        }
+    }
+
+    /**
      * Adds a local log category.
      *
      * Only used within the telephony process.  Use localLog to add log entries.
@@ -509,6 +531,11 @@
         }
     }
 
+    /** Returns the MetricsCollector instance. */
+    public static MetricsCollector getMetricsCollector() {
+        return sMetricsCollector;
+    }
+
     public static void dump(FileDescriptor fd, PrintWriter printwriter, String[] args) {
         IndentingPrintWriter pw = new IndentingPrintWriter(printwriter, "  ");
         pw.println("PhoneFactory:");
@@ -539,16 +566,6 @@
             pw.println("++++++++++++++++++++++++++++++++");
         }
 
-        pw.println("SubscriptionMonitor:");
-        pw.increaseIndent();
-        try {
-            sSubscriptionMonitor.dump(fd, pw, args);
-        } catch (Exception e) {
-            e.printStackTrace();
-        }
-        pw.decreaseIndent();
-        pw.println("++++++++++++++++++++++++++++++++");
-
         pw.println("UiccController:");
         pw.increaseIndent();
         try {
@@ -560,20 +577,6 @@
         pw.decreaseIndent();
         pw.println("++++++++++++++++++++++++++++++++");
 
-        if (sEuiccController != null) {
-            pw.println("EuiccController:");
-            pw.increaseIndent();
-            try {
-                sEuiccController.dump(fd, pw, args);
-                sEuiccCardController.dump(fd, pw, args);
-            } catch (Exception e) {
-                e.printStackTrace();
-            }
-            pw.flush();
-            pw.decreaseIndent();
-            pw.println("++++++++++++++++++++++++++++++++");
-        }
-
         pw.println("SubscriptionController:");
         pw.increaseIndent();
         try {
diff --git a/src/java/com/android/internal/telephony/PhoneInternalInterface.java b/src/java/com/android/internal/telephony/PhoneInternalInterface.java
index b090d08..168506c 100644
--- a/src/java/com/android/internal/telephony/PhoneInternalInterface.java
+++ b/src/java/com/android/internal/telephony/PhoneInternalInterface.java
@@ -17,7 +17,7 @@
 package com.android.internal.telephony;
 
 import android.annotation.NonNull;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.Message;
@@ -25,6 +25,7 @@
 import android.telecom.VideoProfile;
 import android.telephony.ImsiEncryptionInfo;
 import android.telephony.NetworkScanRequest;
+import android.telephony.PreciseDataConnectionState;
 import android.telephony.ServiceState;
 import android.telephony.TelephonyManager;
 
@@ -192,9 +193,12 @@
     int CDMA_RM_ANY         = TelephonyManager.CDMA_ROAMING_MODE_ANY;
 
     // Used for CDMA subscription mode
-    static final int CDMA_SUBSCRIPTION_UNKNOWN  =-1; // Unknown
-    static final int CDMA_SUBSCRIPTION_RUIM_SIM = 0; // RUIM/SIM (default)
-    static final int CDMA_SUBSCRIPTION_NV       = 1; // NV -> non-volatile memory
+    // Unknown
+    static final int CDMA_SUBSCRIPTION_UNKNOWN  = TelephonyManager.CDMA_SUBSCRIPTION_UNKNOWN;
+    // RUIM/SIM (default)
+    static final int CDMA_SUBSCRIPTION_RUIM_SIM = TelephonyManager.CDMA_SUBSCRIPTION_RUIM_SIM;
+    // NV -> non-volatile memory
+    static final int CDMA_SUBSCRIPTION_NV       = TelephonyManager.CDMA_SUBSCRIPTION_NV;
 
     static final int PREFERRED_CDMA_SUBSCRIPTION = CDMA_SUBSCRIPTION_RUIM_SIM;
 
@@ -237,6 +241,16 @@
     DataState getDataConnectionState(String apnType);
 
     /**
+     * Get the current Precise DataState. No change notification exists at this
+     * interface -- use
+     * {@link android.telephony.PhoneStateListener} instead.
+     *
+     * @param apnType specify for which apn to get connection state info.
+     * @return the PreciseDataConnectionState for the data connection supporting apnType
+     */
+    PreciseDataConnectionState getPreciseDataConnectionState(String apnType);
+
+    /**
      * Get the current DataActivityState. No change notification exists at this
      * interface -- use
      * {@link android.telephony.TelephonyManager} instead.
@@ -424,6 +438,21 @@
     Connection dial(String dialString, @NonNull DialArgs dialArgs) throws CallStateException;
 
     /**
+     * Initiate a new conference connection. This happens asynchronously, so you
+     * cannot assume the audio path is connected (or a call index has been
+     * assigned) until PhoneStateChanged notification has occurred.
+     *
+     * @param participantsToDial The participants to dial.
+     * @param dialArgs Parameters to perform the start conference with.
+     * @exception CallStateException if a new outgoing call is not currently
+     *                possible because no more call slots exist or a call exists
+     *                that is dialing, alerting, ringing, or waiting. Other
+     *                errors are handled asynchronously.
+     */
+    Connection startConference(String[] participantsToDial, @NonNull DialArgs dialArgs)
+            throws CallStateException;
+
+    /**
      * Handles PIN MMI commands (PIN/PIN2/PUK/PUK2), which are initiated
      * without SEND (so <code>dial</code> is not appropriate).
      *
@@ -485,7 +514,33 @@
      *
      * @param power true means "on", false means "off".
      */
-    void setRadioPower(boolean power);
+    default void setRadioPower(boolean power) {
+        setRadioPower(power, false, false, false);
+    }
+
+    /**
+     * Sets the radio power on/off state with option to specify whether it's for emergency call
+     * (off is sometimes called "airplane mode"). Current state can be gotten via
+     * {@link #getServiceState()}.{@link
+     * android.telephony.ServiceState#getState() getState()}.
+     * <strong>Note: </strong>This request is asynchronous.
+     * getServiceState().getState() will not change immediately after this call.
+     * registerForServiceStateChanged() to find out when the
+     * request is complete.
+     *
+     * @param power true means "on", false means "off".
+     * @param forEmergencyCall true means the purpose of turning radio power on is for emergency
+     *                         call. No effect if power is set false.
+     * @param isSelectedPhoneForEmergencyCall true means this phone / modem is selected to place
+     *                                  emergency call after turning power on. No effect if power
+     *                                  or forEmergency is set false.
+     * @param forceApply true means always call setRadioPower HAL API without checking against
+     *                   current radio power state. It's needed when: radio was powered on into
+     *                   emergency call mode, to exit from that mode, we set radio
+     *                   power on again with forEmergencyCall being false.
+     */
+    default void setRadioPower(boolean power, boolean forEmergencyCall,
+            boolean isSelectedPhoneForEmergencyCall, boolean forceApply) {}
 
     /**
      * Get the line 1 phone number (MSISDN). For CDMA phones, the MDN is returned
@@ -551,7 +606,7 @@
 
     /**
      * getCallForwardingOptions
-     * gets a call forwarding option. The return value of
+     * gets a call forwarding option for SERVICE_CLASS_VOICE. The return value of
      * ((AsyncResult)onComplete.obj) is an array of CallForwardInfo.
      *
      * @param commandInterfaceCFReason is one of the valid call forwarding
@@ -564,27 +619,67 @@
                                   Message onComplete);
 
     /**
-     * setCallForwardingOptions
-     * sets a call forwarding option.
+     * getCallForwardingOptions
+     * gets a call forwarding option. The return value of
+     * ((AsyncResult)onComplete.obj) is an array of CallForwardInfo.
      *
      * @param commandInterfaceCFReason is one of the valid call forwarding
      *        CF_REASONS, as defined in
      *        <code>com.android.internal.telephony.CommandsInterface.</code>
+     * @param serviceClass is a sum of SERVICE_CLASS_* as defined in
+     *        <code>com.android.internal.telephony.CommandsInterface.</code>
+     * @param onComplete a callback message when the action is completed.
+     *        @see com.android.internal.telephony.CallForwardInfo for details.
+     */
+    void getCallForwardingOption(int commandInterfaceCFReason, int serviceClass,
+                                  Message onComplete);
+
+    /**
+     * setCallForwardingOptions
+     * sets a call forwarding option for SERVICE_CLASS_VOICE.
+     *
      * @param commandInterfaceCFAction is one of the valid call forwarding
      *        CF_ACTIONS, as defined in
      *        <code>com.android.internal.telephony.CommandsInterface.</code>
+     * @param commandInterfaceCFReason is one of the valid call forwarding
+     *        CF_REASONS, as defined in
+     *        <code>com.android.internal.telephony.CommandsInterface.</code>
      * @param dialingNumber is the target phone number to forward calls to
      * @param timerSeconds is used by CFNRy to indicate the timeout before
      *        forwarding is attempted.
      * @param onComplete a callback message when the action is completed.
      */
-    void setCallForwardingOption(int commandInterfaceCFReason,
-                                 int commandInterfaceCFAction,
+    void setCallForwardingOption(int commandInterfaceCFAction,
+                                 int commandInterfaceCFReason,
                                  String dialingNumber,
                                  int timerSeconds,
                                  Message onComplete);
 
     /**
+     * setCallForwardingOptions
+     * sets a call forwarding option.
+     *
+     * @param commandInterfaceCFAction is one of the valid call forwarding
+     *        CF_ACTIONS, as defined in
+     *        <code>com.android.internal.telephony.CommandsInterface.</code>
+     * @param commandInterfaceCFReason is one of the valid call forwarding
+     *        CF_REASONS, as defined in
+     *        <code>com.android.internal.telephony.CommandsInterface.</code>
+     * @param dialingNumber is the target phone number to forward calls to
+     * @param serviceClass is a sum of SERVICE_CLASS_* as defined in
+     *        <code>com.android.internal.telephony.CommandsInterface.</code>
+     * @param timerSeconds is used by CFNRy to indicate the timeout before
+     *        forwarding is attempted.
+     * @param onComplete a callback message when the action is completed.
+     */
+    void setCallForwardingOption(int commandInterfaceCFAction,
+                                 int commandInterfaceCFReason,
+                                 String dialingNumber,
+                                 int serviceClass,
+                                 int timerSeconds,
+                                 Message onComplete);
+
+    /**
      * Gets a call barring option. The return value of ((AsyncResult) onComplete.obj) will be an
      * Integer representing the sum of enabled serivice classes (sum of SERVICE_CLASS_*)
      *
diff --git a/src/java/com/android/internal/telephony/PhoneNotifier.java b/src/java/com/android/internal/telephony/PhoneNotifier.java
index 7840379..ea6fb1f 100644
--- a/src/java/com/android/internal/telephony/PhoneNotifier.java
+++ b/src/java/com/android/internal/telephony/PhoneNotifier.java
@@ -16,14 +16,19 @@
 
 package com.android.internal.telephony;
 
-import android.annotation.UnsupportedAppUsage;
+import android.annotation.NonNull;
+import android.compat.annotation.UnsupportedAppUsage;
+import android.telephony.Annotation.DataFailureCause;
+import android.telephony.Annotation.RadioPowerState;
+import android.telephony.Annotation.SrvccState;
+import android.telephony.BarringInfo;
 import android.telephony.CallQuality;
+import android.telephony.CellIdentity;
 import android.telephony.CellInfo;
-import android.telephony.CellLocation;
-import android.telephony.DataFailCause;
 import android.telephony.PhoneCapability;
-import android.telephony.PhysicalChannelConfig;
-import android.telephony.TelephonyManager;
+import android.telephony.PreciseDataConnectionState;
+import android.telephony.TelephonyDisplayInfo;
+import android.telephony.emergency.EmergencyNumber;
 import android.telephony.ims.ImsReasonInfo;
 
 import java.util.List;
@@ -37,8 +42,12 @@
 
     void notifyServiceState(Phone sender);
 
-    /** Notify registrants of the current CellLocation */
-    void notifyCellLocation(Phone sender, CellLocation cl);
+    /**
+     * Notify registrants of the current CellLocation.
+     *
+     * <p>Use CellIdentity that is Parcellable to pass AIDL; convert to CellLocation in client code.
+     */
+    void notifyCellLocation(Phone sender, CellIdentity cellIdentity);
 
     @UnsupportedAppUsage
     void notifySignalStrength(Phone sender);
@@ -48,44 +57,60 @@
 
     void notifyCallForwardingChanged(Phone sender);
 
-    void notifyDataConnection(Phone sender, String apnType, PhoneConstants.DataState state);
-
-    void notifyDataConnectionFailed(Phone sender, String apnType);
+    /** Send a notification that the Data Connection for a particular apnType has changed */
+    void notifyDataConnection(
+            Phone sender, String apnType, PreciseDataConnectionState preciseState);
 
     void notifyDataActivity(Phone sender);
 
     void notifyCellInfo(Phone sender, List<CellInfo> cellInfo);
 
-    /** Notify of change to PhysicalChannelConfiguration. */
-    void notifyPhysicalChannelConfiguration(Phone sender, List<PhysicalChannelConfig> configs);
-
     void notifyPreciseCallState(Phone sender);
 
     void notifyDisconnectCause(Phone sender, int cause, int preciseCause);
 
     void notifyImsDisconnectCause(Phone sender, ImsReasonInfo imsReasonInfo);
 
-    public void notifyPreciseDataConnectionFailed(Phone sender, String apnType, String apn,
-                                                  @DataFailCause.FailCause int failCause);
+    /** Send a notification that a particular data connection has failed with specified cause. */
+    void notifyDataConnectionFailed(Phone sender, String apnType, String apn,
+                                                  @DataFailureCause int failCause);
 
-    /** send a notification that the SRVCC state has changed.*/
-    void notifySrvccStateChanged(Phone sender, @TelephonyManager.SrvccState int state);
+    /** Send a notification that the SRVCC state has changed.*/
+    void notifySrvccStateChanged(Phone sender, @SrvccState int state);
 
-    public void notifyVoiceActivationStateChanged(Phone sender, int activationState);
+    /** Send a notification that the voice activation state has changed */
+    void notifyVoiceActivationStateChanged(Phone sender, int activationState);
 
-    public void notifyDataActivationStateChanged(Phone sender, int activationState);
+    /** Send a notification that the data activation state has changed */
+    void notifyDataActivationStateChanged(Phone sender, int activationState);
 
-    public void notifyUserMobileDataStateChanged(Phone sender, boolean state);
+    /** Send a notification that the users mobile data setting has changed */
+    void notifyUserMobileDataStateChanged(Phone sender, boolean state);
 
-    public void notifyOemHookRawEventForSubscriber(Phone sender, byte[] rawData);
+    /** Send a notification that the display info has changed */
+    void notifyDisplayInfoChanged(Phone sender, TelephonyDisplayInfo telephonyDisplayInfo);
 
-    public void notifyPhoneCapabilityChanged(PhoneCapability capability);
+    /** Send a notification that the phone capability has changed */
+    void notifyPhoneCapabilityChanged(PhoneCapability capability);
 
-    void notifyRadioPowerStateChanged(Phone sender, @TelephonyManager.RadioPowerState int state);
+    void notifyRadioPowerStateChanged(Phone sender, @RadioPowerState int state);
 
     /** Notify of change to EmergencyNumberList. */
     void notifyEmergencyNumberList(Phone sender);
 
+    /** Notify of a change for Outgoing Emergency Call. */
+    void notifyOutgoingEmergencyCall(Phone sender, EmergencyNumber emergencyNumber);
+
+    /** Notify of a change for Outgoing Emergency Sms. */
+    void notifyOutgoingEmergencySms(Phone sender, EmergencyNumber emergencyNumber);
+
     /** Notify of a change to the call quality of an active foreground call. */
     void notifyCallQualityChanged(Phone sender, CallQuality callQuality, int callNetworkType);
+
+    /** Notify registration failed */
+    void notifyRegistrationFailed(Phone sender, @NonNull CellIdentity cellIdentity,
+            @NonNull String chosenPlmn, int domain, int causeCode, int additionalCauseCode);
+
+    /** Notify barring info has changed */
+    void notifyBarringInfoChanged(Phone sender, @NonNull BarringInfo barringInfo);
 }
diff --git a/src/java/com/android/internal/telephony/PhoneStateIntentReceiver.java b/src/java/com/android/internal/telephony/PhoneStateIntentReceiver.java
deleted file mode 100644
index 1d9f4f0..0000000
--- a/src/java/com/android/internal/telephony/PhoneStateIntentReceiver.java
+++ /dev/null
@@ -1,209 +0,0 @@
-/*
- * Copyright (C) 2006 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;
-
-import android.annotation.UnsupportedAppUsage;
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.os.Handler;
-import android.os.Message;
-import android.telephony.ServiceState;
-import android.telephony.SignalStrength;
-import android.telephony.TelephonyManager;
-import android.telephony.Rlog;
-
-/**
- *
- *                            DO NOT USE THIS CLASS:
- *
- *      Use android.telephony.TelephonyManager and PhoneStateListener instead.
- *
- *
- */
-@Deprecated
-public final class PhoneStateIntentReceiver extends BroadcastReceiver {
-    private static final String LOG_TAG = "PhoneStatIntentReceiver";
-    private static final boolean DBG = false;
-
-    private static final int NOTIF_PHONE    = 1 << 0;
-    private static final int NOTIF_SERVICE  = 1 << 1;
-    private static final int NOTIF_SIGNAL   = 1 << 2;
-
-    PhoneConstants.State mPhoneState = PhoneConstants.State.IDLE;
-    ServiceState mServiceState = new ServiceState();
-    @UnsupportedAppUsage
-    SignalStrength mSignalStrength = new SignalStrength();
-
-    private Context mContext;
-    private Handler mTarget;
-    private IntentFilter mFilter;
-    @UnsupportedAppUsage
-    private int mWants;
-    private int mPhoneStateEventWhat;
-    private int mServiceStateEventWhat;
-    private int mAsuEventWhat;
-
-    public PhoneStateIntentReceiver() {
-        super();
-        mFilter = new IntentFilter();
-    }
-
-    @UnsupportedAppUsage
-    public PhoneStateIntentReceiver(Context context, Handler target) {
-        this();
-        setContext(context);
-        setTarget(target);
-    }
-
-    public void setContext(Context c) {
-        mContext = c;
-    }
-
-    public void setTarget(Handler h) {
-        mTarget = h;
-    }
-
-    public PhoneConstants.State getPhoneState() {
-        if ((mWants & NOTIF_PHONE) == 0) {
-            throw new RuntimeException
-                ("client must call notifyPhoneCallState(int)");
-        }
-        return mPhoneState;
-    }
-
-    public ServiceState getServiceState() {
-        if ((mWants & NOTIF_SERVICE) == 0) {
-            throw new RuntimeException
-                ("client must call notifyServiceState(int)");
-        }
-        return mServiceState;
-    }
-
-    /**
-     * Returns current signal strength in as an asu 0..31
-     *
-     * Throws RuntimeException if client has not called notifySignalStrength()
-     */
-    public int getSignalStrengthLevelAsu() {
-        // TODO: use new SignalStrength instead of asu
-        if ((mWants & NOTIF_SIGNAL) == 0) {
-            throw new RuntimeException
-                ("client must call notifySignalStrength(int)");
-        }
-        return mSignalStrength.getAsuLevel();
-    }
-
-    /**
-     * Return current signal strength in "dBm", ranging from -113 - -51dBm
-     * or -1 if unknown
-     *
-     * @return signal strength in dBm, -1 if not yet updated
-     * Throws RuntimeException if client has not called notifySignalStrength()
-     */
-    @UnsupportedAppUsage
-    public int getSignalStrengthDbm() {
-        if ((mWants & NOTIF_SIGNAL) == 0) {
-            throw new RuntimeException
-                ("client must call notifySignalStrength(int)");
-        }
-        return mSignalStrength.getDbm();
-    }
-
-    public void notifyPhoneCallState(int eventWhat) {
-        mWants |= NOTIF_PHONE;
-        mPhoneStateEventWhat = eventWhat;
-        mFilter.addAction(TelephonyManager.ACTION_PHONE_STATE_CHANGED);
-    }
-
-    public boolean getNotifyPhoneCallState() {
-        return ((mWants & NOTIF_PHONE) != 0);
-    }
-
-    @UnsupportedAppUsage
-    public void notifyServiceState(int eventWhat) {
-        mWants |= NOTIF_SERVICE;
-        mServiceStateEventWhat = eventWhat;
-        mFilter.addAction(TelephonyIntents.ACTION_SERVICE_STATE_CHANGED);
-    }
-
-    public boolean getNotifyServiceState() {
-        return ((mWants & NOTIF_SERVICE) != 0);
-    }
-
-    @UnsupportedAppUsage
-    public void notifySignalStrength (int eventWhat) {
-        mWants |= NOTIF_SIGNAL;
-        mAsuEventWhat = eventWhat;
-        mFilter.addAction(TelephonyIntents.ACTION_SIGNAL_STRENGTH_CHANGED);
-    }
-
-    public boolean getNotifySignalStrength() {
-        return ((mWants & NOTIF_SIGNAL) != 0);
-    }
-
-    @UnsupportedAppUsage
-    public void registerIntent() {
-        mContext.registerReceiver(this, mFilter);
-    }
-
-    @UnsupportedAppUsage
-    public void unregisterIntent() {
-        mContext.unregisterReceiver(this);
-    }
-
-    @Override
-    public void onReceive(Context context, Intent intent) {
-        String action = intent.getAction();
-
-        try {
-            if (TelephonyIntents.ACTION_SIGNAL_STRENGTH_CHANGED.equals(action)) {
-                mSignalStrength = SignalStrength.newFromBundle(intent.getExtras());
-
-                if (mTarget != null && getNotifySignalStrength()) {
-                    Message message = Message.obtain(mTarget, mAsuEventWhat);
-                    mTarget.sendMessage(message);
-                }
-            } else if (TelephonyManager.ACTION_PHONE_STATE_CHANGED.equals(action)) {
-                if (DBG) Rlog.d(LOG_TAG, "onReceiveIntent: ACTION_PHONE_STATE_CHANGED, state="
-                               + intent.getStringExtra(PhoneConstants.STATE_KEY));
-                String phoneState = intent.getStringExtra(PhoneConstants.STATE_KEY);
-                mPhoneState = Enum.valueOf(
-                        PhoneConstants.State.class, phoneState);
-
-                if (mTarget != null && getNotifyPhoneCallState()) {
-                    Message message = Message.obtain(mTarget,
-                            mPhoneStateEventWhat);
-                    mTarget.sendMessage(message);
-                }
-            } else if (TelephonyIntents.ACTION_SERVICE_STATE_CHANGED.equals(action)) {
-                mServiceState = ServiceState.newFromBundle(intent.getExtras());
-
-                if (mTarget != null && getNotifyServiceState()) {
-                    Message message = Message.obtain(mTarget,
-                            mServiceStateEventWhat);
-                    mTarget.sendMessage(message);
-                }
-            }
-        } catch (Exception ex) {
-            Rlog.e(LOG_TAG, "[PhoneStateIntentRecv] caught " + ex);
-            ex.printStackTrace();
-        }
-    }
-
-}
diff --git a/src/java/com/android/internal/telephony/PhoneSubInfoController.java b/src/java/com/android/internal/telephony/PhoneSubInfoController.java
index c5b7eda..1bf46b5 100644
--- a/src/java/com/android/internal/telephony/PhoneSubInfoController.java
+++ b/src/java/com/android/internal/telephony/PhoneSubInfoController.java
@@ -21,21 +21,24 @@
 import static android.Manifest.permission.MODIFY_PHONE_STATE;
 import static android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE;
 
-import android.annotation.UnsupportedAppUsage;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.app.AppOpsManager;
+import android.compat.annotation.UnsupportedAppUsage;
 import android.content.Context;
 import android.content.pm.PackageManager;
 import android.os.Binder;
 import android.os.RemoteException;
-import android.os.ServiceManager;
+import android.os.TelephonyServiceManager.ServiceRegisterer;
 import android.telephony.ImsiEncryptionInfo;
 import android.telephony.PhoneNumberUtils;
-import android.telephony.Rlog;
 import android.telephony.SubscriptionManager;
+import android.telephony.TelephonyFrameworkInitializer;
 
 import com.android.internal.telephony.uicc.IsimRecords;
 import com.android.internal.telephony.uicc.UiccCard;
 import com.android.internal.telephony.uicc.UiccCardApplication;
+import com.android.telephony.Rlog;
 
 public class PhoneSubInfoController extends IPhoneSubInfo.Stub {
     private static final String TAG = "PhoneSubInfoController";
@@ -43,43 +46,50 @@
     private static final boolean VDBG = false; // STOPSHIP if true
 
     @UnsupportedAppUsage
-    private final Phone[] mPhone;
-    @UnsupportedAppUsage
     private final Context mContext;
     private final AppOpsManager mAppOps;
 
-    public PhoneSubInfoController(Context context, Phone[] phone) {
-        mPhone = phone;
-        if (ServiceManager.getService("iphonesubinfo") == null) {
-            ServiceManager.addService("iphonesubinfo", this);
+    public PhoneSubInfoController(Context context) {
+        ServiceRegisterer phoneSubServiceRegisterer = TelephonyFrameworkInitializer
+                .getTelephonyServiceManager()
+                .getPhoneSubServiceRegisterer();
+        if (phoneSubServiceRegisterer.get() == null) {
+            phoneSubServiceRegisterer.register(this);
         }
         mContext = context;
         mAppOps = (AppOpsManager) mContext.getSystemService(Context.APP_OPS_SERVICE);
     }
 
+    @Deprecated
     public String getDeviceId(String callingPackage) {
+        return getDeviceIdWithFeature(callingPackage, null);
+    }
+
+    public String getDeviceIdWithFeature(String callingPackage, String callingFeatureId) {
         return getDeviceIdForPhone(SubscriptionManager.getPhoneId(getDefaultSubscription()),
-                callingPackage);
+                callingPackage, callingFeatureId);
     }
 
-    public String getDeviceIdForPhone(int phoneId, String callingPackage) {
+    public String getDeviceIdForPhone(int phoneId, String callingPackage,
+            String callingFeatureId) {
         return callPhoneMethodForPhoneIdWithReadDeviceIdentifiersCheck(phoneId, callingPackage,
-                "getDeviceId", (phone)-> phone.getDeviceId());
+                callingFeatureId, "getDeviceId", (phone) -> phone.getDeviceId());
     }
 
-    public String getNaiForSubscriber(int subId, String callingPackage) {
-        return callPhoneMethodForSubIdWithReadCheck(subId, callingPackage, "getNai",
-                (phone)-> phone.getNai());
+    public String getNaiForSubscriber(int subId, String callingPackage, String callingFeatureId) {
+        return callPhoneMethodForSubIdWithReadSubscriberIdentifiersCheck(subId, callingPackage,
+                callingFeatureId, "getNai", (phone)-> phone.getNai());
     }
 
-    public String getImeiForSubscriber(int subId, String callingPackage) {
+    public String getImeiForSubscriber(int subId, String callingPackage,
+            String callingFeatureId) {
         return callPhoneMethodForSubIdWithReadDeviceIdentifiersCheck(subId, callingPackage,
-                "getImei", (phone)-> phone.getImei());
+                callingFeatureId, "getImei", (phone) -> phone.getImei());
     }
 
     public ImsiEncryptionInfo getCarrierInfoForImsiEncryption(int subId, int keyType,
                                                               String callingPackage) {
-        return callPhoneMethodForSubIdWithReadCheck(subId, callingPackage,
+        return callPhoneMethodForSubIdWithPrivilegedCheck(subId,
                 "getCarrierInfoForImsiEncryption",
                 (phone)-> phone.getCarrierInfoForImsiEncryption(keyType));
     }
@@ -103,42 +113,50 @@
      */
     public void resetCarrierKeysForImsiEncryption(int subId, String callingPackage) {
         callPhoneMethodForSubIdWithModifyCheck(subId, callingPackage,
-                "setCarrierInfoForImsiEncryption",
+                "resetCarrierKeysForImsiEncryption",
                 (phone)-> {
                     phone.resetCarrierKeysForImsiEncryption();
                     return null;
                 });
     }
 
-
-    public String getDeviceSvn(String callingPackage) {
-        return getDeviceSvnUsingSubId(getDefaultSubscription(), callingPackage);
+    public String getDeviceSvn(String callingPackage, String callingFeatureId) {
+        return getDeviceSvnUsingSubId(getDefaultSubscription(), callingPackage, callingFeatureId);
     }
 
-    public String getDeviceSvnUsingSubId(int subId, String callingPackage) {
-        return callPhoneMethodForSubIdWithReadCheck(subId, callingPackage, "getDeviceSvn",
-                (phone)-> phone.getDeviceSvn());
+    public String getDeviceSvnUsingSubId(int subId, String callingPackage,
+            String callingFeatureId) {
+        return callPhoneMethodForSubIdWithReadCheck(subId, callingPackage, callingFeatureId,
+                "getDeviceSvn", (phone)-> phone.getDeviceSvn());
     }
 
+    @Deprecated
     public String getSubscriberId(String callingPackage) {
-        return getSubscriberIdForSubscriber(getDefaultSubscription(), callingPackage);
+        return getSubscriberIdWithFeature(callingPackage, null);
     }
 
-    public String getSubscriberIdForSubscriber(int subId, String callingPackage) {
+    public String getSubscriberIdWithFeature(String callingPackage, String callingFeatureId) {
+        return getSubscriberIdForSubscriber(getDefaultSubscription(), callingPackage,
+                callingFeatureId);
+    }
+
+    public String getSubscriberIdForSubscriber(int subId, String callingPackage,
+            String callingFeatureId) {
         String message = "getSubscriberId";
         long identity = Binder.clearCallingIdentity();
         boolean isActive;
         try {
-            isActive = SubscriptionController.getInstance().isActiveSubId(subId, callingPackage);
+            isActive = SubscriptionController.getInstance().isActiveSubId(subId, callingPackage,
+                    callingFeatureId);
         } finally {
             Binder.restoreCallingIdentity(identity);
         }
         if (isActive) {
             return callPhoneMethodForSubIdWithReadSubscriberIdentifiersCheck(subId, callingPackage,
-                    message, (phone) -> phone.getSubscriberId());
+                    callingFeatureId, message, (phone) -> phone.getSubscriberId());
         } else {
             if (!TelephonyPermissions.checkCallingOrSelfReadSubscriberIdentifiers(
-                    mContext, subId, callingPackage, message)) {
+                    mContext, subId, callingPackage, callingFeatureId, message)) {
                 return null;
             }
             identity = Binder.clearCallingIdentity();
@@ -150,52 +168,70 @@
         }
     }
 
+    @Deprecated
+    public String getIccSerialNumber(String callingPackage) {
+        return getIccSerialNumberWithFeature(callingPackage, null);
+    }
+
     /**
      * Retrieves the serial number of the ICC, if applicable.
      */
-    public String getIccSerialNumber(String callingPackage) {
-        return getIccSerialNumberForSubscriber(getDefaultSubscription(), callingPackage);
+    public String getIccSerialNumberWithFeature(String callingPackage, String callingFeatureId) {
+        return getIccSerialNumberForSubscriber(getDefaultSubscription(), callingPackage,
+                callingFeatureId);
     }
 
-    public String getIccSerialNumberForSubscriber(int subId, String callingPackage) {
+    public String getIccSerialNumberForSubscriber(int subId, String callingPackage,
+            String callingFeatureId) {
         return callPhoneMethodForSubIdWithReadSubscriberIdentifiersCheck(subId, callingPackage,
-                "getIccSerialNumber", (phone) -> phone.getIccSerialNumber());
+                callingFeatureId, "getIccSerialNumber", (phone) -> phone.getIccSerialNumber());
     }
 
-    public String getLine1Number(String callingPackage) {
-        return getLine1NumberForSubscriber(getDefaultSubscription(), callingPackage);
+    public String getLine1Number(String callingPackage, String callingFeatureId) {
+        return getLine1NumberForSubscriber(getDefaultSubscription(), callingPackage,
+                callingFeatureId);
     }
 
-    public String getLine1NumberForSubscriber(int subId, String callingPackage) {
+    // In R and beyond, READ_PHONE_NUMBERS includes READ_PHONE_NUMBERS and READ_SMS only.
+    // Prior to R, it also included READ_PHONE_STATE.  Maintain that for compatibility.
+    public String getLine1NumberForSubscriber(int subId, String callingPackage,
+            String callingFeatureId) {
         return callPhoneMethodForSubIdWithReadPhoneNumberCheck(
-                subId, callingPackage, "getLine1Number",
+                subId, callingPackage, callingFeatureId, "getLine1Number",
                 (phone)-> phone.getLine1Number());
     }
 
-    public String getLine1AlphaTag(String callingPackage) {
-        return getLine1AlphaTagForSubscriber(getDefaultSubscription(), callingPackage);
+    public String getLine1AlphaTag(String callingPackage, String callingFeatureId) {
+        return getLine1AlphaTagForSubscriber(getDefaultSubscription(), callingPackage,
+                callingFeatureId);
     }
 
-    public String getLine1AlphaTagForSubscriber(int subId, String callingPackage) {
-        return callPhoneMethodForSubIdWithReadCheck(subId, callingPackage, "getLine1AlphaTag",
-                (phone)-> phone.getLine1AlphaTag());
+    public String getLine1AlphaTagForSubscriber(int subId, String callingPackage,
+            String callingFeatureId) {
+        return callPhoneMethodForSubIdWithReadCheck(subId, callingPackage, callingFeatureId,
+                "getLine1AlphaTag", (phone)-> phone.getLine1AlphaTag());
     }
 
-    public String getMsisdn(String callingPackage) {
-        return getMsisdnForSubscriber(getDefaultSubscription(), callingPackage);
+    public String getMsisdn(String callingPackage, String callingFeatureId) {
+        return getMsisdnForSubscriber(getDefaultSubscription(), callingPackage, callingFeatureId);
     }
 
-    public String getMsisdnForSubscriber(int subId, String callingPackage) {
-        return callPhoneMethodForSubIdWithReadCheck(subId, callingPackage, "getMsisdn",
-                (phone)-> phone.getMsisdn());
+    // In R and beyond this will require READ_PHONE_NUMBERS.
+    // Prior to R it needed READ_PHONE_STATE.  Maintain that for compatibility.
+    public String getMsisdnForSubscriber(int subId, String callingPackage,
+            String callingFeatureId) {
+        return callPhoneMethodForSubIdWithReadPhoneNumberCheck(
+                subId, callingPackage, callingFeatureId, "getMsisdn", (phone)-> phone.getMsisdn());
     }
 
-    public String getVoiceMailNumber(String callingPackage) {
-        return getVoiceMailNumberForSubscriber(getDefaultSubscription(), callingPackage);
+    public String getVoiceMailNumber(String callingPackage, String callingFeatureId) {
+        return getVoiceMailNumberForSubscriber(getDefaultSubscription(), callingPackage,
+                callingFeatureId);
     }
 
-    public String getVoiceMailNumberForSubscriber(int subId, String callingPackage) {
-        return callPhoneMethodForSubIdWithReadCheck(subId, callingPackage,
+    public String getVoiceMailNumberForSubscriber(int subId, String callingPackage,
+            String callingFeatureId) {
+        return callPhoneMethodForSubIdWithReadCheck(subId, callingPackage, callingFeatureId,
                 "getVoiceMailNumber", (phone)-> {
                     String number = PhoneNumberUtils.extractNetworkPortion(
                             phone.getVoiceMailNumber());
@@ -204,12 +240,14 @@
                 });
     }
 
-    public String getVoiceMailAlphaTag(String callingPackage) {
-        return getVoiceMailAlphaTagForSubscriber(getDefaultSubscription(), callingPackage);
+    public String getVoiceMailAlphaTag(String callingPackage, String callingFeatureId) {
+        return getVoiceMailAlphaTagForSubscriber(getDefaultSubscription(), callingPackage,
+                callingFeatureId);
     }
 
-    public String getVoiceMailAlphaTagForSubscriber(int subId, String callingPackage) {
-        return callPhoneMethodForSubIdWithReadCheck(subId, callingPackage,
+    public String getVoiceMailAlphaTagForSubscriber(int subId, String callingPackage,
+            String callingFeatureId) {
+        return callPhoneMethodForSubIdWithReadCheck(subId, callingPackage, callingFeatureId,
                 "getVoiceMailAlphaTag", (phone)-> phone.getVoiceMailAlphaTag());
     }
 
@@ -222,7 +260,7 @@
         if (!SubscriptionManager.isValidPhoneId(phoneId)) {
             phoneId = 0;
         }
-        return mPhone[phoneId];
+        return PhoneFactory.getPhone(phoneId);
     }
 
     /**
@@ -240,7 +278,7 @@
             return;
         }
         if (VDBG) log("No read privileged phone permission, check carrier privilege next.");
-        TelephonyPermissions.enforceCallingOrSelfCarrierPrivilege(subId, message);
+        TelephonyPermissions.enforceCallingOrSelfCarrierPrivilege(mContext, subId, message);
     }
 
     /**
@@ -357,16 +395,17 @@
             return uiccApp.getIccRecords().getIccSimChallengeResponse(authType, data);
         };
 
-        return callPhoneMethodWithPermissionCheck(
-                subId, null, "getIccSimChallengeResponse", toExecute,
-                (aContext, aSubId, aCallingPackage, aMessage)-> {
+        return callPhoneMethodWithPermissionCheck(subId, null, null, "getIccSimChallengeResponse",
+                toExecute,
+                (aContext, aSubId, aCallingPackage, aCallingFeatureId, aMessage) -> {
                     enforcePrivilegedPermissionOrCarrierPrivilege(aSubId, aMessage);
                     return true;
                 });
     }
 
-    public String getGroupIdLevel1ForSubscriber(int subId, String callingPackage) {
-        return callPhoneMethodForSubIdWithReadCheck(subId, callingPackage,
+    public String getGroupIdLevel1ForSubscriber(int subId, String callingPackage,
+            String callingFeatureId) {
+        return callPhoneMethodForSubIdWithReadCheck(subId, callingPackage, callingFeatureId,
                 "getGroupIdLevel1", (phone)-> phone.getGroupIdLevel1());
     }
 
@@ -385,14 +424,17 @@
         // If passes, it should return true.
         // If permission is not granted, throws SecurityException.
         // If permission is revoked by AppOps, return false.
-        boolean checkPermission(Context context, int subId, String callingPackage, String message);
+        boolean checkPermission(Context context, int subId, String callingPackage,
+                @Nullable String callingFeatureId, String message);
     }
 
     // Base utility method that others use.
     private <T> T callPhoneMethodWithPermissionCheck(int subId, String callingPackage,
-            String message, CallPhoneMethodHelper<T> callMethodHelper,
+            @Nullable String callingFeatureId, String message,
+            CallPhoneMethodHelper<T> callMethodHelper,
             PermissionCheckHelper permissionCheckHelper) {
-        if (!permissionCheckHelper.checkPermission(mContext, subId, callingPackage, message)) {
+        if (!permissionCheckHelper.checkPermission(mContext, subId, callingPackage,
+                callingFeatureId, message)) {
             return null;
         }
 
@@ -411,34 +453,39 @@
     }
 
     private <T> T callPhoneMethodForSubIdWithReadCheck(int subId, String callingPackage,
-            String message, CallPhoneMethodHelper<T> callMethodHelper) {
-        return callPhoneMethodWithPermissionCheck(subId, callingPackage, message, callMethodHelper,
-                (aContext, aSubId, aCallingPackage, aMessage)->
+            @Nullable String callingFeatureId, String message,
+            CallPhoneMethodHelper<T> callMethodHelper) {
+        return callPhoneMethodWithPermissionCheck(subId, callingPackage, callingFeatureId,
+                message, callMethodHelper,
+                (aContext, aSubId, aCallingPackage, aCallingFeatureId, aMessage)->
                         TelephonyPermissions.checkCallingOrSelfReadPhoneState(
-                                aContext, aSubId, aCallingPackage, aMessage));
+                                aContext, aSubId, aCallingPackage, aCallingFeatureId, aMessage));
     }
 
     private <T> T callPhoneMethodForSubIdWithReadDeviceIdentifiersCheck(int subId,
-            String callingPackage, String message, CallPhoneMethodHelper<T> callMethodHelper) {
-        return callPhoneMethodWithPermissionCheck(subId, callingPackage, message, callMethodHelper,
-                (aContext, aSubId, aCallingPackage, aMessage)->
+            String callingPackage, @Nullable String callingFeatureId, String message,
+            CallPhoneMethodHelper<T> callMethodHelper) {
+        return callPhoneMethodWithPermissionCheck(subId, callingPackage, callingFeatureId,
+                message, callMethodHelper,
+                (aContext, aSubId, aCallingPackage, aCallingFeatureId, aMessage)->
                         TelephonyPermissions.checkCallingOrSelfReadDeviceIdentifiers(
-                                aContext, aSubId, aCallingPackage, aMessage));
+                                aContext, aSubId, aCallingPackage, aCallingFeatureId, aMessage));
     }
 
     private <T> T callPhoneMethodForSubIdWithReadSubscriberIdentifiersCheck(int subId,
-            String callingPackage, String message, CallPhoneMethodHelper<T> callMethodHelper) {
-        return callPhoneMethodWithPermissionCheck(subId, callingPackage, message, callMethodHelper,
-                (aContext, aSubId, aCallingPackage, aMessage)->
+            String callingPackage, @Nullable String callingFeatureId, String message,
+            CallPhoneMethodHelper<T> callMethodHelper) {
+        return callPhoneMethodWithPermissionCheck(subId, callingPackage, callingFeatureId,
+                message, callMethodHelper,
+                (aContext, aSubId, aCallingPackage, aCallingFeatureId, aMessage)->
                         TelephonyPermissions.checkCallingOrSelfReadSubscriberIdentifiers(
-                                aContext, aSubId, aCallingPackage, aMessage));
+                                aContext, aSubId, aCallingPackage, aCallingFeatureId, aMessage));
     }
 
-
     private <T> T callPhoneMethodForSubIdWithPrivilegedCheck(
             int subId, String message, CallPhoneMethodHelper<T> callMethodHelper) {
-        return callPhoneMethodWithPermissionCheck(subId, null, message, callMethodHelper,
-                (aContext, aSubId, aCallingPackage, aMessage)-> {
+        return callPhoneMethodWithPermissionCheck(subId, null, null, message, callMethodHelper,
+                (aContext, aSubId, aCallingPackage, aCallingFeatureId, aMessage) -> {
                     mContext.enforceCallingOrSelfPermission(READ_PRIVILEGED_PHONE_STATE, message);
                     return true;
                 });
@@ -446,33 +493,36 @@
 
     private <T> T callPhoneMethodForSubIdWithModifyCheck(int subId, String callingPackage,
             String message, CallPhoneMethodHelper<T> callMethodHelper) {
-        return callPhoneMethodWithPermissionCheck(subId, null, message, callMethodHelper,
-                (aContext, aSubId, aCallingPackage, aMessage)-> {
+        return callPhoneMethodWithPermissionCheck(subId, null, null, message, callMethodHelper,
+                (aContext, aSubId, aCallingPackage, aCallingFeatureId, aMessage)-> {
                     enforceModifyPermission();
                     return true;
                 });
     }
 
     private <T> T callPhoneMethodForSubIdWithReadPhoneNumberCheck(int subId, String callingPackage,
-            String message, CallPhoneMethodHelper<T> callMethodHelper) {
-        return callPhoneMethodWithPermissionCheck(subId, callingPackage, message, callMethodHelper,
-                (aContext, aSubId, aCallingPackage, aMessage)->
+            @NonNull String callingFeatureId, String message,
+            CallPhoneMethodHelper<T> callMethodHelper) {
+        return callPhoneMethodWithPermissionCheck(subId, callingPackage, callingFeatureId,
+                message, callMethodHelper,
+                (aContext, aSubId, aCallingPackage, aCallingFeatureId, aMessage) ->
                         TelephonyPermissions.checkCallingOrSelfReadPhoneNumber(
-                                aContext, aSubId, aCallingPackage, aMessage));
+                                aContext, aSubId, aCallingPackage, aCallingFeatureId, aMessage));
     }
 
     private <T> T callPhoneMethodForPhoneIdWithReadDeviceIdentifiersCheck(int phoneId,
-            String callingPackage, String message, CallPhoneMethodHelper<T> callMethodHelper) {
+            String callingPackage, @Nullable String callingFeatureId, String message,
+            CallPhoneMethodHelper<T> callMethodHelper) {
         // Getting subId before doing permission check.
         if (!SubscriptionManager.isValidPhoneId(phoneId)) {
             phoneId = 0;
         }
-        final Phone phone = mPhone[phoneId];
+        final Phone phone = PhoneFactory.getPhone(phoneId);
         if (phone == null) {
             return null;
         }
         if (!TelephonyPermissions.checkCallingOrSelfReadDeviceIdentifiers(mContext,
-                phone.getSubId(), callingPackage, message)) {
+                phone.getSubId(), callingPackage, callingFeatureId, message)) {
             return null;
         }
 
diff --git a/src/java/com/android/internal/telephony/PhoneSwitcher.java b/src/java/com/android/internal/telephony/PhoneSwitcher.java
index c3eea5f..3e15666 100644
--- a/src/java/com/android/internal/telephony/PhoneSwitcher.java
+++ b/src/java/com/android/internal/telephony/PhoneSwitcher.java
@@ -25,7 +25,9 @@
 import static android.telephony.TelephonyManager.SET_OPPORTUNISTIC_SUB_SUCCESS;
 import static android.telephony.TelephonyManager.SET_OPPORTUNISTIC_SUB_VALIDATION_FAILED;
 
-import android.annotation.UnsupportedAppUsage;
+import static java.util.Arrays.copyOf;
+
+import android.compat.annotation.UnsupportedAppUsage;
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
@@ -37,7 +39,7 @@
 import android.net.NetworkFactory;
 import android.net.NetworkRequest;
 import android.net.NetworkSpecifier;
-import android.net.StringNetworkSpecifier;
+import android.net.TelephonyNetworkSpecifier;
 import android.os.AsyncResult;
 import android.os.Handler;
 import android.os.Looper;
@@ -46,23 +48,25 @@
 import android.os.Registrant;
 import android.os.RegistrantList;
 import android.os.RemoteException;
-import android.os.ServiceManager;
 import android.telephony.CarrierConfigManager;
 import android.telephony.PhoneCapability;
 import android.telephony.PhoneStateListener;
-import android.telephony.Rlog;
 import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyManager;
+import android.telephony.TelephonyRegistryManager;
 import android.telephony.data.ApnSetting;
 import android.util.LocalLog;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.telephony.SubscriptionController.WatchedInt;
+import com.android.internal.telephony.dataconnection.ApnConfigTypeRepository;
 import com.android.internal.telephony.dataconnection.DcRequest;
 import com.android.internal.telephony.metrics.TelephonyMetrics;
 import com.android.internal.telephony.nano.TelephonyProto.TelephonyEvent;
 import com.android.internal.telephony.nano.TelephonyProto.TelephonyEvent.DataSwitch;
 import com.android.internal.telephony.nano.TelephonyProto.TelephonyEvent.OnDemandDataSwitch;
 import com.android.internal.util.IndentingPrintWriter;
+import com.android.telephony.Rlog;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
@@ -81,7 +85,7 @@
  */
 public class PhoneSwitcher extends Handler {
     private static final String LOG_TAG = "PhoneSwitcher";
-    private static final boolean VDBG = false;
+    protected static final boolean VDBG = false;
 
     private static final int DEFAULT_NETWORK_CHANGE_TIMEOUT_MS = 5000;
     private static final int MODEM_COMMAND_RETRY_PERIOD_MS     = 5000;
@@ -151,33 +155,47 @@
         }
     }
 
-    private final List<DcRequest> mPrioritizedDcRequests = new ArrayList<DcRequest>();
-    private final RegistrantList mActivePhoneRegistrants;
-    private final SubscriptionController mSubscriptionController;
-    private final int[] mPhoneSubscriptions;
-    private final CommandsInterface[] mCommandsInterfaces;
-    private final Context mContext;
-    private final PhoneState[] mPhoneStates;
-    @UnsupportedAppUsage
-    private final int mNumPhones;
-    @UnsupportedAppUsage
-    private final Phone[] mPhones;
+    protected final List<DcRequest> mPrioritizedDcRequests = new ArrayList<>();
+    protected final RegistrantList mActivePhoneRegistrants;
+    protected final SubscriptionController mSubscriptionController;
+    protected final Context mContext;
     private final LocalLog mLocalLog;
+    protected PhoneState[] mPhoneStates;
+    protected int[] mPhoneSubscriptions;
     @VisibleForTesting
-    public final PhoneStateListener mPhoneStateListener;
-    private final CellularNetworkValidator mValidator;
+    protected final CellularNetworkValidator mValidator;
+    private int mPendingSwitchSubId = INVALID_SUBSCRIPTION_ID;
+    private boolean mPendingSwitchNeedValidation;
     @VisibleForTesting
     public final CellularNetworkValidator.ValidationCallback mValidationCallback =
-            (validated, subId) -> Message.obtain(PhoneSwitcher.this,
-                    EVENT_NETWORK_VALIDATION_DONE, subId, validated ? 1 : 0).sendToTarget();
+            new CellularNetworkValidator.ValidationCallback() {
+                @Override
+                public void onValidationDone(boolean validated, int subId) {
+                    Message.obtain(PhoneSwitcher.this,
+                            EVENT_NETWORK_VALIDATION_DONE, subId, validated ? 1 : 0).sendToTarget();
+                }
+
+                @Override
+                public void onNetworkAvailable(Network network, int subId) {
+                    Message.obtain(PhoneSwitcher.this,
+                            EVENT_NETWORK_AVAILABLE, subId, 0, network).sendToTarget();
+
+                }
+            };
+
     @UnsupportedAppUsage
-    private int mMaxActivePhones;
-    private static PhoneSwitcher sPhoneSwitcher = null;
+    // How many phones (correspondingly logical modems) are allowed for PS attach. This is used
+    // when we specifically use setDataAllowed to initiate on-demand PS(data) attach for each phone.
+    protected int mMaxDataAttachModemCount;
+    // Local cache of TelephonyManager#getActiveModemCount(). 1 if in single SIM mode, 2 if in dual
+    // SIM mode.
+    protected int mActiveModemCount;
+    protected static PhoneSwitcher sPhoneSwitcher = null;
 
     // Which primary (non-opportunistic) subscription is set as data subscription among all primary
     // subscriptions. This value usually comes from user setting, and it's the subscription used for
     // Internet data if mOpptDataSubId is not set.
-    private int mPrimaryDataSubId = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
+    protected int mPrimaryDataSubId = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
 
     // mOpptDataSubId must be an active subscription. If it's set, it overrides mPrimaryDataSubId
     // to be used for Internet data.
@@ -185,7 +203,7 @@
 
     // The phone ID that has an active voice call. If set, and its mobile data setting is on,
     // it will become the mPreferredDataPhoneId.
-    private int mPhoneIdInVoiceCall = SubscriptionManager.INVALID_PHONE_INDEX;
+    protected int mPhoneIdInVoiceCall = SubscriptionManager.INVALID_PHONE_INDEX;
 
     @VisibleForTesting
     // It decides:
@@ -197,7 +215,14 @@
     protected int mPreferredDataPhoneId = SubscriptionManager.INVALID_PHONE_INDEX;
 
     // Subscription ID corresponds to mPreferredDataPhoneId.
-    private int mPreferredDataSubId = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
+    protected WatchedInt mPreferredDataSubId =
+            new WatchedInt(SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
+        @Override
+        public void set(int newValue) {
+            super.set(newValue);
+            SubscriptionController.invalidateActiveDataSubIdCaches();
+        }
+    };
 
     // If non-null, An emergency call is about to be started, is ongoing, or has just ended and we
     // are overriding the DDS.
@@ -207,7 +232,7 @@
     private ISetOpportunisticDataCallback mSetOpptSubCallback;
 
     private static final int EVENT_PRIMARY_DATA_SUB_CHANGED       = 101;
-    private static final int EVENT_SUBSCRIPTION_CHANGED           = 102;
+    protected static final int EVENT_SUBSCRIPTION_CHANGED           = 102;
     private static final int EVENT_REQUEST_NETWORK                = 103;
     private static final int EVENT_RELEASE_NETWORK                = 104;
     // ECBM has started/ended. If we just ended an emergency call and mEmergencyOverride is not
@@ -237,16 +262,20 @@
     private static final int EVENT_OVERRIDE_DDS_FOR_EMERGENCY     = 115;
     // If it exists, remove the current mEmergencyOverride DDS override.
     private static final int EVENT_REMOVE_DDS_EMERGENCY_OVERRIDE  = 116;
+    // If it exists, remove the current mEmergencyOverride DDS override.
+    @VisibleForTesting
+    public static final int EVENT_MULTI_SIM_CONFIG_CHANGED        = 117;
+    private static final int EVENT_NETWORK_AVAILABLE              = 118;
 
     // Depending on version of IRadioConfig, we need to send either RIL_REQUEST_ALLOW_DATA if it's
     // 1.0, or RIL_REQUEST_SET_PREFERRED_DATA if it's 1.1 or later. So internally mHalCommandToUse
     // will be either HAL_COMMAND_ALLOW_DATA or HAL_COMMAND_ALLOW_DATA or HAL_COMMAND_UNKNOWN.
-    private static final int HAL_COMMAND_UNKNOWN        = 0;
-    private static final int HAL_COMMAND_ALLOW_DATA     = 1;
-    private static final int HAL_COMMAND_PREFERRED_DATA = 2;
-    private int mHalCommandToUse = HAL_COMMAND_UNKNOWN;
+    protected static final int HAL_COMMAND_UNKNOWN        = 0;
+    protected static final int HAL_COMMAND_ALLOW_DATA     = 1;
+    protected static final int HAL_COMMAND_PREFERRED_DATA = 2;
+    protected int mHalCommandToUse = HAL_COMMAND_UNKNOWN;
 
-    private RadioConfig mRadioConfig;
+    protected RadioConfig mRadioConfig;
 
     private final static int MAX_LOCAL_LOG_LINES = 30;
 
@@ -288,47 +317,22 @@
     /**
      * Method to create singleton instance.
      */
-    public static PhoneSwitcher make(int maxActivePhones, int numPhones, Context context,
-            SubscriptionController subscriptionController, Looper looper, ITelephonyRegistry tr,
-            CommandsInterface[] cis, Phone[] phones) {
+    public static PhoneSwitcher make(int maxDataAttachModemCount, Context context, Looper looper) {
         if (sPhoneSwitcher == null) {
-            sPhoneSwitcher = new PhoneSwitcher(maxActivePhones, numPhones, context,
-                    subscriptionController, looper, tr, cis, phones);
+            sPhoneSwitcher = new PhoneSwitcher(maxDataAttachModemCount, context, looper);
+            SubscriptionController.invalidateActiveDataSubIdCaches();
         }
 
         return sPhoneSwitcher;
     }
 
-    /** This constructor is only used for testing purpose. */
-    @VisibleForTesting
-    public PhoneSwitcher(int numPhones, Looper looper) {
-        super(looper);
-        mMaxActivePhones = 0;
-        mSubscriptionController = null;
-        mCommandsInterfaces = null;
-        mContext = null;
-        mPhoneStates = null;
-        mPhones = null;
-        mLocalLog = null;
-        mActivePhoneRegistrants = null;
-        mNumPhones = numPhones;
-        mPhoneSubscriptions = new int[numPhones];
-        mRadioConfig = RadioConfig.getInstance(mContext);
-        mPhoneStateListener = new PhoneStateListener(looper) {
-            public void onPhoneCapabilityChanged(PhoneCapability capability) {
-                onPhoneCapabilityChangedInternal(capability);
-            }
-        };
-        mValidator = CellularNetworkValidator.getInstance();
-    }
-
     private boolean isPhoneInVoiceCallChanged() {
         int oldPhoneIdInVoiceCall = mPhoneIdInVoiceCall;
         // If there's no active call, the value will become INVALID_PHONE_INDEX
         // and internet data will be switched back to system selected or user selected
         // subscription.
         mPhoneIdInVoiceCall = SubscriptionManager.INVALID_PHONE_INDEX;
-        for (Phone phone : mPhones) {
+        for (Phone phone : PhoneFactory.getPhones()) {
             if (isPhoneInVoiceCall(phone) || isPhoneInVoiceCall(phone.getImsPhone())) {
                 mPhoneIdInVoiceCall = phone.getPhoneId();
                 break;
@@ -345,58 +349,45 @@
     }
 
     @VisibleForTesting
-    public PhoneSwitcher(int maxActivePhones, int numPhones, Context context,
-            SubscriptionController subscriptionController, Looper looper, ITelephonyRegistry tr,
-            CommandsInterface[] cis, Phone[] phones) {
+    public PhoneSwitcher(int maxActivePhones, Context context, Looper looper) {
         super(looper);
         mContext = context;
-        mNumPhones = numPhones;
-        mPhones = phones;
-        mPhoneSubscriptions = new int[numPhones];
-        mMaxActivePhones = maxActivePhones;
+        mActiveModemCount = getTm().getActiveModemCount();
+        mPhoneSubscriptions = new int[mActiveModemCount];
+        mPhoneStates = new PhoneState[mActiveModemCount];
+        mMaxDataAttachModemCount = maxActivePhones;
         mLocalLog = new LocalLog(MAX_LOCAL_LOG_LINES);
 
-        mSubscriptionController = subscriptionController;
+        mSubscriptionController = SubscriptionController.getInstance();
         mRadioConfig = RadioConfig.getInstance(mContext);
-
-        mPhoneStateListener = new PhoneStateListener(looper) {
-            @Override
-            public void onPhoneCapabilityChanged(PhoneCapability capability) {
-                onPhoneCapabilityChangedInternal(capability);
-            }
-        };
-
         mValidator = CellularNetworkValidator.getInstance();
 
         mActivePhoneRegistrants = new RegistrantList();
-        mPhoneStates = new PhoneState[numPhones];
-        for (int i = 0; i < numPhones; i++) {
+        for (int i = 0; i < mActiveModemCount; i++) {
             mPhoneStates[i] = new PhoneState();
-            if (mPhones[i] != null) {
-                mPhones[i].registerForEmergencyCallToggle(this, EVENT_EMERGENCY_TOGGLE, null);
+            if (PhoneFactory.getPhone(i) != null) {
+                PhoneFactory.getPhone(i).registerForEmergencyCallToggle(
+                        this, EVENT_EMERGENCY_TOGGLE, null);
                 // TODO (b/135566422): combine register for both GsmCdmaPhone and ImsPhone.
-                mPhones[i].registerForPreciseCallStateChanged(
+                PhoneFactory.getPhone(i).registerForPreciseCallStateChanged(
                         this, EVENT_PRECISE_CALL_STATE_CHANGED, null);
-                if (mPhones[i].getImsPhone() != null) {
-                    mPhones[i].getImsPhone().registerForPreciseCallStateChanged(
+                if (PhoneFactory.getPhone(i).getImsPhone() != null) {
+                    PhoneFactory.getPhone(i).getImsPhone().registerForPreciseCallStateChanged(
                             this, EVENT_PRECISE_CALL_STATE_CHANGED, null);
                 }
-                mPhones[i].getDataEnabledSettings().registerForDataEnabledChanged(
+                PhoneFactory.getPhone(i).getDataEnabledSettings().registerForDataEnabledChanged(
                         this, EVENT_DATA_ENABLED_CHANGED, null);
             }
         }
 
-        mCommandsInterfaces = cis;
-
-        if (numPhones > 0) {
-            mCommandsInterfaces[0].registerForAvailable(this, EVENT_RADIO_AVAILABLE, null);
+        if (mActiveModemCount > 0) {
+            PhoneFactory.getPhone(0).mCi.registerForAvailable(this, EVENT_RADIO_AVAILABLE, null);
         }
 
-        try {
-            tr.addOnSubscriptionsChangedListener(context.getOpPackageName(),
-                    mSubscriptionsChangedListener);
-        } catch (RemoteException e) {
-        }
+        TelephonyRegistryManager telephonyRegistryManager = (TelephonyRegistryManager)
+                context.getSystemService(Context.TELEPHONY_REGISTRY_SERVICE);
+        telephonyRegistryManager.addOnSubscriptionsChangedListener(
+                mSubscriptionsChangedListener, mSubscriptionsChangedListener.getHandlerExecutor());
 
         mConnectivityManager =
             (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
@@ -404,6 +395,9 @@
         mContext.registerReceiver(mDefaultDataChangedReceiver,
                 new IntentFilter(TelephonyIntents.ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED));
 
+        PhoneConfigurationManager.registerForMultiSimConfigChange(
+                this, EVENT_MULTI_SIM_CONFIG_CHANGED, null);
+
         NetworkCapabilities netCap = new NetworkCapabilities();
         netCap.addTransportType(TRANSPORT_CELLULAR);
         netCap.addCapability(NetworkCapabilities.NET_CAPABILITY_MMS);
@@ -438,8 +432,8 @@
         }
     };
 
-    private final IOnSubscriptionsChangedListener mSubscriptionsChangedListener =
-            new IOnSubscriptionsChangedListener.Stub() {
+    private final SubscriptionManager.OnSubscriptionsChangedListener mSubscriptionsChangedListener =
+            new SubscriptionManager.OnSubscriptionsChangedListener() {
         @Override
         public void onSubscriptionsChanged() {
             Message msg = PhoneSwitcher.this.obtainMessage(EVENT_SUBSCRIPTION_CHANGED);
@@ -456,10 +450,10 @@
             }
             case EVENT_PRIMARY_DATA_SUB_CHANGED: {
                 if (onEvaluate(REQUESTS_UNCHANGED, "primary data subId changed")) {
-                    logDataSwitchEvent(mPreferredDataSubId,
+                    logDataSwitchEvent(mPreferredDataSubId.get(),
                             TelephonyEvent.EventState.EVENT_STATE_START,
                             DataSwitch.Reason.DATA_SWITCH_REASON_MANUAL);
-                    registerDefaultNetworkChangeCallback(mPreferredDataSubId,
+                    registerDefaultNetworkChangeCallback(mPreferredDataSubId.get(),
                             DataSwitch.Reason.DATA_SWITCH_REASON_MANUAL);
                 }
                 break;
@@ -530,10 +524,10 @@
             // fall through
             case EVENT_DATA_ENABLED_CHANGED:
                 if (onEvaluate(REQUESTS_UNCHANGED, "EVENT_PRECISE_CALL_STATE_CHANGED")) {
-                    logDataSwitchEvent(mPreferredDataSubId,
+                    logDataSwitchEvent(mPreferredDataSubId.get(),
                             TelephonyEvent.EventState.EVENT_STATE_START,
                             DataSwitch.Reason.DATA_SWITCH_REASON_IN_CALL);
-                    registerDefaultNetworkChangeCallback(mPreferredDataSubId,
+                    registerDefaultNetworkChangeCallback(mPreferredDataSubId.get(),
                             DataSwitch.Reason.DATA_SWITCH_REASON_IN_CALL);
                 }
                 break;
@@ -543,6 +537,12 @@
                 onValidationDone(subId, passed);
                 break;
             }
+            case EVENT_NETWORK_AVAILABLE: {
+                int subId = msg.arg1;
+                Network network = (Network) msg.obj;
+                onNetworkAvailable(subId, network);
+                break;
+            }
             case EVENT_REMOVE_DEFAULT_NETWORK_CHANGE_CALLBACK: {
                 removeDefaultNetworkChangeCallback();
                 break;
@@ -619,11 +619,43 @@
                 onEvaluate(REQUESTS_UNCHANGED, "emer_rm_override_dds");
                 break;
             }
+            case EVENT_MULTI_SIM_CONFIG_CHANGED: {
+                int activeModemCount = (int) ((AsyncResult) msg.obj).result;
+                onMultiSimConfigChanged(activeModemCount);
+                break;
+            }
+        }
+    }
+
+    private synchronized void onMultiSimConfigChanged(int activeModemCount) {
+        // No change.
+        if (mActiveModemCount == activeModemCount) return;
+        int oldActiveModemCount = mActiveModemCount;
+        mActiveModemCount = activeModemCount;
+
+        mPhoneSubscriptions = copyOf(mPhoneSubscriptions, mActiveModemCount);
+        mPhoneStates = copyOf(mPhoneStates, mActiveModemCount);
+
+        // Single SIM -> dual SIM switch.
+        for (int phoneId = oldActiveModemCount; phoneId < mActiveModemCount; phoneId++) {
+            mPhoneStates[phoneId] = new PhoneState();
+            Phone phone = PhoneFactory.getPhone(phoneId);
+            if (phone == null) continue;
+
+            phone.registerForEmergencyCallToggle(this, EVENT_EMERGENCY_TOGGLE, null);
+            // TODO (b/135566422): combine register for both GsmCdmaPhone and ImsPhone.
+            phone.registerForPreciseCallStateChanged(this, EVENT_PRECISE_CALL_STATE_CHANGED, null);
+            if (phone.getImsPhone() != null) {
+                phone.getImsPhone().registerForPreciseCallStateChanged(
+                        this, EVENT_PRECISE_CALL_STATE_CHANGED, null);
+            }
+            phone.getDataEnabledSettings().registerForDataEnabledChanged(
+                    this, EVENT_DATA_ENABLED_CHANGED, null);
         }
     }
 
     private boolean isInEmergencyCallbackMode() {
-        for (Phone p : mPhones) {
+        for (Phone p : PhoneFactory.getPhones()) {
             if (p == null) continue;
             if (p.isInEcm()) return true;
             Phone imsPhone = p.getImsPhone();
@@ -660,24 +692,46 @@
     }
 
     private void onRequestNetwork(NetworkRequest networkRequest) {
-        final DcRequest dcRequest = new DcRequest(networkRequest, mContext);
-        if (!mPrioritizedDcRequests.contains(dcRequest)) {
-            collectRequestNetworkMetrics(networkRequest);
-            mPrioritizedDcRequests.add(dcRequest);
-            Collections.sort(mPrioritizedDcRequests);
-            onEvaluate(REQUESTS_CHANGED, "netRequest");
+        final DcRequest dcRequest =
+                DcRequest.create(networkRequest, createApnRepository(networkRequest));
+        if (dcRequest != null) {
+            if (!mPrioritizedDcRequests.contains(dcRequest)) {
+                collectRequestNetworkMetrics(networkRequest);
+                mPrioritizedDcRequests.add(dcRequest);
+                Collections.sort(mPrioritizedDcRequests);
+                onEvaluate(REQUESTS_CHANGED, "netRequest");
+                log("Added DcRequest, size: " + mPrioritizedDcRequests.size());
+            }
         }
     }
 
     private void onReleaseNetwork(NetworkRequest networkRequest) {
-        final DcRequest dcRequest = new DcRequest(networkRequest, mContext);
-
-        if (mPrioritizedDcRequests.remove(dcRequest)) {
-            onEvaluate(REQUESTS_CHANGED, "netReleased");
-            collectReleaseNetworkMetrics(networkRequest);
+        final DcRequest dcRequest =
+                DcRequest.create(networkRequest, createApnRepository(networkRequest));
+        if (dcRequest != null) {
+            if (mPrioritizedDcRequests.remove(dcRequest)) {
+                onEvaluate(REQUESTS_CHANGED, "netReleased");
+                collectReleaseNetworkMetrics(networkRequest);
+                log("Removed DcRequest, size: " + mPrioritizedDcRequests.size());
+            }
         }
     }
 
+    private ApnConfigTypeRepository createApnRepository(NetworkRequest networkRequest) {
+        int phoneIdForRequest = phoneIdForRequest(networkRequest);
+        int subId = mSubscriptionController.getSubIdUsingPhoneId(phoneIdForRequest);
+        CarrierConfigManager configManager = (CarrierConfigManager) mContext
+                .getSystemService(Context.CARRIER_CONFIG_SERVICE);
+
+        PersistableBundle carrierConfig;
+        if (configManager != null) {
+            carrierConfig = configManager.getConfigForSubId(subId);
+        } else {
+            carrierConfig = null;
+        }
+        return new ApnConfigTypeRepository(carrierConfig);
+    }
+
     private void removeDefaultNetworkChangeCallback() {
         removeMessages(EVENT_REMOVE_DEFAULT_NETWORK_CHANGE_CALLBACK);
         mDefaultNetworkCallback.mExpectedSubId = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
@@ -698,7 +752,7 @@
     private void collectRequestNetworkMetrics(NetworkRequest networkRequest) {
         // Request network for MMS will temporary disable the network on default data subscription,
         // this only happen on multi-sim device.
-        if (mNumPhones > 1 && networkRequest.networkCapabilities.hasCapability(
+        if (mActiveModemCount > 1 && networkRequest.hasCapability(
                 NetworkCapabilities.NET_CAPABILITY_MMS)) {
             OnDemandDataSwitch onDemandDataSwitch = new OnDemandDataSwitch();
             onDemandDataSwitch.apn = TelephonyEvent.ApnType.APN_TYPE_MMS;
@@ -710,7 +764,7 @@
     private void collectReleaseNetworkMetrics(NetworkRequest networkRequest) {
         // Release network for MMS will recover the network on default data subscription, this only
         // happen on multi-sim device.
-        if (mNumPhones > 1 && networkRequest.networkCapabilities.hasCapability(
+        if (mActiveModemCount > 1 && networkRequest.hasCapability(
                 NetworkCapabilities.NET_CAPABILITY_MMS)) {
             OnDemandDataSwitch onDemandDataSwitch = new OnDemandDataSwitch();
             onDemandDataSwitch.apn = TelephonyEvent.ApnType.APN_TYPE_MMS;
@@ -719,8 +773,12 @@
         }
     }
 
-    private static final boolean REQUESTS_CHANGED   = true;
-    private static final boolean REQUESTS_UNCHANGED = false;
+    private TelephonyManager getTm() {
+        return (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE);
+    }
+
+    protected static final boolean REQUESTS_CHANGED   = true;
+    protected static final boolean REQUESTS_UNCHANGED = false;
     /**
      * Re-evaluate things. Do nothing if nothing's changed.
      *
@@ -730,7 +788,7 @@
      *
      * @return {@code True} if the default data subscription need to be changed.
      */
-    private boolean onEvaluate(boolean requestsChanged, String reason) {
+    protected boolean onEvaluate(boolean requestsChanged, String reason) {
         StringBuilder sb = new StringBuilder(reason);
 
         // If we use HAL_COMMAND_PREFERRED_DATA,
@@ -748,7 +806,7 @@
         boolean hasAnyActiveSubscription = false;
 
         // Check if phoneId to subId mapping is changed.
-        for (int i = 0; i < mNumPhones; i++) {
+        for (int i = 0; i < mActiveModemCount; i++) {
             int sub = mSubscriptionController.getSubIdUsingPhoneId(i);
 
             if (SubscriptionManager.isValidSubscriptionId(sub)) hasAnyActiveSubscription = true;
@@ -789,7 +847,7 @@
                 // With HAL_COMMAND_PREFERRED_DATA, all phones are assumed to allow PS attach.
                 // So marking all phone as active, and the phone with mPreferredDataPhoneId
                 // will send radioConfig command.
-                for (int phoneId = 0; phoneId < mNumPhones; phoneId++) {
+                for (int phoneId = 0; phoneId < mActiveModemCount; phoneId++) {
                     mPhoneStates[phoneId].active = true;
                 }
                 sendRilCommands(mPreferredDataPhoneId);
@@ -801,9 +859,9 @@
                  * Otherwise, choose to activate phones according to requests. And
                  * if list is not full, add mPreferredDataPhoneId.
                  */
-                if (mMaxActivePhones == mPhones.length) {
-                    for (int i = 0; i < mMaxActivePhones; i++) {
-                        newActivePhones.add(mPhones[i].getPhoneId());
+                if (mMaxDataAttachModemCount == mActiveModemCount) {
+                    for (int i = 0; i < mMaxDataAttachModemCount; i++) {
+                        newActivePhones.add(i);
                     }
                 } else {
                     // First try to activate phone in voice call.
@@ -811,17 +869,17 @@
                         newActivePhones.add(mPhoneIdInVoiceCall);
                     }
 
-                    if (newActivePhones.size() < mMaxActivePhones) {
+                    if (newActivePhones.size() < mMaxDataAttachModemCount) {
                         for (DcRequest dcRequest : mPrioritizedDcRequests) {
                             int phoneIdForRequest = phoneIdForRequest(dcRequest.networkRequest);
                             if (phoneIdForRequest == INVALID_PHONE_INDEX) continue;
                             if (newActivePhones.contains(phoneIdForRequest)) continue;
                             newActivePhones.add(phoneIdForRequest);
-                            if (newActivePhones.size() >= mMaxActivePhones) break;
+                            if (newActivePhones.size() >= mMaxDataAttachModemCount) break;
                         }
                     }
 
-                    if (newActivePhones.size() < mMaxActivePhones
+                    if (newActivePhones.size() < mMaxDataAttachModemCount
                             && newActivePhones.contains(mPreferredDataPhoneId)
                             && SubscriptionManager.isUsableSubIdValue(mPreferredDataPhoneId)) {
                         newActivePhones.add(mPreferredDataPhoneId);
@@ -831,14 +889,14 @@
                 if (VDBG) {
                     log("mPrimaryDataSubId = " + mPrimaryDataSubId);
                     log("mOpptDataSubId = " + mOpptDataSubId);
-                    for (int i = 0; i < mNumPhones; i++) {
+                    for (int i = 0; i < mActiveModemCount; i++) {
                         log(" phone[" + i + "] using sub[" + mPhoneSubscriptions[i] + "]");
                     }
                     log(" newActivePhones:");
                     for (Integer i : newActivePhones) log("  " + i);
                 }
 
-                for (int phoneId = 0; phoneId < mNumPhones; phoneId++) {
+                for (int phoneId = 0; phoneId < mActiveModemCount; phoneId++) {
                     if (!newActivePhones.contains(phoneId)) {
                         deactivate(phoneId);
                     }
@@ -858,18 +916,18 @@
         return diffDetected;
     }
 
-    private static class PhoneState {
+    protected static class PhoneState {
         public volatile boolean active = false;
         public long lastRequested = 0;
     }
 
     @UnsupportedAppUsage
-    private void activate(int phoneId) {
+    protected void activate(int phoneId) {
         switchPhone(phoneId, true);
     }
 
     @UnsupportedAppUsage
-    private void deactivate(int phoneId) {
+    protected void deactivate(int phoneId) {
         switchPhone(phoneId, false);
     }
 
@@ -887,7 +945,7 @@
      * want to resend setDataAllowed or setPreferredDataSubscriptionId
      */
     public void onRadioCapChanged(int phoneId) {
-        validatePhoneId(phoneId);
+        if (!SubscriptionManager.isValidPhoneId(phoneId)) return;
         Message msg = obtainMessage(EVENT_RADIO_CAPABILITY_CHANGED);
         msg.arg1 = phoneId;
         msg.sendToTarget();
@@ -912,7 +970,7 @@
      */
     public void overrideDefaultDataForEmergency(int phoneId, int overrideTimeSec,
             CompletableFuture<Boolean> dataSwitchResult) {
-        validatePhoneId(phoneId);
+        if (!SubscriptionManager.isValidPhoneId(phoneId)) return;
         Message msg = obtainMessage(EVENT_OVERRIDE_DDS_FOR_EMERGENCY);
         EmergencyOverrideRequest request  = new EmergencyOverrideRequest();
         request.mPhoneId = phoneId;
@@ -922,14 +980,14 @@
         msg.sendToTarget();
     }
 
-    private void sendRilCommands(int phoneId) {
-        if (!SubscriptionManager.isValidPhoneId(phoneId) || phoneId >= mNumPhones) return;
+    protected void sendRilCommands(int phoneId) {
+        if (!SubscriptionManager.isValidPhoneId(phoneId)) return;
 
         Message message = Message.obtain(this, EVENT_MODEM_COMMAND_DONE, phoneId);
         if (mHalCommandToUse == HAL_COMMAND_ALLOW_DATA || mHalCommandToUse == HAL_COMMAND_UNKNOWN) {
             // Skip ALLOW_DATA for single SIM device
-            if (mNumPhones > 1) {
-                mCommandsInterfaces[phoneId].setDataAllowed(isPhoneActive(phoneId), message);
+            if (mActiveModemCount > 1) {
+                PhoneFactory.getPhone(phoneId).mCi.setDataAllowed(isPhoneActive(phoneId), message);
             }
         } else if (phoneId == mPreferredDataPhoneId) {
             // Only setPreferredDataModem if the phoneId equals to current mPreferredDataPhoneId.
@@ -938,23 +996,23 @@
     }
 
     private void onPhoneCapabilityChangedInternal(PhoneCapability capability) {
-        int newMaxActivePhones = TelephonyManager.getDefault()
+        int newMaxDataAttachModemCount = TelephonyManager.getDefault()
                 .getNumberOfModemsWithSimultaneousDataConnections();
-        if (mMaxActivePhones != newMaxActivePhones) {
-            mMaxActivePhones = newMaxActivePhones;
-            log("Max active phones changed to " + mMaxActivePhones);
+        if (mMaxDataAttachModemCount != newMaxDataAttachModemCount) {
+            mMaxDataAttachModemCount = newMaxDataAttachModemCount;
+            log("Max active phones changed to " + mMaxDataAttachModemCount);
             onEvaluate(REQUESTS_UNCHANGED, "phoneCfgChanged");
         }
     }
 
     private int phoneIdForRequest(NetworkRequest netRequest) {
-        int subId = getSubIdFromNetworkSpecifier(netRequest.networkCapabilities
-                .getNetworkSpecifier());
+        int subId = getSubIdFromNetworkSpecifier(netRequest.getNetworkSpecifier());
 
         if (subId == DEFAULT_SUBSCRIPTION_ID) return mPreferredDataPhoneId;
         if (subId == INVALID_SUBSCRIPTION_ID) return INVALID_PHONE_INDEX;
 
-        int preferredDataSubId = SubscriptionManager.isValidPhoneId(mPreferredDataPhoneId)
+        int preferredDataSubId = (mPreferredDataPhoneId >= 0
+                && mPreferredDataPhoneId < mActiveModemCount)
                 ? mPhoneSubscriptions[mPreferredDataPhoneId] : INVALID_SUBSCRIPTION_ID;
 
         // Currently we assume multi-SIM devices will only support one Internet PDN connection. So
@@ -972,7 +1030,7 @@
 
         // Try to find matching phone ID. If it doesn't exist, we'll end up returning INVALID.
         int phoneId = INVALID_PHONE_INDEX;
-        for (int i = 0; i < mNumPhones; i++) {
+        for (int i = 0; i < mActiveModemCount; i++) {
             if (mPhoneSubscriptions[i] == subId) {
                 phoneId = i;
                 break;
@@ -981,26 +1039,14 @@
         return phoneId;
     }
 
-    private int getSubIdFromNetworkSpecifier(NetworkSpecifier specifier) {
+    protected int getSubIdFromNetworkSpecifier(NetworkSpecifier specifier) {
         if (specifier == null) {
             return DEFAULT_SUBSCRIPTION_ID;
         }
-
-        int subId;
-
-        if (specifier instanceof StringNetworkSpecifier) {
-            try {
-                subId = Integer.parseInt(((StringNetworkSpecifier) specifier).specifier);
-            } catch (NumberFormatException e) {
-                Rlog.e(LOG_TAG, "NumberFormatException on "
-                        + ((StringNetworkSpecifier) specifier).specifier);
-                return INVALID_SUBSCRIPTION_ID;
-            }
-        } else {
-            return INVALID_SUBSCRIPTION_ID;
+        if (specifier instanceof TelephonyNetworkSpecifier) {
+            return ((TelephonyNetworkSpecifier) specifier).getSubscriptionId();
         }
-
-        return subId;
+        return INVALID_SUBSCRIPTION_ID;
     }
 
     private int getSubIdForDefaultNetworkRequests() {
@@ -1013,7 +1059,7 @@
 
     // This updates mPreferredDataPhoneId which decides which phone should handle default network
     // requests.
-    private void updatePreferredDataPhoneId() {
+    protected void updatePreferredDataPhoneId() {
         Phone voicePhone = findPhoneById(mPhoneIdInVoiceCall);
         if (mEmergencyOverride != null && findPhoneById(mEmergencyOverride.mPhoneId) != null) {
             // Override DDS for emergency even if user data is not enabled, since it is an
@@ -1035,7 +1081,7 @@
             int phoneId = SubscriptionManager.INVALID_PHONE_INDEX;
 
             if (SubscriptionManager.isUsableSubIdValue(subId)) {
-                for (int i = 0; i < mNumPhones; i++) {
+                for (int i = 0; i < mActiveModemCount; i++) {
                     if (mPhoneSubscriptions[i] == subId) {
                         phoneId = i;
                         break;
@@ -1046,30 +1092,37 @@
             mPreferredDataPhoneId = phoneId;
         }
 
-        mPreferredDataSubId = mSubscriptionController.getSubIdUsingPhoneId(mPreferredDataPhoneId);
+        mPreferredDataSubId.set(
+                mSubscriptionController.getSubIdUsingPhoneId(mPreferredDataPhoneId));
     }
 
-    private void transitionToEmergencyPhone() {
+    protected void transitionToEmergencyPhone() {
+        if (mActiveModemCount <= 0) {
+            log("No phones: unable to reset preferred phone for emergency");
+            return;
+        }
+
         if (mPreferredDataPhoneId != DEFAULT_EMERGENCY_PHONE_ID) {
             log("No active subscriptions: resetting preferred phone to 0 for emergency");
             mPreferredDataPhoneId = DEFAULT_EMERGENCY_PHONE_ID;
         }
 
-        if (mPreferredDataSubId != INVALID_SUBSCRIPTION_ID) {
-            mPreferredDataSubId = INVALID_SUBSCRIPTION_ID;
+        if (mPreferredDataSubId.get() != INVALID_SUBSCRIPTION_ID) {
+            mPreferredDataSubId.set(INVALID_SUBSCRIPTION_ID);
             notifyPreferredDataSubIdChanged();
         }
     }
 
     private Phone findPhoneById(final int phoneId) {
-        if (phoneId < 0 || phoneId >= mNumPhones) {
+        if (!SubscriptionManager.isValidPhoneId(phoneId)) {
             return null;
         }
-        return mPhones[phoneId];
+        return PhoneFactory.getPhone(phoneId);
     }
 
-    public boolean shouldApplyNetworkRequest(NetworkRequest networkRequest, int phoneId) {
-        validatePhoneId(phoneId);
+    public synchronized boolean shouldApplyNetworkRequest(
+            NetworkRequest networkRequest, int phoneId) {
+        if (!SubscriptionManager.isValidPhoneId(phoneId)) return false;
 
         // In any case, if phone state is inactive, don't apply the network request.
         if (!isPhoneActive(phoneId) || (
@@ -1089,6 +1142,8 @@
 
     @VisibleForTesting
     protected boolean isPhoneActive(int phoneId) {
+        if (phoneId >= mActiveModemCount)
+            return false;
         return mPhoneStates[phoneId].active;
     }
 
@@ -1106,13 +1161,6 @@
         mActivePhoneRegistrants.remove(h);
     }
 
-    @VisibleForTesting
-    protected void validatePhoneId(int phoneId) {
-        if (phoneId < 0 || phoneId >= mNumPhones) {
-            throw new IllegalArgumentException("Invalid PhoneId");
-        }
-    }
-
     /**
      * Set opportunistic data subscription. It's an indication to switch Internet data to this
      * subscription. It has to be an active subscription, and PhoneSwitcher will try to validate
@@ -1139,10 +1187,13 @@
         // Remove EVENT_NETWORK_VALIDATION_DONE. Don't handle validation result of previously subId
         // if queued.
         removeMessages(EVENT_NETWORK_VALIDATION_DONE);
+        removeMessages(EVENT_NETWORK_AVAILABLE);
 
         int subIdToValidate = (subId == SubscriptionManager.DEFAULT_SUBSCRIPTION_ID)
                 ? mPrimaryDataSubId : subId;
 
+        mPendingSwitchSubId = INVALID_SUBSCRIPTION_ID;
+
         if (mValidator.isValidating()) {
             mValidator.stopValidation();
             sendSetOpptCallbackHelper(mSetOpptSubCallback, SET_OPPORTUNISTIC_SUB_VALIDATION_FAILED);
@@ -1163,22 +1214,37 @@
 
         // If validation feature is not supported, set it directly. Otherwise,
         // start validation on the subscription first.
-        if (mValidator.isValidationFeatureSupported() && needValidation) {
-            mSetOpptSubCallback = callback;
-            long validationTimeout = DEFAULT_VALIDATION_EXPIRATION_TIME;
-            CarrierConfigManager configManager = (CarrierConfigManager)
-                    mContext.getSystemService(Context.CARRIER_CONFIG_SERVICE);
-            if (configManager != null) {
-                PersistableBundle b = configManager.getConfigForSubId(subIdToValidate);
-                if (b != null) {
-                    validationTimeout = b.getLong(KEY_DATA_SWITCH_VALIDATION_TIMEOUT_LONG);
-                }
-            }
-            mValidator.validate(subIdToValidate, validationTimeout, false, mValidationCallback);
-        } else {
+        if (!mValidator.isValidationFeatureSupported()) {
             setOpportunisticSubscriptionInternal(subId);
             sendSetOpptCallbackHelper(callback, SET_OPPORTUNISTIC_SUB_SUCCESS);
+            return;
         }
+
+        // Even if needValidation is false, we still send request to validator. The reason is we
+        // want to delay data switch until network is available on the target sub, to have a
+        // smoothest transition possible.
+        // In this case, even if data connection eventually failed in 2 seconds, we still
+        // confirm the switch, to maximally respect the request.
+        mPendingSwitchSubId = subIdToValidate;
+        mPendingSwitchNeedValidation = needValidation;
+        mSetOpptSubCallback = callback;
+        long validationTimeout = getValidationTimeout(subIdToValidate, needValidation);
+        mValidator.validate(subIdToValidate, validationTimeout, false, mValidationCallback);
+    }
+
+    private long getValidationTimeout(int subId, boolean needValidation) {
+        if (!needValidation) return DEFAULT_VALIDATION_EXPIRATION_TIME;
+
+        long validationTimeout = DEFAULT_VALIDATION_EXPIRATION_TIME;
+        CarrierConfigManager configManager = (CarrierConfigManager)
+                mContext.getSystemService(Context.CARRIER_CONFIG_SERVICE);
+        if (configManager != null) {
+            PersistableBundle b = configManager.getConfigForSubId(subId);
+            if (b != null) {
+                validationTimeout = b.getLong(KEY_DATA_SWITCH_VALIDATION_TIMEOUT_LONG);
+            }
+        }
+        return validationTimeout;
     }
 
     private void sendSetOpptCallbackHelper(ISetOpportunisticDataCallback callback, int result) {
@@ -1200,15 +1266,13 @@
         }
     }
 
-    private void onValidationDone(int subId, boolean passed) {
-        log("onValidationDone: " + (passed ? "passed" : "failed")
-                + " on subId " + subId);
+    private void confirmSwitch(int subId, boolean confirm) {
+        log("confirmSwitch: subId " + subId + (confirm ? " confirmed." : " cancelled."));
         int resultForCallBack;
-
         if (!mSubscriptionController.isActiveSubId(subId)) {
-            log("onValidationDone: subId " + subId + " is no longer active");
+            log("confirmSwitch: subId " + subId + " is no longer active");
             resultForCallBack = SET_OPPORTUNISTIC_SUB_INACTIVE_SUBSCRIPTION;
-        } else if (!passed) {
+        } else if (!confirm) {
             resultForCallBack = SET_OPPORTUNISTIC_SUB_VALIDATION_FAILED;
         } else {
             if (mSubscriptionController.isOpportunistic(subId)) {
@@ -1223,6 +1287,27 @@
         // Trigger callback if needed
         sendSetOpptCallbackHelper(mSetOpptSubCallback, resultForCallBack);
         mSetOpptSubCallback = null;
+        mPendingSwitchSubId = INVALID_SUBSCRIPTION_ID;
+    }
+
+    private void onNetworkAvailable(int subId, Network network) {
+        log("onNetworkAvailable: on subId " + subId);
+        // Do nothing unless pending switch matches target subId and it doesn't require
+        // validation pass.
+        if (mPendingSwitchSubId == INVALID_SUBSCRIPTION_ID || mPendingSwitchSubId != subId
+                || mPendingSwitchNeedValidation) {
+            return;
+        }
+        confirmSwitch(subId, true);
+    }
+
+    private void onValidationDone(int subId, boolean passed) {
+        log("onValidationDone: " + (passed ? "passed" : "failed") + " on subId " + subId);
+        if (mPendingSwitchSubId == INVALID_SUBSCRIPTION_ID || mPendingSwitchSubId != subId) return;
+
+        // If validation failed and mPendingSwitch.mNeedValidation is false, we still confirm
+        // the switch.
+        confirmSwitch(subId, passed || !mPendingSwitchNeedValidation);
     }
 
     /**
@@ -1249,7 +1334,7 @@
                 subId, needValidation ? 1 : 0, callback).sendToTarget();
     }
 
-    private boolean isPhoneInVoiceCall(Phone phone) {
+    protected boolean isPhoneInVoiceCall(Phone phone) {
         if (phone == null) {
             return false;
         }
@@ -1279,7 +1364,7 @@
     }
 
     @UnsupportedAppUsage
-    private void log(String l) {
+    protected void log(String l) {
         Rlog.d(LOG_TAG, l);
         mLocalLog.log(l);
     }
@@ -1295,29 +1380,31 @@
     /**
      * See {@link PhoneStateListener#LISTEN_ACTIVE_DATA_SUBSCRIPTION_ID_CHANGE}.
      */
-    private void notifyPreferredDataSubIdChanged() {
-        ITelephonyRegistry tr = ITelephonyRegistry.Stub.asInterface(ServiceManager.getService(
-                "telephony.registry"));
-        try {
-            log("notifyPreferredDataSubIdChanged to " + mPreferredDataSubId);
-            tr.notifyActiveDataSubIdChanged(mPreferredDataSubId);
-        } catch (RemoteException ex) {
-            // Should never happen because its always available.
-        }
+    protected void notifyPreferredDataSubIdChanged() {
+        TelephonyRegistryManager telephonyRegistryManager = (TelephonyRegistryManager) mContext
+                .getSystemService(Context.TELEPHONY_REGISTRY_SERVICE);
+        log("notifyPreferredDataSubIdChanged to " + mPreferredDataSubId.get());
+        telephonyRegistryManager.notifyActiveDataSubIdChanged(mPreferredDataSubId.get());
     }
 
     /**
      * @return The active data subscription id
      */
     public int getActiveDataSubId() {
-        return mPreferredDataSubId;
+        return mPreferredDataSubId.get();
+    }
+
+    // TODO (b/148396668): add an internal callback method to monitor phone capability change,
+    // and hook this call to that callback.
+    private void onPhoneCapabilityChanged(PhoneCapability capability) {
+        onPhoneCapabilityChangedInternal(capability);
     }
 
     public void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
         final IndentingPrintWriter pw = new IndentingPrintWriter(writer, "  ");
         pw.println("PhoneSwitcher:");
         Calendar c = Calendar.getInstance();
-        for (int i = 0; i < mNumPhones; i++) {
+        for (int i = 0; i < mActiveModemCount; i++) {
             PhoneState ps = mPhoneStates[i];
             c.setTimeInMillis(ps.lastRequested);
             pw.println("PhoneId(" + i + ") active=" + ps.active + ", lastRequest=" +
diff --git a/src/java/com/android/internal/telephony/ProxyController.java b/src/java/com/android/internal/telephony/ProxyController.java
index be84784..d6ed87b 100644
--- a/src/java/com/android/internal/telephony/ProxyController.java
+++ b/src/java/com/android/internal/telephony/ProxyController.java
@@ -16,7 +16,9 @@
 
 package com.android.internal.telephony;
 
-import android.annotation.UnsupportedAppUsage;
+import static java.util.Arrays.copyOf;
+
+import android.compat.annotation.UnsupportedAppUsage;
 import android.content.Context;
 import android.content.Intent;
 import android.os.AsyncResult;
@@ -25,12 +27,12 @@
 import android.os.PowerManager;
 import android.os.PowerManager.WakeLock;
 import android.telephony.RadioAccessFamily;
-import android.telephony.Rlog;
+import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyManager;
 import android.util.Log;
 
-import com.android.internal.telephony.ims.RcsMessageStoreController;
-import com.android.internal.telephony.uicc.UiccController;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.telephony.Rlog;
 
 import java.util.ArrayList;
 import java.util.HashSet;
@@ -40,11 +42,13 @@
 public class ProxyController {
     static final String LOG_TAG = "ProxyController";
 
-    private static final int EVENT_NOTIFICATION_RC_CHANGED        = 1;
+    private static final int EVENT_NOTIFICATION_RC_CHANGED  = 1;
     private static final int EVENT_START_RC_RESPONSE        = 2;
     private static final int EVENT_APPLY_RC_RESPONSE        = 3;
     private static final int EVENT_FINISH_RC_RESPONSE       = 4;
     private static final int EVENT_TIMEOUT                  = 5;
+    @VisibleForTesting
+    public static final int EVENT_MULTI_SIM_CONFIG_CHANGED  = 6;
 
     private static final int SET_RC_STATUS_IDLE             = 0;
     private static final int SET_RC_STATUS_STARTING         = 1;
@@ -64,10 +68,6 @@
 
     private Phone[] mPhones;
 
-    private UiccController mUiccController;
-
-    private CommandsInterface[] mCi;
-
     private Context mContext;
 
     private PhoneSwitcher mPhoneSwitcher;
@@ -109,10 +109,9 @@
 
 
     //***** Class Methods
-    public static ProxyController getInstance(Context context, Phone[] phone,
-            UiccController uiccController, CommandsInterface[] ci, PhoneSwitcher ps) {
+    public static ProxyController getInstance(Context context) {
         if (sProxyController == null) {
-            sProxyController = new ProxyController(context, phone, uiccController, ci, ps);
+            sProxyController = new ProxyController(context);
         }
         return sProxyController;
     }
@@ -122,20 +121,15 @@
         return sProxyController;
     }
 
-    private ProxyController(Context context, Phone[] phone, UiccController uiccController,
-            CommandsInterface[] ci, PhoneSwitcher phoneSwitcher) {
+    private ProxyController(Context context) {
         logd("Constructor - Enter");
 
         mContext = context;
-        mPhones = phone;
-        mUiccController = uiccController;
-        mCi = ci;
-        mPhoneSwitcher = phoneSwitcher;
+        mPhones = PhoneFactory.getPhones();
+        mPhoneSwitcher = PhoneSwitcher.getInstance();
 
-        RcsMessageStoreController.init(context);
-
-        mUiccPhoneBookController = new UiccPhoneBookController(mPhones);
-        mPhoneSubInfoController = new PhoneSubInfoController(mContext, mPhones);
+        mUiccPhoneBookController = new UiccPhoneBookController();
+        mPhoneSubInfoController = new PhoneSubInfoController(mContext);
         mSmsController = new SmsController(mContext);
         mSetRadioAccessFamilyStatus = new int[mPhones.length];
         mNewRadioAccessFamily = new int[mPhones.length];
@@ -154,13 +148,16 @@
             mPhones[i].registerForRadioCapabilityChanged(
                     mHandler, EVENT_NOTIFICATION_RC_CHANGED, null);
         }
+
+        PhoneConfigurationManager.registerForMultiSimConfigChange(
+                mHandler, EVENT_MULTI_SIM_CONFIG_CHANGED, null);
         logd("Constructor - Exit");
     }
 
     public void registerForAllDataDisconnected(int subId, Handler h, int what) {
         int phoneId = SubscriptionController.getInstance().getPhoneId(subId);
 
-        if (phoneId >= 0 && phoneId < TelephonyManager.getDefault().getPhoneCount()) {
+        if (SubscriptionManager.isValidPhoneId(phoneId)) {
             mPhones[phoneId].registerForAllDataDisconnected(h, what);
         }
     }
@@ -168,7 +165,7 @@
     public void unregisterForAllDataDisconnected(int subId, Handler h) {
         int phoneId = SubscriptionController.getInstance().getPhoneId(subId);
 
-        if (phoneId >= 0 && phoneId < TelephonyManager.getDefault().getPhoneCount()) {
+        if (SubscriptionManager.isValidPhoneId(phoneId)) {
             mPhones[phoneId].unregisterForAllDataDisconnected(h);
         }
     }
@@ -177,7 +174,7 @@
     public boolean areAllDataDisconnected(int subId) {
         int phoneId = SubscriptionController.getInstance().getPhoneId(subId);
 
-        if (phoneId >= 0 && phoneId < TelephonyManager.getDefault().getPhoneCount()) {
+        if (SubscriptionManager.isValidPhoneId(phoneId)) {
             return mPhones[phoneId].areAllDataDisconnected();
         } else {
             // if we can't find a phone for the given subId, it is disconnected.
@@ -209,7 +206,7 @@
      */
     public boolean setRadioCapability(RadioAccessFamily[] rafs) {
         if (rafs.length != mPhones.length) {
-            throw new RuntimeException("Length of input rafs must equal to total phone count");
+            return false;
         }
         // Check if there is any ongoing transaction and throw an exception if there
         // is one as this is a programming error.
@@ -299,7 +296,8 @@
         return true;
     }
 
-    private Handler mHandler = new Handler() {
+    @VisibleForTesting
+    public final Handler mHandler = new Handler() {
         @Override
         public void handleMessage(Message msg) {
             logd("handleMessage msg.what=" + msg.what);
@@ -324,12 +322,37 @@
                     onTimeoutRadioCapability(msg);
                     break;
 
+                case EVENT_MULTI_SIM_CONFIG_CHANGED:
+                    onMultiSimConfigChanged();
+                    break;
+
                 default:
                     break;
             }
         }
     };
 
+    private void onMultiSimConfigChanged() {
+        int oldPhoneCount = mPhones.length;
+        mPhones = PhoneFactory.getPhones();
+
+        // Re-size arrays.
+        mSetRadioAccessFamilyStatus = copyOf(mSetRadioAccessFamilyStatus, mPhones.length);
+        mNewRadioAccessFamily = copyOf(mNewRadioAccessFamily, mPhones.length);
+        mOldRadioAccessFamily = copyOf(mOldRadioAccessFamily, mPhones.length);
+        mCurrentLogicalModemIds = copyOf(mCurrentLogicalModemIds, mPhones.length);
+        mNewLogicalModemIds = copyOf(mNewLogicalModemIds, mPhones.length);
+
+        // Clear to be sure we're in the initial state
+        clearTransaction();
+
+        // Register radio cap change for new phones.
+        for (int i = oldPhoneCount; i < mPhones.length; i++) {
+            mPhones[i].registerForRadioCapabilityChanged(
+                    mHandler, EVENT_NOTIFICATION_RC_CHANGED, null);
+        }
+    }
+
     /**
      * Handle START response
      * @param msg obj field isa RadioCapability
diff --git a/src/java/com/android/internal/telephony/RIL.java b/src/java/com/android/internal/telephony/RIL.java
index 4953d26..91c79cb 100644
--- a/src/java/com/android/internal/telephony/RIL.java
+++ b/src/java/com/android/internal/telephony/RIL.java
@@ -19,7 +19,9 @@
 import static com.android.internal.telephony.RILConstants.*;
 import static com.android.internal.util.Preconditions.checkNotNull;
 
-import android.annotation.UnsupportedAppUsage;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.compat.annotation.UnsupportedAppUsage;
 import android.content.Context;
 import android.hardware.radio.V1_0.Carrier;
 import android.hardware.radio.V1_0.CarrierRestrictions;
@@ -35,7 +37,6 @@
 import android.hardware.radio.V1_0.IRadio;
 import android.hardware.radio.V1_0.IccIo;
 import android.hardware.radio.V1_0.ImsSmsMessage;
-import android.hardware.radio.V1_0.IndicationFilter;
 import android.hardware.radio.V1_0.LceDataInfo;
 import android.hardware.radio.V1_0.MvnoType;
 import android.hardware.radio.V1_0.NvWriteItem;
@@ -48,17 +49,18 @@
 import android.hardware.radio.V1_0.SimApdu;
 import android.hardware.radio.V1_0.SmsWriteArgs;
 import android.hardware.radio.V1_0.UusInfo;
-import android.hardware.radio.V1_2.AccessNetwork;
 import android.hardware.radio.V1_4.CarrierRestrictionsWithPriority;
 import android.hardware.radio.V1_4.SimLockMultiSimPolicy;
+import android.hardware.radio.V1_5.AccessNetwork;
+import android.hardware.radio.V1_5.IndicationFilter;
+import android.hardware.radio.V1_5.PersoSubstate;
+import android.hardware.radio.V1_5.RadioAccessNetworks;
 import android.hardware.radio.deprecated.V1_0.IOemHook;
-import android.net.ConnectivityManager;
+import android.net.InetAddresses;
 import android.net.KeepalivePacketData;
 import android.net.LinkAddress;
 import android.net.LinkProperties;
-import android.net.NetworkUtils;
 import android.os.AsyncResult;
-import android.os.Build;
 import android.os.Handler;
 import android.os.HwBinder;
 import android.os.Message;
@@ -66,10 +68,10 @@
 import android.os.PowerManager.WakeLock;
 import android.os.RemoteException;
 import android.os.SystemClock;
-import android.os.SystemProperties;
 import android.os.WorkSource;
 import android.provider.Settings;
 import android.service.carrier.CarrierIdentifier;
+import android.sysprop.TelephonyProperties;
 import android.telephony.AccessNetworkConstants.AccessNetworkType;
 import android.telephony.CarrierRestrictionRules;
 import android.telephony.CellInfo;
@@ -87,10 +89,11 @@
 import android.telephony.PhoneNumberUtils;
 import android.telephony.RadioAccessFamily;
 import android.telephony.RadioAccessSpecifier;
-import android.telephony.Rlog;
 import android.telephony.ServiceState;
 import android.telephony.SignalStrength;
+import android.telephony.SignalThresholdInfo;
 import android.telephony.SmsManager;
+import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyHistogram;
 import android.telephony.TelephonyManager;
 import android.telephony.TelephonyManager.PrefNetworkMode;
@@ -111,7 +114,10 @@
 import com.android.internal.telephony.gsm.SmsBroadcastConfigInfo;
 import com.android.internal.telephony.metrics.TelephonyMetrics;
 import com.android.internal.telephony.nano.TelephonyProto.SmsSession;
+import com.android.internal.telephony.uicc.IccCardApplicationStatus.PersoSubState;
 import com.android.internal.telephony.uicc.IccUtils;
+import com.android.internal.telephony.util.TelephonyUtils;
+import com.android.telephony.Rlog;
 
 import java.io.ByteArrayInputStream;
 import java.io.DataInputStream;
@@ -127,8 +133,10 @@
 import java.util.List;
 import java.util.NoSuchElementException;
 import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.concurrent.atomic.AtomicLong;
+import java.util.stream.Collectors;
 
 /**
  * RIL implementation of the CommandsInterface.
@@ -179,14 +187,32 @@
     /** @hide */
     public static final HalVersion RADIO_HAL_VERSION_1_4 = new HalVersion(1, 4);
 
+    /** @hide */
+    public static final HalVersion RADIO_HAL_VERSION_1_5 = new HalVersion(1, 5);
+
     // IRadio version
     private HalVersion mRadioVersion = RADIO_HAL_VERSION_UNKNOWN;
 
+    private static final int INDICATION_FILTERS_ALL_V1_0 =
+            IndicationFilter.SIGNAL_STRENGTH
+            | IndicationFilter.FULL_NETWORK_STATE
+            | IndicationFilter.DATA_CALL_DORMANCY_CHANGED;
+    private static final int INDICATION_FILTERS_ALL_V1_2 =
+            INDICATION_FILTERS_ALL_V1_0
+            | IndicationFilter.LINK_CAPACITY_ESTIMATE
+            | IndicationFilter.PHYSICAL_CHANNEL_CONFIG;
+    private static final  int INDICATION_FILTERS_ALL_V1_5 =
+            INDICATION_FILTERS_ALL_V1_2
+            | IndicationFilter.REGISTRATION_FAILURE
+            | IndicationFilter.BARRING_INFO;
+
     //***** Instance Variables
 
     @UnsupportedAppUsage
-    final WakeLock mWakeLock;           // Wake lock associated with request/response
-    final WakeLock mAckWakeLock;        // Wake lock associated with ack sent
+    @VisibleForTesting
+    public final WakeLock mWakeLock;           // Wake lock associated with request/response
+    @VisibleForTesting
+    public final WakeLock mAckWakeLock;        // Wake lock associated with ack sent
     final int mWakeLockTimeout;         // Timeout associated with request/response
     final int mAckWakeLockTimeout;      // Timeout associated with ack sent
     // The number of wakelock requests currently active.  Don't release the lock
@@ -204,7 +230,8 @@
 
     Object[] mLastNITZTimeInfo;
 
-    // When we are testing emergency calls
+    // When we are testing emergency calls using ril.test.emergencynumber, this will trigger test
+    // ECbM when the call is ended.
     @UnsupportedAppUsage
     AtomicBoolean mTestingEmergencyCall = new AtomicBoolean(false);
 
@@ -233,7 +260,7 @@
     /** Radio bug detector instance */
     private RadioBugDetector mRadioBugDetector = null;
 
-    boolean mIsMobileNetworkSupported;
+    boolean mIsCellularSupported;
     RadioResponse mRadioResponse;
     RadioIndication mRadioIndication;
     volatile IRadio mRadioProxy = null;
@@ -244,6 +271,13 @@
     final RadioProxyDeathRecipient mRadioProxyDeathRecipient;
     final RilHandler mRilHandler;
 
+    // Thread-safe HashMap to map from RIL_REQUEST_XXX constant to HalVersion.
+    // This is for Radio HAL Fallback Compatibility feature. When a RIL request
+    // is received, the HAL method from the mapping HalVersion here (if present),
+    // instead of the latest HalVersion, will be invoked.
+    private ConcurrentHashMap<Integer, HalVersion> mCompatOverrides =
+            new ConcurrentHashMap<>();
+
     //***** Events
     static final int EVENT_WAKE_LOCK_TIMEOUT    = 2;
     static final int EVENT_ACK_WAKE_LOCK_TIMEOUT    = 4;
@@ -378,7 +412,7 @@
         switch(rr.mRequest) {
             case RIL_REQUEST_GET_ACTIVITY_INFO:
                 timeoutResponse = new ModemActivityInfo(
-                        0, 0, 0, new int [ModemActivityInfo.TX_POWER_LEVELS], 0, 0);
+                        0, 0, 0, new int [ModemActivityInfo.TX_POWER_LEVELS], 0);
                 break;
         };
         return timeoutResponse;
@@ -393,7 +427,7 @@
         }
     }
 
-    private void resetProxyAndRequestList() {
+    private synchronized void resetProxyAndRequestList() {
         mRadioProxy = null;
         mOemHookProxy = null;
 
@@ -410,10 +444,30 @@
         getOemHookProxy(null);
     }
 
+    /** Set a radio HAL fallback compatibility override. */
+    @VisibleForTesting
+    public void setCompatVersion(int rilRequest, @NonNull HalVersion halVersion) {
+        HalVersion oldVersion = getCompatVersion(rilRequest);
+        // Do not allow to set same or greater verions
+        if (oldVersion != null && halVersion.greaterOrEqual(oldVersion)) {
+            riljLoge("setCompatVersion with equal or greater one, ignored, halVerion=" + halVersion
+                    + ", oldVerion=" + oldVersion);
+            return;
+        }
+        mCompatOverrides.put(rilRequest, halVersion);
+    }
+
+    /** Get a radio HAL fallback compatibility override, or null if not exist. */
+    @VisibleForTesting
+    public @Nullable HalVersion getCompatVersion(int rilRequest) {
+        return mCompatOverrides.getOrDefault(rilRequest, null);
+    }
+
     /** Returns a {@link IRadio} instance or null if the service is not available. */
     @VisibleForTesting
     public synchronized IRadio getRadioProxy(Message result) {
-        if (!mIsMobileNetworkSupported) {
+        if (!SubscriptionManager.isValidPhoneId(mPhoneId)) return null;
+        if (!mIsCellularSupported) {
             if (RILJ_LOGV) riljLog("getRadioProxy: Not calling getService(): wifi-only");
             if (result != null) {
                 AsyncResult.forMessage(result, null,
@@ -433,14 +487,23 @@
                         + " is disabled");
             } else {
                 try {
-                    mRadioProxy = android.hardware.radio.V1_4.IRadio.getService(
+                    mRadioProxy = android.hardware.radio.V1_5.IRadio.getService(
                             HIDL_SERVICE_NAME[mPhoneId], true);
-                    mRadioVersion = RADIO_HAL_VERSION_1_4;
+                    mRadioVersion = RADIO_HAL_VERSION_1_5;
                 } catch (NoSuchElementException e) {
                 }
 
                 if (mRadioProxy == null) {
                     try {
+                        mRadioProxy = android.hardware.radio.V1_4.IRadio.getService(
+                                HIDL_SERVICE_NAME[mPhoneId], true);
+                        mRadioVersion = RADIO_HAL_VERSION_1_4;
+                    } catch (NoSuchElementException e) {
+                    }
+                }
+
+                if (mRadioProxy == null) {
+                    try {
                         mRadioProxy = android.hardware.radio.V1_3.IRadio.getService(
                                 HIDL_SERVICE_NAME[mPhoneId], true);
                         mRadioVersion = RADIO_HAL_VERSION_1_3;
@@ -503,10 +566,22 @@
         return mRadioProxy;
     }
 
+    @Override
+    public synchronized void onSlotActiveStatusChange(boolean active) {
+        if (active) {
+            // Try to connect to RIL services and set response functions.
+            getRadioProxy(null);
+            getOemHookProxy(null);
+        } else {
+            resetProxyAndRequestList();
+        }
+    }
+
     /** Returns an {@link IOemHook} instance or null if the service is not available. */
     @VisibleForTesting
     public synchronized IOemHook getOemHookProxy(Message result) {
-        if (!mIsMobileNetworkSupported) {
+        if (!SubscriptionManager.isValidPhoneId((mPhoneId))) return null;
+        if (!mIsCellularSupported) {
             if (RILJ_LOGV) riljLog("getOemHookProxy: Not calling getService(): wifi-only");
             if (result != null) {
                 AsyncResult.forMessage(result, null,
@@ -580,9 +655,9 @@
             mRadioBugDetector = new RadioBugDetector(context, mPhoneId);
         }
 
-        ConnectivityManager cm = (ConnectivityManager)context.getSystemService(
-                Context.CONNECTIVITY_SERVICE);
-        mIsMobileNetworkSupported = cm.isNetworkSupported(ConnectivityManager.TYPE_MOBILE);
+        TelephonyManager tm = (TelephonyManager) context.getSystemService(
+                Context.TELEPHONY_SERVICE);
+        mIsCellularSupported = tm.isVoiceCapable() || tm.isSmsCapable() || tm.isDataCapable();
 
         mRadioResponse = new RadioResponse(this);
         mRadioIndication = new RadioIndication(this);
@@ -596,10 +671,10 @@
         mWakeLock.setReferenceCounted(false);
         mAckWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, RILJ_ACK_WAKELOCK_NAME);
         mAckWakeLock.setReferenceCounted(false);
-        mWakeLockTimeout = SystemProperties.getInt(TelephonyProperties.PROPERTY_WAKE_LOCK_TIMEOUT,
-                DEFAULT_WAKE_LOCK_TIMEOUT_MS);
-        mAckWakeLockTimeout = SystemProperties.getInt(
-                TelephonyProperties.PROPERTY_WAKE_LOCK_TIMEOUT, DEFAULT_ACK_WAKE_LOCK_TIMEOUT_MS);
+        mWakeLockTimeout = TelephonyProperties.wake_lock_timeout()
+                .orElse(DEFAULT_WAKE_LOCK_TIMEOUT_MS);
+        mAckWakeLockTimeout = TelephonyProperties.wake_lock_timeout()
+                .orElse(DEFAULT_ACK_WAKE_LOCK_TIMEOUT_MS);
         mWakeLockCount = 0;
         mRILDefaultWorkSource = new WorkSource(context.getApplicationInfo().uid,
                 context.getPackageName());
@@ -651,6 +726,13 @@
         return rr;
     }
 
+    private RILRequest obtainRequest(int request, Message result, WorkSource workSource,
+            Object... args) {
+        RILRequest rr = RILRequest.obtain(request, result, workSource, args);
+        addRequest(rr);
+        return rr;
+    }
+
     private void handleRadioProxyExceptionForRR(RILRequest rr, String caller, Exception e) {
         riljLoge(caller + ": " + e);
         resetProxyAndRequestList();
@@ -883,6 +965,115 @@
     }
 
     @Override
+    public void supplySimDepersonalization(PersoSubState persoType,
+            String controlKey, Message result) {
+        IRadio radioProxy = getRadioProxy(result);
+        // IRadio V1.5
+        if (mRadioVersion.greaterOrEqual(RADIO_HAL_VERSION_1_5)) {
+            android.hardware.radio.V1_5.IRadio radioProxy15 =
+                (android.hardware.radio.V1_5.IRadio) radioProxy;
+            if (radioProxy15 != null) {
+                RILRequest rr = obtainRequest(RIL_REQUEST_ENTER_SIM_DEPERSONALIZATION, result,
+                        mRILDefaultWorkSource);
+                if (RILJ_LOGD) {
+                    riljLog(rr.serialString() + "> " + requestToString(rr.mRequest) + " controlKey = "
+                        + controlKey + " persoType" + persoType);
+                }
+                try {
+                    radioProxy15.supplySimDepersonalization(rr.mSerial,
+                            convertPersoTypeToHalPersoType(persoType),
+                            convertNullToEmptyString(controlKey));
+                } catch (RemoteException | RuntimeException e) {
+                    handleRadioProxyExceptionForRR(rr, "supplySimDepersonalization", e);
+                }
+            }
+        } else {
+            if (result != null) {
+                AsyncResult.forMessage(result, null,
+                        CommandException.fromRilErrno(REQUEST_NOT_SUPPORTED));
+                result.sendToTarget();
+            }
+        }
+    }
+
+    private static int convertPersoTypeToHalPersoType(PersoSubState persoType) {
+
+        switch(persoType) {
+
+            case PERSOSUBSTATE_IN_PROGRESS:
+                return PersoSubstate.IN_PROGRESS;
+            case  PERSOSUBSTATE_READY:
+                return PersoSubstate.READY;
+            case PERSOSUBSTATE_SIM_NETWORK:
+                return PersoSubstate.SIM_NETWORK;
+            case PERSOSUBSTATE_SIM_NETWORK_SUBSET:
+                return PersoSubstate.SIM_NETWORK_SUBSET;
+            case PERSOSUBSTATE_SIM_CORPORATE:
+                return PersoSubstate.SIM_CORPORATE;
+            case PERSOSUBSTATE_SIM_SERVICE_PROVIDER:
+                return PersoSubstate.SIM_SERVICE_PROVIDER;
+            case PERSOSUBSTATE_SIM_SIM:
+                return PersoSubstate.SIM_SIM;
+            case PERSOSUBSTATE_SIM_NETWORK_PUK:
+                return PersoSubstate.SIM_NETWORK_PUK;
+            case PERSOSUBSTATE_SIM_NETWORK_SUBSET_PUK:
+                return PersoSubstate.SIM_NETWORK_SUBSET_PUK;
+            case PERSOSUBSTATE_SIM_CORPORATE_PUK:
+                return PersoSubstate.SIM_CORPORATE_PUK;
+            case PERSOSUBSTATE_SIM_SERVICE_PROVIDER_PUK:
+                return PersoSubstate.SIM_SERVICE_PROVIDER_PUK;
+            case PERSOSUBSTATE_SIM_SIM_PUK:
+                return PersoSubstate.SIM_SIM_PUK;
+            case PERSOSUBSTATE_RUIM_NETWORK1:
+                return PersoSubstate.RUIM_NETWORK1;
+            case PERSOSUBSTATE_RUIM_NETWORK2:
+                return PersoSubstate.RUIM_NETWORK2;
+            case PERSOSUBSTATE_RUIM_HRPD:
+                return PersoSubstate.RUIM_HRPD;
+            case PERSOSUBSTATE_RUIM_CORPORATE:
+                return PersoSubstate.RUIM_CORPORATE;
+            case PERSOSUBSTATE_RUIM_SERVICE_PROVIDER:
+                return PersoSubstate.RUIM_SERVICE_PROVIDER;
+            case PERSOSUBSTATE_RUIM_RUIM:
+                return PersoSubstate.RUIM_RUIM;
+            case PERSOSUBSTATE_RUIM_NETWORK1_PUK:
+                return PersoSubstate.RUIM_NETWORK1_PUK;
+            case PERSOSUBSTATE_RUIM_NETWORK2_PUK:
+                return PersoSubstate.RUIM_NETWORK2_PUK;
+            case PERSOSUBSTATE_RUIM_HRPD_PUK:
+                return PersoSubstate.RUIM_HRPD_PUK;
+            case PERSOSUBSTATE_RUIM_CORPORATE_PUK:
+                return PersoSubstate.RUIM_CORPORATE_PUK;
+            case PERSOSUBSTATE_RUIM_SERVICE_PROVIDER_PUK:
+                return PersoSubstate.RUIM_SERVICE_PROVIDER_PUK;
+            case PERSOSUBSTATE_RUIM_RUIM_PUK:
+                return PersoSubstate.RUIM_RUIM_PUK;
+            case PERSOSUBSTATE_SIM_SPN:
+                return PersoSubstate.SIM_SPN;
+            case PERSOSUBSTATE_SIM_SPN_PUK:
+                return PersoSubstate.SIM_SPN_PUK;
+            case PERSOSUBSTATE_SIM_SP_EHPLMN:
+                return PersoSubstate.SIM_SP_EHPLMN;
+            case PERSOSUBSTATE_SIM_SP_EHPLMN_PUK:
+                return PersoSubstate.SIM_SP_EHPLMN_PUK;
+            case PERSOSUBSTATE_SIM_ICCID:
+                return PersoSubstate.SIM_ICCID;
+            case PERSOSUBSTATE_SIM_ICCID_PUK:
+                return PersoSubstate.SIM_ICCID_PUK;
+            case PERSOSUBSTATE_SIM_IMPI:
+                return PersoSubstate.SIM_IMPI;
+            case PERSOSUBSTATE_SIM_IMPI_PUK:
+                return PersoSubstate.SIM_IMPI_PUK;
+            case PERSOSUBSTATE_SIM_NS_SP:
+                return PersoSubstate.SIM_NS_SP;
+            case PERSOSUBSTATE_SIM_NS_SP_PUK:
+                return PersoSubstate.SIM_NS_SP_PUK;
+            default:
+                return PersoSubstate.UNKNOWN;
+        }
+    }
+
+    @Override
     public void getCurrentCalls(Message result) {
         IRadio radioProxy = getRadioProxy(result);
         if (radioProxy != null) {
@@ -941,6 +1132,75 @@
     }
 
     @Override
+    public void setSystemSelectionChannels(@NonNull List<RadioAccessSpecifier> specifiers,
+            Message onComplete) {
+        IRadio radioProxy = getRadioProxy(onComplete);
+        if (mRadioVersion.less(RADIO_HAL_VERSION_1_3)) {
+            if (RILJ_LOGV) riljLog("setSystemSelectionChannels: not supported.");
+            if (onComplete != null) {
+                AsyncResult.forMessage(onComplete, null,
+                        CommandException.fromRilErrno(REQUEST_NOT_SUPPORTED));
+                onComplete.sendToTarget();
+            }
+            return;
+        }
+
+        RILRequest rr = obtainRequest(RIL_REQUEST_SET_SYSTEM_SELECTION_CHANNELS, onComplete,
+                mRILDefaultWorkSource);
+
+        if (mRadioVersion.less(RADIO_HAL_VERSION_1_5)) {
+            android.hardware.radio.V1_3.IRadio radioProxy13 =
+                    (android.hardware.radio.V1_3.IRadio) radioProxy;
+            if (radioProxy13 != null) {
+                if (RILJ_LOGD) {
+                    riljLog(rr.serialString() + "> " + requestToString(rr.mRequest)
+                            + " setSystemSelectionChannels_1.3= "
+                            + specifiers);
+                }
+
+                ArrayList<android.hardware.radio.V1_1.RadioAccessSpecifier> halSpecifiers =
+                        specifiers.stream()
+                                .map(this::convertRadioAccessSpecifierToRadioHAL)
+                                .collect(Collectors.toCollection(ArrayList::new));
+
+                try {
+                    radioProxy13.setSystemSelectionChannels(rr.mSerial,
+                            !halSpecifiers.isEmpty(),
+                            halSpecifiers);
+                } catch (RemoteException | RuntimeException e) {
+                    handleRadioProxyExceptionForRR(rr, "setSystemSelectionChannels", e);
+                }
+            }
+        }
+
+        if (mRadioVersion.greaterOrEqual(RADIO_HAL_VERSION_1_5)) {
+            android.hardware.radio.V1_5.IRadio radioProxy15 =
+                    (android.hardware.radio.V1_5.IRadio) radioProxy;
+
+            if (radioProxy15 != null) {
+                if (RILJ_LOGD) {
+                    riljLog(rr.serialString() + "> " + requestToString(rr.mRequest)
+                            + " setSystemSelectionChannels_1.5= "
+                            + specifiers);
+                }
+
+                ArrayList<android.hardware.radio.V1_5.RadioAccessSpecifier> halSpecifiers =
+                        specifiers.stream()
+                                .map(this::convertRadioAccessSpecifierToRadioHAL_1_5)
+                                .collect(Collectors.toCollection(ArrayList::new));
+
+                try {
+                    radioProxy15.setSystemSelectionChannels_1_5(rr.mSerial,
+                            !halSpecifiers.isEmpty(),
+                            halSpecifiers);
+                } catch (RemoteException | RuntimeException e) {
+                    handleRadioProxyExceptionForRR(rr, "setSystemSelectionChannels", e);
+                }
+            }
+        }
+    }
+
+    @Override
     public void getModemStatus(Message result) {
         IRadio radioProxy = getRadioProxy(result);
         if (mRadioVersion.less(RADIO_HAL_VERSION_1_3)) {
@@ -1236,10 +1496,26 @@
 
             if (RILJ_LOGD) riljLog(rr.serialString() + "> " + requestToString(rr.mRequest));
 
-            try {
-                radioProxy.getVoiceRegistrationState(rr.mSerial);
-            } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(rr, "getVoiceRegistrationState", e);
+            HalVersion overrideHalVersion = getCompatVersion(RIL_REQUEST_VOICE_REGISTRATION_STATE);
+            if (RILJ_LOGD) {
+                riljLog("getVoiceRegistrationState: overrideHalVersion=" + overrideHalVersion);
+            }
+            if ((overrideHalVersion == null
+                        || overrideHalVersion.greaterOrEqual(RADIO_HAL_VERSION_1_5))
+                    && mRadioVersion.greaterOrEqual(RADIO_HAL_VERSION_1_5)) {
+                final android.hardware.radio.V1_5.IRadio radioProxy15 =
+                        (android.hardware.radio.V1_5.IRadio) radioProxy;
+                try {
+                    radioProxy15.getVoiceRegistrationState_1_5(rr.mSerial);
+                } catch (RemoteException | RuntimeException e) {
+                    handleRadioProxyExceptionForRR(rr, "getVoiceRegistrationState_1_5", e);
+                }
+            } else {
+                try {
+                    radioProxy.getVoiceRegistrationState(rr.mSerial);
+                } catch (RemoteException | RuntimeException e) {
+                    handleRadioProxyExceptionForRR(rr, "getVoiceRegistrationState", e);
+                }
             }
         }
     }
@@ -1253,10 +1529,26 @@
 
             if (RILJ_LOGD) riljLog(rr.serialString() + "> " + requestToString(rr.mRequest));
 
-            try {
-                radioProxy.getDataRegistrationState(rr.mSerial);
-            } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(rr, "getDataRegistrationState", e);
+            HalVersion overrideHalVersion = getCompatVersion(RIL_REQUEST_DATA_REGISTRATION_STATE);
+            if (RILJ_LOGD) {
+                riljLog("getDataRegistrationState: overrideHalVersion=" + overrideHalVersion);
+            }
+            if ((overrideHalVersion == null
+                        || overrideHalVersion.greaterOrEqual(RADIO_HAL_VERSION_1_5))
+                    && mRadioVersion.greaterOrEqual(RADIO_HAL_VERSION_1_5)) {
+                final android.hardware.radio.V1_5.IRadio radioProxy15 =
+                        (android.hardware.radio.V1_5.IRadio) radioProxy;
+                try {
+                    radioProxy15.getDataRegistrationState_1_5(rr.mSerial);
+                } catch (RemoteException | RuntimeException e) {
+                    handleRadioProxyExceptionForRR(rr, "getDataRegistrationState_1_5", e);
+                }
+            } else {
+                try {
+                    radioProxy.getDataRegistrationState(rr.mSerial);
+                } catch (RemoteException | RuntimeException e) {
+                    handleRadioProxyExceptionForRR(rr, "getDataRegistrationState", e);
+                }
             }
         }
     }
@@ -1280,7 +1572,8 @@
 
     @UnsupportedAppUsage
     @Override
-    public void setRadioPower(boolean on, Message result) {
+    public void setRadioPower(boolean on, boolean forEmergencyCall,
+            boolean preferredForEmergencyCall, Message result) {
         IRadio radioProxy = getRadioProxy(result);
         if (radioProxy != null) {
             RILRequest rr = obtainRequest(RIL_REQUEST_RADIO_POWER, result,
@@ -1288,13 +1581,25 @@
 
             if (RILJ_LOGD) {
                 riljLog(rr.serialString() + "> " + requestToString(rr.mRequest)
-                        + " on = " + on);
+                        + " on = " + on + " forEmergencyCall= " + forEmergencyCall
+                        + " preferredForEmergencyCall="  + preferredForEmergencyCall);
             }
 
-            try {
-                radioProxy.setRadioPower(rr.mSerial, on);
-            } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(rr, "setRadioPower", e);
+            if (mRadioVersion.greaterOrEqual(RADIO_HAL_VERSION_1_5)) {
+                android.hardware.radio.V1_5.IRadio radioProxy15 =
+                        (android.hardware.radio.V1_5.IRadio) radioProxy;
+                try {
+                    radioProxy15.setRadioPower_1_5(rr.mSerial, on, forEmergencyCall,
+                            preferredForEmergencyCall);
+                } catch (RemoteException | RuntimeException e) {
+                    handleRadioProxyExceptionForRR(rr, "setRadioPower_1_5", e);
+                }
+            } else {
+                try {
+                    radioProxy.setRadioPower(rr.mSerial, on);
+                } catch (RemoteException | RuntimeException e) {
+                    handleRadioProxyExceptionForRR(rr, "setRadioPower", e);
+                }
             }
         }
     }
@@ -1412,7 +1717,7 @@
         // ServiceState.RIL_RADIO_TECHNOLOGY_XXXX.
         dpi.bearerBitmap = ServiceState.convertNetworkTypeBitmaskToBearerBitmask(
                 dp.getBearerBitmask()) << 1;
-        dpi.mtu = dp.getMtu();
+        dpi.mtu = dp.getMtuV4();
         dpi.mvnoType = MvnoType.NONE;
         dpi.mvnoMatchData = "";
 
@@ -1446,7 +1751,45 @@
         // ServiceState.RIL_RADIO_TECHNOLOGY_XXXX.
         dpi.bearerBitmap = ServiceState.convertNetworkTypeBitmaskToBearerBitmask(
                 dp.getBearerBitmask()) << 1;
-        dpi.mtu = dp.getMtu();
+        dpi.mtu = dp.getMtuV4();
+        dpi.persistent = dp.isPersistent();
+        dpi.preferred = dp.isPreferred();
+
+        // profile id is only meaningful when it's persistent on the modem.
+        dpi.profileId = (dpi.persistent) ? dp.getProfileId() : DataProfileId.INVALID;
+
+        return dpi;
+    }
+
+    /**
+     * Convert to DataProfileInfo defined in radio/1.5/types.hal
+     * @param dp Data profile
+     * @return A converted data profile
+     */
+    private static android.hardware.radio.V1_5.DataProfileInfo convertToHalDataProfile15(
+            DataProfile dp) {
+        android.hardware.radio.V1_5.DataProfileInfo dpi =
+                new android.hardware.radio.V1_5.DataProfileInfo();
+
+        dpi.apn = dp.getApn();
+        dpi.protocol = dp.getProtocolType();
+        dpi.roamingProtocol = dp.getRoamingProtocolType();
+        dpi.authType = dp.getAuthType();
+        dpi.user = dp.getUserName();
+        dpi.password = dp.getPassword();
+        dpi.type = dp.getType();
+        dpi.maxConnsTime = dp.getMaxConnectionsTime();
+        dpi.maxConns = dp.getMaxConnections();
+        dpi.waitTime = dp.getWaitTime();
+        dpi.enabled = dp.isEnabled();
+        dpi.supportedApnTypesBitmap = dp.getSupportedApnTypesBitmask();
+        // Shift by 1 bit due to the discrepancy between
+        // android.hardware.radio.V1_0.RadioAccessFamily and the bitmask version of
+        // ServiceState.RIL_RADIO_TECHNOLOGY_XXXX.
+        dpi.bearerBitmap = ServiceState.convertNetworkTypeBitmaskToBearerBitmask(
+            dp.getBearerBitmask()) << 1;
+        dpi.mtuV4 = dp.getMtuV4();
+        dpi.mtuV6 = dp.getMtuV6();
         dpi.persistent = dp.isPersistent();
         dpi.preferred = dp.isPreferred();
 
@@ -1500,7 +1843,40 @@
             }
 
             try {
-                if (mRadioVersion.greaterOrEqual(RADIO_HAL_VERSION_1_4)) {
+                if (mRadioVersion.greaterOrEqual(RADIO_HAL_VERSION_1_5)) {
+                    // IRadio V1.5
+                    android.hardware.radio.V1_5.IRadio radioProxy15 =
+                            (android.hardware.radio.V1_5.IRadio) radioProxy;
+
+                    // Convert to HAL data profile
+                    android.hardware.radio.V1_5.DataProfileInfo dpi =
+                            convertToHalDataProfile15(dataProfile);
+
+                    ArrayList<android.hardware.radio.V1_5.LinkAddress> addresses15 =
+                            new ArrayList<>();
+                    if (linkProperties != null) {
+                        for (LinkAddress la : linkProperties.getAllLinkAddresses()) {
+                            android.hardware.radio.V1_5.LinkAddress linkAddress =
+                                    new android.hardware.radio.V1_5.LinkAddress();
+                            linkAddress.address = la.getAddress().getHostAddress();
+                            linkAddress.properties = la.getFlags();
+                            linkAddress.deprecationTime = la.getDeprecationTime();
+                            linkAddress.expirationTime = la.getExpirationTime();
+                            addresses15.add(linkAddress);
+                        }
+                    }
+
+                    if (RILJ_LOGD) {
+                        riljLog(rr.serialString() + "> " + requestToString(rr.mRequest)
+                                + ",accessNetworkType="
+                                + AccessNetworkType.toString(accessNetworkType) + ",isRoaming="
+                                + isRoaming + ",allowRoaming=" + allowRoaming + "," + dataProfile
+                                + ",addresses=" + addresses15 + ",dnses=" + dnses);
+                    }
+
+                    radioProxy15.setupDataCall_1_5(rr.mSerial, accessNetworkType, dpi, allowRoaming,
+                            reason, addresses15, dnses);
+                } else if (mRadioVersion.greaterOrEqual(RADIO_HAL_VERSION_1_4)) {
                     // IRadio V1.4
                     android.hardware.radio.V1_4.IRadio radioProxy14 =
                             (android.hardware.radio.V1_4.IRadio) radioProxy;
@@ -1589,7 +1965,7 @@
                     mRILDefaultWorkSource);
 
             if (RILJ_LOGD) {
-                if (Build.IS_DEBUGGABLE) {
+                if (TelephonyUtils.IS_DEBUGGABLE) {
                     riljLog(rr.serialString() + "> iccIO: "
                             + requestToString(rr.mRequest) + " command = 0x"
                             + Integer.toHexString(command) + " fileId = 0x"
@@ -1993,20 +2369,31 @@
     }
 
     @Override
-    public void setNetworkSelectionModeManual(String operatorNumeric, Message result) {
+    public void setNetworkSelectionModeManual(String operatorNumeric, int ran, Message result) {
         IRadio radioProxy = getRadioProxy(result);
         if (radioProxy != null) {
             RILRequest rr = obtainRequest(RIL_REQUEST_SET_NETWORK_SELECTION_MANUAL, result,
                     mRILDefaultWorkSource);
-
-            if (RILJ_LOGD) {
-                riljLog(rr.serialString() + "> " + requestToString(rr.mRequest)
-                        + " operatorNumeric = " + operatorNumeric);
-            }
-
             try {
-                radioProxy.setNetworkSelectionModeManual(rr.mSerial,
-                        convertNullToEmptyString(operatorNumeric));
+                int halRan = convertAntToRan(ran);
+                if (mRadioVersion.greaterOrEqual(RADIO_HAL_VERSION_1_5)) {
+                    android.hardware.radio.V1_5.IRadio radioProxy15 =
+                            (android.hardware.radio.V1_5.IRadio) radioProxy;
+                    if (RILJ_LOGD) {
+                        riljLog(rr.serialString() + "> " + requestToString(rr.mRequest)
+                                + " operatorNumeric = " + operatorNumeric
+                                + ", ran = " + halRan);
+                    }
+                    radioProxy15.setNetworkSelectionModeManual_1_5(rr.mSerial,
+                            convertNullToEmptyString(operatorNumeric), halRan);
+                } else {
+                    if (RILJ_LOGD) {
+                        riljLog(rr.serialString() + "> " + requestToString(rr.mRequest)
+                                + " operatorNumeric = " + operatorNumeric);
+                    }
+                    radioProxy.setNetworkSelectionModeManual(rr.mSerial,
+                            convertNullToEmptyString(operatorNumeric));
+                }
             } catch (RemoteException | RuntimeException e) {
                 handleRadioProxyExceptionForRR(rr, "setNetworkSelectionModeManual", e);
             }
@@ -2035,28 +2422,28 @@
         android.hardware.radio.V1_1.RadioAccessSpecifier rasInHalFormat =
                 new android.hardware.radio.V1_1.RadioAccessSpecifier();
         rasInHalFormat.radioAccessNetwork = ras.getRadioAccessNetwork();
-        List<Integer> bands = null;
-        switch (ras.getRadioAccessNetwork()) {
-            case AccessNetworkType.GERAN:
-                bands = rasInHalFormat.geranBands;
-                break;
-            case AccessNetworkType.UTRAN:
-                bands = rasInHalFormat.utranBands;
-                break;
-            case AccessNetworkType.EUTRAN:
-                bands = rasInHalFormat.eutranBands;
-                break;
-            default:
-                Log.wtf(RILJ_LOG_TAG, "radioAccessNetwork " + ras.getRadioAccessNetwork()
-                        + " not supported!");
-                return null;
-        }
-
+        ArrayList<Integer> bands = new ArrayList<>();
         if (ras.getBands() != null) {
             for (int band : ras.getBands()) {
                 bands.add(band);
             }
         }
+        switch (ras.getRadioAccessNetwork()) {
+            case AccessNetworkType.GERAN:
+                rasInHalFormat.geranBands = bands;
+                break;
+            case AccessNetworkType.UTRAN:
+                rasInHalFormat.utranBands = bands;
+                break;
+            case AccessNetworkType.EUTRAN:
+                rasInHalFormat.eutranBands = bands;
+                break;
+            default:
+                Log.wtf(RILJ_LOG_TAG, "radioAccessNetwork " + ras.getRadioAccessNetwork()
+                        + " not supported on IRadio < 1.5!");
+                return null;
+        }
+
         if (ras.getChannels() != null) {
             for (int channel : ras.getChannels()) {
                 rasInHalFormat.channels.add(channel);
@@ -2066,12 +2453,103 @@
         return rasInHalFormat;
     }
 
+    private android.hardware.radio.V1_5.RadioAccessSpecifier
+            convertRadioAccessSpecifierToRadioHAL_1_5(RadioAccessSpecifier ras) {
+        android.hardware.radio.V1_5.RadioAccessSpecifier rasInHalFormat =
+                new android.hardware.radio.V1_5.RadioAccessSpecifier();
+        android.hardware.radio.V1_5.RadioAccessSpecifier.Bands bandsInHalFormat =
+                new android.hardware.radio.V1_5.RadioAccessSpecifier.Bands();
+        rasInHalFormat.radioAccessNetwork = convertAntToRan(ras.getRadioAccessNetwork());
+        ArrayList<Integer> bands = new ArrayList<>();
+        if (ras.getBands() != null) {
+            for (int band : ras.getBands()) {
+                bands.add(band);
+            }
+        }
+        switch (ras.getRadioAccessNetwork()) {
+            case AccessNetworkType.GERAN:
+                bandsInHalFormat.geranBands(bands);
+                break;
+            case AccessNetworkType.UTRAN:
+                bandsInHalFormat.utranBands(bands);
+                break;
+            case AccessNetworkType.EUTRAN:
+                bandsInHalFormat.eutranBands(bands);
+                break;
+            case AccessNetworkType.NGRAN:
+                bandsInHalFormat.ngranBands(bands);
+                break;
+            default:
+                Log.wtf(RILJ_LOG_TAG, "radioAccessNetwork " + ras.getRadioAccessNetwork()
+                        + " not supported on IRadio 1.5!");
+                return null;
+        }
+        rasInHalFormat.bands = bandsInHalFormat;
+
+        if (ras.getChannels() != null) {
+            for (int channel : ras.getChannels()) {
+                rasInHalFormat.channels.add(channel);
+            }
+        }
+
+        return rasInHalFormat;
+    }
+
+    /**
+     * Radio HAL fallback compatibility feature (b/151106728) assumes that the input parameter
+     * networkScanRequest is immutable (read-only) here. Once the caller invokes the method, the
+     * parameter networkScanRequest should not be modified. This helps us keep a consistent and
+     * simple data model that avoid copying it in the scan result.
+     */
     @Override
-    public void startNetworkScan(NetworkScanRequest nsr, Message result) {
+    public void startNetworkScan(NetworkScanRequest networkScanRequest, Message result) {
+        final NetworkScanRequest nsr = networkScanRequest;
         IRadio radioProxy = getRadioProxy(result);
         if (radioProxy != null) {
-            if (mRadioVersion.greaterOrEqual(RADIO_HAL_VERSION_1_2)) {
 
+            HalVersion overrideHalVersion = getCompatVersion(RIL_REQUEST_START_NETWORK_SCAN);
+            if (RILJ_LOGD) {
+                riljLog("startNetworkScan: overrideHalVersion=" + overrideHalVersion);
+            }
+            if ((overrideHalVersion == null
+                        || overrideHalVersion.greaterOrEqual(RADIO_HAL_VERSION_1_5))
+                    && mRadioVersion.greaterOrEqual(RADIO_HAL_VERSION_1_5)) {
+                android.hardware.radio.V1_5.NetworkScanRequest request =
+                        new android.hardware.radio.V1_5.NetworkScanRequest();
+                request.type = nsr.getScanType();
+                request.interval = nsr.getSearchPeriodicity();
+                request.maxSearchTime = nsr.getMaxSearchTime();
+                request.incrementalResultsPeriodicity = nsr.getIncrementalResultsPeriodicity();
+                request.incrementalResults = nsr.getIncrementalResults();
+
+                for (RadioAccessSpecifier ras : nsr.getSpecifiers()) {
+                    android.hardware.radio.V1_5.RadioAccessSpecifier rasInHalFormat =
+                            convertRadioAccessSpecifierToRadioHAL_1_5(ras);
+                    if (rasInHalFormat == null) {
+                        AsyncResult.forMessage(result, null,
+                                CommandException.fromRilErrno(REQUEST_NOT_SUPPORTED));
+                        result.sendToTarget();
+                        return;
+                    }
+                    request.specifiers.add(rasInHalFormat);
+                }
+
+                request.mccMncs.addAll(nsr.getPlmns());
+                RILRequest rr = obtainRequest(RIL_REQUEST_START_NETWORK_SCAN, result,
+                        mRILDefaultWorkSource, nsr);
+
+                if (RILJ_LOGD) {
+                    riljLog(rr.serialString() + "> " + requestToString(rr.mRequest));
+                }
+
+                try {
+                    android.hardware.radio.V1_5.IRadio radioProxy15 =
+                            (android.hardware.radio.V1_5.IRadio) radioProxy;
+                    radioProxy15.startNetworkScan_1_5(rr.mSerial, request);
+                } catch (RemoteException | RuntimeException e) {
+                    handleRadioProxyExceptionForRR(rr, "startNetworkScan", e);
+                }
+            } else if (mRadioVersion.greaterOrEqual(RADIO_HAL_VERSION_1_2)) {
                 android.hardware.radio.V1_2.NetworkScanRequest request =
                         new android.hardware.radio.V1_2.NetworkScanRequest();
                 request.type = nsr.getScanType();
@@ -2085,6 +2563,9 @@
                     android.hardware.radio.V1_1.RadioAccessSpecifier rasInHalFormat =
                             convertRadioAccessSpecifierToRadioHAL(ras);
                     if (rasInHalFormat == null) {
+                        AsyncResult.forMessage(result, null,
+                                CommandException.fromRilErrno(REQUEST_NOT_SUPPORTED));
+                        result.sendToTarget();
                         return;
                     }
 
@@ -2124,6 +2605,9 @@
                     android.hardware.radio.V1_1.RadioAccessSpecifier rasInHalFormat =
                             convertRadioAccessSpecifierToRadioHAL(ras);
                     if (rasInHalFormat == null) {
+                        AsyncResult.forMessage(result, null,
+                                CommandException.fromRilErrno(REQUEST_NOT_SUPPORTED));
+                        result.sendToTarget();
                         return;
                     }
 
@@ -2517,7 +3001,8 @@
 
             if (RILJ_LOGD) {
                 riljLog(rr.serialString() + "> " + requestToString(rr.mRequest) + " contents = "
-                        + (Build.IS_DEBUGGABLE ? contents : censoredTerminalResponse(contents)));
+                        + (TelephonyUtils.IS_DEBUGGABLE
+                            ? contents : censoredTerminalResponse(contents)));
             }
 
             try {
@@ -2607,13 +3092,7 @@
             mPreferredNetworkType = networkType;
             mMetrics.writeSetPreferredNetworkType(mPhoneId, networkType);
 
-            if (mRadioVersion.lessOrEqual(RADIO_HAL_VERSION_1_3)) {
-                try {
-                    radioProxy.setPreferredNetworkType(rr.mSerial, networkType);
-                } catch (RemoteException | RuntimeException e) {
-                    handleRadioProxyExceptionForRR(rr, "setPreferredNetworkType", e);
-                }
-            } else if (mRadioVersion.equals(RADIO_HAL_VERSION_1_4)) {
+            if (mRadioVersion.greaterOrEqual(RADIO_HAL_VERSION_1_4)) {
                 android.hardware.radio.V1_4.IRadio radioProxy14 =
                         (android.hardware.radio.V1_4.IRadio) radioProxy;
                 try {
@@ -2623,6 +3102,12 @@
                 } catch (RemoteException | RuntimeException e) {
                     handleRadioProxyExceptionForRR(rr, "setPreferredNetworkTypeBitmap", e);
                 }
+            } else {
+                try {
+                    radioProxy.setPreferredNetworkType(rr.mSerial, networkType);
+                } catch (RemoteException | RuntimeException e) {
+                    handleRadioProxyExceptionForRR(rr, "setPreferredNetworkType", e);
+                }
             }
         }
     }
@@ -2630,7 +3115,7 @@
     /**
      * convert RAF from {@link android.hardware.radio.V1_0.RadioAccessFamily} to
      * {@link TelephonyManager.NetworkTypeBitMask}, the bitmask represented by
-     * {@link TelephonyManager.NetworkType}.
+     * {@link android.telephony.Annotation.NetworkType}.
      *
      * @param raf {@link android.hardware.radio.V1_0.RadioAccessFamily}
      * @return {@link TelephonyManager.NetworkTypeBitMask}
@@ -2775,13 +3260,7 @@
             RILRequest rr = obtainRequest(RIL_REQUEST_GET_PREFERRED_NETWORK_TYPE, result,
                     mRILDefaultWorkSource);
             if (RILJ_LOGD) riljLog(rr.serialString() + "> " + requestToString(rr.mRequest));
-            if (mRadioVersion.lessOrEqual(RADIO_HAL_VERSION_1_3)) {
-                try {
-                    radioProxy.getPreferredNetworkType(rr.mSerial);
-                } catch (RemoteException | RuntimeException e) {
-                    handleRadioProxyExceptionForRR(rr, "getPreferredNetworkType", e);
-                }
-            } else if (mRadioVersion.equals(RADIO_HAL_VERSION_1_4)) {
+            if (mRadioVersion.greaterOrEqual(RADIO_HAL_VERSION_1_4)) {
                 android.hardware.radio.V1_4.IRadio radioProxy14 =
                         (android.hardware.radio.V1_4.IRadio) radioProxy;
                 try {
@@ -2789,6 +3268,12 @@
                 } catch (RemoteException | RuntimeException e) {
                     handleRadioProxyExceptionForRR(rr, "getPreferredNetworkTypeBitmap", e);
                 }
+            } else {
+                try {
+                    radioProxy.getPreferredNetworkType(rr.mSerial);
+                } catch (RemoteException | RuntimeException e) {
+                    handleRadioProxyExceptionForRR(rr, "getPreferredNetworkType", e);
+                }
             }
         }
     }
@@ -3023,6 +3508,36 @@
     }
 
     @Override
+    public void sendCdmaSMSExpectMore(byte[] pdu, Message result) {
+        if (mRadioVersion.greaterOrEqual(RADIO_HAL_VERSION_1_5)) {
+            IRadio radioProxy = getRadioProxy(result);
+            // IRadio V1.5
+            android.hardware.radio.V1_5.IRadio radioProxy15 =
+                    (android.hardware.radio.V1_5.IRadio) radioProxy;
+            if (radioProxy15 != null) {
+                RILRequest rr = obtainRequest(RIL_REQUEST_CDMA_SEND_SMS_EXPECT_MORE, result,
+                        mRILDefaultWorkSource);
+
+                // Do not log function arg for privacy
+                if (RILJ_LOGD) riljLog(rr.serialString() + "> " + requestToString(rr.mRequest));
+
+                CdmaSmsMessage msg = new CdmaSmsMessage();
+                constructCdmaSendSmsRilRequest(msg, pdu);
+
+                try {
+                    radioProxy15.sendCdmaSmsExpectMore(rr.mSerial, msg);
+                    mMetrics.writeRilSendSms(mPhoneId, rr.mSerial, SmsSession.Event.Tech.SMS_CDMA,
+                            SmsSession.Event.Format.SMS_FORMAT_3GPP2);
+                } catch (RemoteException | RuntimeException e) {
+                    handleRadioProxyExceptionForRR(rr, "sendCdmaSMSExpectMore", e);
+                }
+            }
+        } else {
+            sendCdmaSms(pdu, result);
+        }
+    }
+
+    @Override
     public void sendCdmaSms(byte[] pdu, Message result) {
         IRadio radioProxy = getRadioProxy(result);
         if (radioProxy != null) {
@@ -3236,7 +3751,7 @@
     }
 
     @Override
-    public void writeSmsToRuim(int status, String pdu, Message result) {
+    public void writeSmsToRuim(int status, byte[] pdu, Message result) {
         status = translateStatus(status);
         IRadio radioProxy = getRadioProxy(result);
         if (radioProxy != null) {
@@ -3251,7 +3766,7 @@
 
             CdmaSmsWriteArgs args = new CdmaSmsWriteArgs();
             args.status = status;
-            constructCdmaSendSmsRilRequest(args.message, pdu.getBytes());
+            constructCdmaSendSmsRilRequest(args.message, pdu);
 
             try {
                 radioProxy.writeSmsToRuim(rr.mSerial, args);
@@ -3499,7 +4014,13 @@
             }
 
             try {
-                if (mRadioVersion.greaterOrEqual(RADIO_HAL_VERSION_1_4)) {
+                if (mRadioVersion.greaterOrEqual(RADIO_HAL_VERSION_1_5)) {
+                    // v1.5
+                    android.hardware.radio.V1_5.IRadio radioProxy15 =
+                            (android.hardware.radio.V1_5.IRadio) radioProxy;
+                    radioProxy15.setInitialAttachApn_1_5(rr.mSerial,
+                            convertToHalDataProfile15(dataProfile));
+                } else if (mRadioVersion.greaterOrEqual(RADIO_HAL_VERSION_1_4)) {
                     // v1.4
                     android.hardware.radio.V1_4.IRadio radioProxy14 =
                             (android.hardware.radio.V1_4.IRadio) radioProxy;
@@ -3615,7 +4136,7 @@
                     mRILDefaultWorkSource);
 
             if (RILJ_LOGD) {
-                if (Build.IS_DEBUGGABLE) {
+                if (TelephonyUtils.IS_DEBUGGABLE) {
                     riljLog(rr.serialString() + "> " + requestToString(rr.mRequest)
                             + String.format(" cla = 0x%02X ins = 0x%02X", cla, instruction)
                             + String.format(" p1 = 0x%02X p2 = 0x%02X p3 = 0x%02X", p1, p2, p3)
@@ -3642,7 +4163,7 @@
                     mRILDefaultWorkSource);
 
             if (RILJ_LOGD) {
-                if (Build.IS_DEBUGGABLE) {
+                if (TelephonyUtils.IS_DEBUGGABLE) {
                     riljLog(rr.serialString() + "> " + requestToString(rr.mRequest) + " aid = " + aid
                             + " p2 = " + p2);
                 } else {
@@ -3693,7 +4214,7 @@
                     mRILDefaultWorkSource);
 
             if (RILJ_LOGD) {
-                if (Build.IS_DEBUGGABLE) {
+                if (TelephonyUtils.IS_DEBUGGABLE) {
                     riljLog(rr.serialString() + "> " + requestToString(rr.mRequest)
                             + String.format(" channel = %d", channel)
                             + String.format(" cla = 0x%02X ins = 0x%02X", cla, instruction)
@@ -3902,7 +4423,29 @@
 
             RILRequest rr = null;
             try {
-                if (mRadioVersion.greaterOrEqual(RADIO_HAL_VERSION_1_4)) {
+                if (mRadioVersion.greaterOrEqual(RADIO_HAL_VERSION_1_5)) {
+                    // V1.5
+                    android.hardware.radio.V1_5.IRadio radioProxy15 =
+                            (android.hardware.radio.V1_5.IRadio) radioProxy;
+
+                    rr = obtainRequest(RIL_REQUEST_SET_DATA_PROFILE, result,
+                            mRILDefaultWorkSource);
+
+                    ArrayList<android.hardware.radio.V1_5.DataProfileInfo> dpis = new ArrayList<>();
+                    for (DataProfile dp : dps) {
+                        dpis.add(convertToHalDataProfile15(dp));
+                    }
+
+                    if (RILJ_LOGD) {
+                        riljLog(rr.serialString() + "> " + requestToString(rr.mRequest)
+                                + " with data profiles : ");
+                        for (DataProfile profile : dps) {
+                            riljLog(profile.toString());
+                        }
+                    }
+
+                    radioProxy15.setDataProfile_1_5(rr.mSerial, dpis);
+                } else if (mRadioVersion.greaterOrEqual(RADIO_HAL_VERSION_1_4)) {
                     // V1.4
                     android.hardware.radio.V1_4.IRadio radioProxy14 =
                             (android.hardware.radio.V1_4.IRadio) radioProxy;
@@ -4176,9 +4719,7 @@
         workSource = getDeafultWorkSourceIfInvalid(workSource);
 
         IRadio radioProxy = getRadioProxy(result);
-        if (radioProxy == null) {
-            return;
-        }
+        if (radioProxy == null) return;
 
         RILRequest rr = obtainRequest(RIL_REQUEST_SET_ALLOWED_CARRIERS, result, workSource);
 
@@ -4258,9 +4799,7 @@
         workSource = getDeafultWorkSourceIfInvalid(workSource);
 
         IRadio radioProxy = getRadioProxy(result);
-        if (radioProxy == null) {
-            return;
-        }
+        if (radioProxy == null) return;
 
         RILRequest rr = obtainRequest(RIL_REQUEST_GET_ALLOWED_CARRIERS, result,
                 workSource);
@@ -4323,19 +4862,30 @@
                 riljLog(rr.serialString() + "> " + requestToString(rr.mRequest) + " " + filter);
             }
 
-            if (mRadioVersion.greaterOrEqual(RADIO_HAL_VERSION_1_2)) {
+            if (mRadioVersion.greaterOrEqual(RADIO_HAL_VERSION_1_5)) {
+                try {
+                    android.hardware.radio.V1_5.IRadio radioProxy15 =
+                            (android.hardware.radio.V1_5.IRadio) radioProxy;
+
+                    filter &= INDICATION_FILTERS_ALL_V1_5;
+                    radioProxy15.setIndicationFilter_1_5(rr.mSerial, filter);
+                } catch (RemoteException | RuntimeException e) {
+                    handleRadioProxyExceptionForRR(rr, "setIndicationFilter_1_5", e);
+                }
+            } else if (mRadioVersion.greaterOrEqual(RADIO_HAL_VERSION_1_2)) {
                 try {
                     android.hardware.radio.V1_2.IRadio radioProxy12 =
                             (android.hardware.radio.V1_2.IRadio) radioProxy;
 
+                    filter &= INDICATION_FILTERS_ALL_V1_2;
                     radioProxy12.setIndicationFilter_1_2(rr.mSerial, filter);
                 } catch (RemoteException | RuntimeException e) {
                     handleRadioProxyExceptionForRR(rr, "setIndicationFilter_1_2", e);
                 }
             } else {
                 try {
-                    int filter10 = filter & IndicationFilter.ALL;
-                    radioProxy.setIndicationFilter(rr.mSerial, filter10);
+                    filter &= INDICATION_FILTERS_ALL_V1_0;
+                    radioProxy.setIndicationFilter(rr.mSerial, filter);
                 } catch (RemoteException | RuntimeException e) {
                     handleRadioProxyExceptionForRR(rr, "setIndicationFilter", e);
                 }
@@ -4344,8 +4894,8 @@
     }
 
     @Override
-    public void setSignalStrengthReportingCriteria(int hysteresisMs, int hysteresisDb,
-            int[] thresholdsDbm, int ran, Message result) {
+    public void setSignalStrengthReportingCriteria(SignalThresholdInfo signalThresholdInfo,
+            int ran, Message result) {
         IRadio radioProxy = getRadioProxy(result);
         if (radioProxy != null) {
             if (mRadioVersion.less(RADIO_HAL_VERSION_1_2)) {
@@ -4353,59 +4903,99 @@
                         + "than 1.2");
                 return;
             }
-
-            RILRequest rr = obtainRequest(RIL_REQUEST_SET_SIGNAL_STRENGTH_REPORTING_CRITERIA,
-                    result, mRILDefaultWorkSource);
-
-            if (RILJ_LOGD) {
-                riljLog(rr.serialString() + "> " + requestToString(rr.mRequest));
-            }
-
-            try {
-                android.hardware.radio.V1_2.IRadio radioProxy12 =
+            if (mRadioVersion.greaterOrEqual(RADIO_HAL_VERSION_1_2)
+                    && mRadioVersion.less(RADIO_HAL_VERSION_1_5)) {
+                RILRequest rr = obtainRequest(RIL_REQUEST_SET_SIGNAL_STRENGTH_REPORTING_CRITERIA,
+                        result, mRILDefaultWorkSource);
+                if (RILJ_LOGD) {
+                    riljLog(rr.serialString() + "> " + requestToString(rr.mRequest));
+                }
+                try {
+                    android.hardware.radio.V1_2.IRadio radioProxy12 =
                         (android.hardware.radio.V1_2.IRadio) radioProxy;
-                radioProxy12.setSignalStrengthReportingCriteria(rr.mSerial, hysteresisMs,
-                        hysteresisDb, primitiveArrayToArrayList(thresholdsDbm),
-                        convertRanToHalRan(ran));
-            } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(rr, "setSignalStrengthReportingCriteria", e);
+                    radioProxy12.setSignalStrengthReportingCriteria(rr.mSerial,
+                            signalThresholdInfo.getHysteresisMs(),
+                            signalThresholdInfo.getHysteresisDb(),
+                            primitiveArrayToArrayList(signalThresholdInfo.getThresholds()),
+                            convertAntToHalAnt(ran));
+                } catch (RemoteException | RuntimeException e) {
+                    handleRadioProxyExceptionForRR(rr, "setSignalStrengthReportingCriteria", e);
+                }
+            }
+            if (mRadioVersion.greaterOrEqual(RADIO_HAL_VERSION_1_5)) {
+                RILRequest rr = obtainRequest(RIL_REQUEST_SET_SIGNAL_STRENGTH_REPORTING_CRITERIA,
+                        result, mRILDefaultWorkSource);
+                if (RILJ_LOGD) {
+                    riljLog(rr.serialString() + "> " + requestToString(rr.mRequest));
+                }
+                try {
+                    android.hardware.radio.V1_5.IRadio radioProxy15 =
+                            (android.hardware.radio.V1_5.IRadio) radioProxy;
+                    radioProxy15.setSignalStrengthReportingCriteria_1_5(rr.mSerial,
+                            convertToHalSignalThresholdInfo(signalThresholdInfo),
+                            convertAntToHalAnt(ran));
+                } catch (RemoteException | RuntimeException e) {
+                    handleRadioProxyExceptionForRR(
+                            rr, "setSignalStrengthReportingCriteria_1_5", e);
+                }
             }
         }
     }
 
+    private static android.hardware.radio.V1_5.SignalThresholdInfo convertToHalSignalThresholdInfo(
+            SignalThresholdInfo signalThresholdInfo) {
+        android.hardware.radio.V1_5.SignalThresholdInfo signalThresholdInfoHal =
+                new android.hardware.radio.V1_5.SignalThresholdInfo();
+        signalThresholdInfoHal.signalMeasurement = signalThresholdInfo.getSignalMeasurement();
+        signalThresholdInfoHal.hysteresisMs = signalThresholdInfo.getHysteresisMs();
+        signalThresholdInfoHal.hysteresisDb = signalThresholdInfo.getHysteresisDb();
+        signalThresholdInfoHal.thresholds = primitiveArrayToArrayList(
+                signalThresholdInfo.getThresholds());
+        signalThresholdInfoHal.isEnabled = signalThresholdInfo.isEnabled();
+        return signalThresholdInfoHal;
+    }
+
     @Override
     public void setLinkCapacityReportingCriteria(int hysteresisMs, int hysteresisDlKbps,
             int hysteresisUlKbps, int[] thresholdsDlKbps, int[] thresholdsUlKbps, int ran,
             Message result) {
         IRadio radioProxy = getRadioProxy(result);
         if (radioProxy != null) {
-            if (mRadioVersion.less(RADIO_HAL_VERSION_1_2)) {
-                riljLoge("setLinkCapacityReportingCriteria ignored on IRadio version less "
-                        + "than 1.2");
-                return;
-            }
-
-            RILRequest rr = obtainRequest(RIL_REQUEST_SET_LINK_CAPACITY_REPORTING_CRITERIA,
-                    result, mRILDefaultWorkSource);
-
+            RILRequest rr = obtainRequest(RIL_REQUEST_SET_LINK_CAPACITY_REPORTING_CRITERIA, result,
+                    mRILDefaultWorkSource);
             if (RILJ_LOGD) {
                 riljLog(rr.serialString() + "> " + requestToString(rr.mRequest));
             }
-
             try {
-                android.hardware.radio.V1_2.IRadio radioProxy12 =
-                        (android.hardware.radio.V1_2.IRadio) radioProxy;
-                radioProxy12.setLinkCapacityReportingCriteria(rr.mSerial, hysteresisMs,
-                        hysteresisDlKbps, hysteresisUlKbps,
-                        primitiveArrayToArrayList(thresholdsDlKbps),
-                        primitiveArrayToArrayList(thresholdsUlKbps), convertRanToHalRan(ran));
+                if (mRadioVersion.greaterOrEqual(RADIO_HAL_VERSION_1_5)) {
+                    android.hardware.radio.V1_5.IRadio radioProxy15 =
+                            (android.hardware.radio.V1_5.IRadio) radioProxy;
+                    radioProxy15.setLinkCapacityReportingCriteria_1_5(rr.mSerial, hysteresisMs,
+                            hysteresisDlKbps, hysteresisUlKbps,
+                            primitiveArrayToArrayList(thresholdsDlKbps),
+                            primitiveArrayToArrayList(thresholdsUlKbps), convertAntToHalAnt(ran));
+                } else if (mRadioVersion.greaterOrEqual(RADIO_HAL_VERSION_1_2)) {
+                    android.hardware.radio.V1_2.IRadio radioProxy12 =
+                            (android.hardware.radio.V1_2.IRadio) radioProxy;
+                    if (ran == AccessNetworkType.NGRAN) {
+                        throw new RuntimeException("NGRAN unsupported on IRadio version 1.2.");
+                    }
+                    radioProxy12.setLinkCapacityReportingCriteria(rr.mSerial, hysteresisMs,
+                            hysteresisDlKbps, hysteresisUlKbps,
+                            primitiveArrayToArrayList(thresholdsDlKbps),
+                            primitiveArrayToArrayList(thresholdsUlKbps), convertAntToHalAnt(ran));
+                } else {
+                    riljLoge("setLinkCapacityReportingCriteria ignored on IRadio version less "
+                            + "than 1.2");
+                }
             } catch (RemoteException | RuntimeException e) {
                 handleRadioProxyExceptionForRR(rr, "setLinkCapacityReportingCriteria", e);
             }
         }
     }
 
-    private static int convertRanToHalRan(int radioAccessNetwork) {
+    /** Converts from AccessNetworkType in frameworks to AccessNetwork in HAL. */
+    private static int convertAntToHalAnt(int radioAccessNetwork) {
         switch (radioAccessNetwork) {
             case AccessNetworkType.GERAN:
                 return AccessNetwork.GERAN;
@@ -4417,9 +5007,30 @@
                 return AccessNetwork.CDMA2000;
             case AccessNetworkType.IWLAN:
                 return AccessNetwork.IWLAN;
+            case AccessNetworkType.NGRAN:
+                return AccessNetwork.NGRAN;
             case AccessNetworkType.UNKNOWN:
             default:
-                return 0;
+                return AccessNetwork.UNKNOWN;
+        }
+    }
+
+    /** Converts from AccessNetworkType in frameworks to RadioAccessNetworks in HAL. */
+    private static int convertAntToRan(int accessNetworkType) {
+        switch (accessNetworkType) {
+            case AccessNetworkType.GERAN:
+                return RadioAccessNetworks.GERAN;
+            case AccessNetworkType.UTRAN:
+                return RadioAccessNetworks.UTRAN;
+            case AccessNetworkType.EUTRAN:
+                return RadioAccessNetworks.EUTRAN;
+            case AccessNetworkType.NGRAN:
+                return RadioAccessNetworks.NGRAN;
+            case AccessNetworkType.CDMA2000:
+                return RadioAccessNetworks.CDMA2000;
+            case AccessNetworkType.UNKNOWN:
+            default:
+                return RadioAccessNetworks.UNKNOWN;
         }
     }
 
@@ -4515,10 +5126,7 @@
             int contextId, KeepalivePacketData packetData, int intervalMillis, Message result) {
         checkNotNull(packetData, "KeepaliveRequest cannot be null.");
         IRadio radioProxy = getRadioProxy(result);
-        if (radioProxy == null) {
-            riljLoge("Radio Proxy object is null!");
-            return;
-        }
+        if (radioProxy == null) return;
 
         if (mRadioVersion.less(RADIO_HAL_VERSION_1_1)) {
             if (result != null) {
@@ -4543,9 +5151,9 @@
 
             req.cid = contextId;
 
-            if (packetData.dstAddress instanceof Inet4Address) {
+            if (packetData.getDstAddress() instanceof Inet4Address) {
                 req.type = android.hardware.radio.V1_1.KeepaliveType.NATT_IPV4;
-            } else if (packetData.dstAddress instanceof Inet6Address) {
+            } else if (packetData.getDstAddress() instanceof Inet6Address) {
                 req.type = android.hardware.radio.V1_1.KeepaliveType.NATT_IPV6;
             } else {
                 AsyncResult.forMessage(result, null,
@@ -4554,12 +5162,14 @@
                 return;
             }
 
+            final InetAddress srcAddress = packetData.getSrcAddress();
+            final InetAddress dstAddress = packetData.getDstAddress();
             appendPrimitiveArrayToArrayList(
-                    packetData.srcAddress.getAddress(), req.sourceAddress);
-            req.sourcePort = packetData.srcPort;
+                    srcAddress.getAddress(), req.sourceAddress);
+            req.sourcePort = packetData.getSrcPort();
             appendPrimitiveArrayToArrayList(
-                    packetData.dstAddress.getAddress(), req.destinationAddress);
-            req.destinationPort = packetData.dstPort;
+                    dstAddress.getAddress(), req.destinationAddress);
+            req.destinationPort = packetData.getDstPort();
             req.maxKeepaliveIntervalMillis = intervalMillis;
 
             radioProxy11.startKeepalive(rr.mSerial, req);
@@ -4571,10 +5181,7 @@
     @Override
     public void stopNattKeepalive(int sessionHandle, Message result) {
         IRadio radioProxy = getRadioProxy(result);
-        if (radioProxy == null) {
-            Rlog.e(RIL.RILJ_LOG_TAG, "Radio Proxy object is null!");
-            return;
-        }
+        if (radioProxy == null) return;
 
         if (mRadioVersion.less(RADIO_HAL_VERSION_1_1)) {
             if (result != null) {
@@ -4628,6 +5235,84 @@
     }
 
     /**
+     * Enable or disable uicc applications on the SIM.
+     *
+     * @param enable whether to enable or disable uicc applications.
+     * @param onCompleteMessage a Message to return to the requester
+     */
+    @Override
+    public void enableUiccApplications(boolean enable, Message onCompleteMessage) {
+        IRadio radioProxy = getRadioProxy(onCompleteMessage);
+        if (radioProxy == null) return;
+
+        if (mRadioVersion.less(RADIO_HAL_VERSION_1_5)) {
+            if (onCompleteMessage != null) {
+                AsyncResult.forMessage(onCompleteMessage, null,
+                        CommandException.fromRilErrno(REQUEST_NOT_SUPPORTED));
+                onCompleteMessage.sendToTarget();
+            }
+            return;
+        }
+
+        android.hardware.radio.V1_5.IRadio radioProxy15 =
+                (android.hardware.radio.V1_5.IRadio) radioProxy;
+
+        RILRequest rr = obtainRequest(RIL_REQUEST_ENABLE_UICC_APPLICATIONS,
+                onCompleteMessage, mRILDefaultWorkSource);
+
+        if (RILJ_LOGD) riljLog(rr.serialString() + "> " + requestToString(rr.mRequest));
+
+        try {
+            radioProxy15.enableUiccApplications(rr.mSerial, enable);
+        } catch (RemoteException | RuntimeException e) {
+            handleRadioProxyExceptionForRR(rr, "enableUiccApplications", e);
+        }
+    }
+
+    /**
+     * Whether uicc applications are enabled or not.
+     *
+     * @param onCompleteMessage a Message to return to the requester
+     */
+    @Override
+    public void areUiccApplicationsEnabled(Message onCompleteMessage) {
+        IRadio radioProxy = getRadioProxy(onCompleteMessage);
+        if (radioProxy == null) return;
+
+        if (mRadioVersion.less(RADIO_HAL_VERSION_1_5)) {
+            if (onCompleteMessage != null) {
+                AsyncResult.forMessage(onCompleteMessage, null,
+                        CommandException.fromRilErrno(REQUEST_NOT_SUPPORTED));
+                onCompleteMessage.sendToTarget();
+            }
+            return;
+        }
+
+        android.hardware.radio.V1_5.IRadio radioProxy15 =
+                (android.hardware.radio.V1_5.IRadio) radioProxy;
+
+        RILRequest rr = obtainRequest(RIL_REQUEST_GET_UICC_APPLICATIONS_ENABLEMENT,
+                onCompleteMessage, mRILDefaultWorkSource);
+
+        if (RILJ_LOGD) riljLog(rr.serialString() + "> " + requestToString(rr.mRequest));
+
+        try {
+            radioProxy15.areUiccApplicationsEnabled(rr.mSerial);
+        } catch (RemoteException | RuntimeException e) {
+            handleRadioProxyExceptionForRR(rr, "areUiccApplicationsEnabled", e);
+        }
+    }
+
+    /**
+     * Whether {@link #enableUiccApplications} is supported, which is supported in 1.5 version.
+     */
+    @Override
+    public boolean canToggleUiccApplicationsEnablement() {
+        return getRadioProxy(null) != null && mRadioVersion
+                .greaterOrEqual(RADIO_HAL_VERSION_1_5);
+    }
+
+    /**
      *  Translates EF_SMS status bits to a status value compatible with
      *  SMS AT commands.  See TS 27.005 3.1.
      */
@@ -4674,6 +5359,39 @@
         }
     }
 
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void getBarringInfo(Message result) {
+        IRadio radioProxy = getRadioProxy(result);
+        if (radioProxy == null) return;
+
+        if (mRadioVersion.less(RADIO_HAL_VERSION_1_5)) {
+            if (result != null) {
+                AsyncResult.forMessage(result, null,
+                        CommandException.fromRilErrno(REQUEST_NOT_SUPPORTED));
+                result.sendToTarget();
+            }
+            return;
+        }
+
+        android.hardware.radio.V1_5.IRadio radioProxy15 =
+                (android.hardware.radio.V1_5.IRadio) radioProxy;
+        if (radioProxy15 != null) {
+            RILRequest rr = obtainRequest(RIL_REQUEST_GET_BARRING_INFO, result,
+                    mRILDefaultWorkSource);
+
+            if (RILJ_LOGD) riljLog(rr.serialString() + "> " + requestToString(rr.mRequest));
+
+            try {
+                radioProxy15.getBarringInfo(rr.mSerial);
+            } catch (RemoteException | RuntimeException e) {
+                handleRadioProxyExceptionForRR(rr, "getBarringInfo", e);
+            }
+        }
+    }
+
     //***** Private Methods
 
     /**
@@ -4834,6 +5552,28 @@
             }
             rr.onError(responseInfo.error, ret);
         }
+        processResponseCleanUp(rr, responseInfo, ret);
+    }
+
+    /**
+     * This is a helper function to be called at the end of all RadioResponse callbacks for
+     * radio HAL fallback cases. It takes care of logging, decrementing wakelock if needed, and
+     * releases the request from memory pool. Unlike processResponseDone, it will not send
+     * error response to caller.
+     * @param rr RILRequest for which response callback was called
+     * @param responseInfo RadioResponseInfo received in the callback
+     * @param ret object to be returned to request sender
+     */
+    @VisibleForTesting
+    public void processResponseFallback(RILRequest rr, RadioResponseInfo responseInfo, Object ret) {
+        if (responseInfo.error == REQUEST_NOT_SUPPORTED && RILJ_LOGD) {
+            riljLog(rr.serialString() + "< " + requestToString(rr.mRequest)
+                    + " request not supported, falling back");
+        }
+        processResponseCleanUp(rr, responseInfo, ret);
+    }
+
+    private void processResponseCleanUp(RILRequest rr, RadioResponseInfo responseInfo, Object ret) {
         mMetrics.writeOnRilSolicitedResponse(mPhoneId, rr.mSerial, responseInfo.error,
                 rr.mRequest, ret);
         if (rr != null) {
@@ -5174,6 +5914,7 @@
 
     void writeMetricsSrvcc(int state) {
         mMetrics.writeRilSrvcc(mPhoneId, state);
+        PhoneFactory.getPhone(mPhoneId).getVoiceCallSessionStats().onRilSrvccStateChanged(state);
     }
 
     void writeMetricsModemRestartEvent(String reason) {
@@ -5549,6 +6290,19 @@
                 return "RIL_REQUEST_ENABLE_MODEM";
             case RIL_REQUEST_GET_MODEM_STATUS:
                 return "RIL_REQUEST_GET_MODEM_STATUS";
+            case RIL_REQUEST_ENABLE_UICC_APPLICATIONS:
+                return "RIL_REQUEST_ENABLE_UICC_APPLICATIONS";
+            case RIL_REQUEST_GET_UICC_APPLICATIONS_ENABLEMENT:
+                return "RIL_REQUEST_GET_UICC_APPLICATIONS_ENABLEMENT";
+            case RIL_REQUEST_SET_SYSTEM_SELECTION_CHANNELS:
+                return "RIL_REQUEST_SET_SYSTEM_SELECTION_CHANNELS";
+            case RIL_REQUEST_CDMA_SEND_SMS_EXPECT_MORE:
+                return "RIL_REQUEST_CDMA_SEND_SMS_EXPECT_MORE";
+            case RIL_REQUEST_GET_BARRING_INFO:
+                return "RIL_REQUEST_GET_BARRING_INFO";
+            case RIL_REQUEST_ENTER_SIM_DEPERSONALIZATION:
+                return "RIL_REQUEST_ENTER_SIM_DEPERSONALIZATION";
+
             default: return "<unknown request>";
         }
     }
@@ -5660,6 +6414,14 @@
                 return "RIL_UNSOL_KEEPALIVE_STATUS";
             case RIL_UNSOL_PHYSICAL_CHANNEL_CONFIG:
                 return "RIL_UNSOL_PHYSICAL_CHANNEL_CONFIG";
+            case RIL_UNSOL_EMERGENCY_NUMBER_LIST:
+                return "RIL_UNSOL_EMERGENCY_NUMBER_LIST";
+            case RIL_UNSOL_UICC_APPLICATIONS_ENABLEMENT_CHANGED:
+                return "RIL_UNSOL_UICC_APPLICATIONS_ENABLEMENT_CHANGED";
+            case RIL_UNSOL_REGISTRATION_FAILED:
+                return "RIL_UNSOL_REGISTRATION_FAILED";
+            case RIL_UNSOL_BARRING_INFO_CHANGED:
+                return "RIL_UNSOL_BARRING_INFO_CHANGED";
             default:
                 return "<unknown response>";
         }
@@ -5667,19 +6429,19 @@
 
     @UnsupportedAppUsage
     void riljLog(String msg) {
-        Rlog.d(RILJ_LOG_TAG, msg + (" [SUB" + mPhoneId + "]"));
+        Rlog.d(RILJ_LOG_TAG, msg + (" [PHONE" + mPhoneId + "]"));
     }
 
     void riljLoge(String msg) {
-        Rlog.e(RILJ_LOG_TAG, msg + (" [SUB" + mPhoneId + "]"));
+        Rlog.e(RILJ_LOG_TAG, msg + (" [PHONE" + mPhoneId + "]"));
     }
 
     void riljLoge(String msg, Exception e) {
-        Rlog.e(RILJ_LOG_TAG, msg + (" [SUB" + mPhoneId + "]"), e);
+        Rlog.e(RILJ_LOG_TAG, msg + (" [PHONE" + mPhoneId + "]"), e);
     }
 
     void riljLogv(String msg) {
-        Rlog.v(RILJ_LOG_TAG, msg + (" [SUB" + mPhoneId + "]"));
+        Rlog.v(RILJ_LOG_TAG, msg + (" [PHONE" + mPhoneId + "]"));
     }
 
     @UnsupportedAppUsage
@@ -5960,7 +6722,56 @@
     }
 
     /**
-     * Convert SetupDataCallResult defined in 1.0 or 1.4/types.hal into DataCallResponse
+     * Convert CellInfo defined in 1.5/types.hal to CellInfo type.
+     * @param records List of CellInfo defined in 1.5/types.hal.
+     * @return List of converted CellInfo object.
+     */
+    @VisibleForTesting
+    public static ArrayList<CellInfo> convertHalCellInfoList_1_5(
+            ArrayList<android.hardware.radio.V1_5.CellInfo> records) {
+        ArrayList<CellInfo> response = new ArrayList<>(records.size());
+
+        final long nanotime = SystemClock.elapsedRealtimeNanos();
+        for (android.hardware.radio.V1_5.CellInfo record : records) {
+            response.add(CellInfo.create(record, nanotime));
+        }
+        return response;
+    }
+
+    private static LinkAddress createLinkAddressFromString(String addressString) {
+        return createLinkAddressFromString(addressString, 0, LinkAddress.LIFETIME_UNKNOWN,
+                LinkAddress.LIFETIME_UNKNOWN);
+    }
+
+    private static LinkAddress createLinkAddressFromString(String addressString, int properties,
+            long deprecationTime, long expirationTime) {
+        addressString = addressString.trim();
+        InetAddress address = null;
+        int prefixLength = -1;
+        try {
+            String[] pieces = addressString.split("/", 2);
+            address = InetAddresses.parseNumericAddress(pieces[0]);
+            if (pieces.length == 1) {
+                prefixLength = (address instanceof Inet4Address) ? 32 : 128;
+            } else if (pieces.length == 2) {
+                prefixLength = Integer.parseInt(pieces[1]);
+            }
+        } catch (NullPointerException e) {            // Null string.
+        } catch (ArrayIndexOutOfBoundsException e) {  // No prefix length.
+        } catch (NumberFormatException e) {           // Non-numeric prefix.
+        } catch (IllegalArgumentException e) {        // Invalid IP address.
+        }
+
+        if (address == null || prefixLength == -1) {
+            throw new IllegalArgumentException("Invalid link address " + addressString);
+        }
+
+        return new LinkAddress(address, prefixLength, properties, 0,
+                deprecationTime, expirationTime);
+    }
+
+    /**
+     * Convert SetupDataCallResult defined in 1.0, 1.4, or 1.5 types.hal into DataCallResponse
      * @param dcResult setup data call result
      * @return converted DataCallResponse object
      */
@@ -5968,13 +6779,16 @@
     public static DataCallResponse convertDataCallResult(Object dcResult) {
         if (dcResult == null) return null;
 
-        int cause, suggestedRetryTime, cid, active, mtu;
+        int cause, suggestedRetryTime, cid, active, mtu, mtuV4, mtuV6;
         String ifname;
         int protocolType;
         String[] addresses = null;
         String[] dnses = null;
         String[] gateways = null;
         String[] pcscfs = null;
+
+        List<LinkAddress> laList = new ArrayList<>();
+
         if (dcResult instanceof android.hardware.radio.V1_0.SetupDataCallResult) {
             final android.hardware.radio.V1_0.SetupDataCallResult result =
                     (android.hardware.radio.V1_0.SetupDataCallResult) dcResult;
@@ -5996,7 +6810,12 @@
             if (!TextUtils.isEmpty(result.pcscf)) {
                 pcscfs = result.pcscf.split("\\s+");
             }
-            mtu = result.mtu;
+            mtu = mtuV4 = mtuV6 = result.mtu;
+            if (addresses != null) {
+                for (String address : addresses) {
+                    laList.add(createLinkAddressFromString(address));
+                }
+            }
         } else if (dcResult instanceof android.hardware.radio.V1_4.SetupDataCallResult) {
             final android.hardware.radio.V1_4.SetupDataCallResult result =
                     (android.hardware.radio.V1_4.SetupDataCallResult) dcResult;
@@ -6010,37 +6829,36 @@
             dnses = result.dnses.stream().toArray(String[]::new);
             gateways = result.gateways.stream().toArray(String[]::new);
             pcscfs = result.pcscf.stream().toArray(String[]::new);
-            mtu = result.mtu;
+            mtu = mtuV4 = mtuV6 = result.mtu;
+            if (addresses != null) {
+                for (String address : addresses) {
+                    laList.add(createLinkAddressFromString(address));
+                }
+            }
+        } else if (dcResult instanceof android.hardware.radio.V1_5.SetupDataCallResult) {
+            final android.hardware.radio.V1_5.SetupDataCallResult result =
+                    (android.hardware.radio.V1_5.SetupDataCallResult) dcResult;
+            cause = result.cause;
+            suggestedRetryTime = result.suggestedRetryTime;
+            cid = result.cid;
+            active = result.active;
+            protocolType = result.type;
+            ifname = result.ifname;
+            laList = result.addresses.stream().map(la -> createLinkAddressFromString(
+                    la.address, la.properties, la.deprecationTime, la.expirationTime))
+                    .collect(Collectors.toList());
+
+            dnses = result.dnses.stream().toArray(String[]::new);
+            gateways = result.gateways.stream().toArray(String[]::new);
+            pcscfs = result.pcscf.stream().toArray(String[]::new);
+            mtu = Math.max(result.mtuV4, result.mtuV6);
+            mtuV4 = result.mtuV4;
+            mtuV6 = result.mtuV6;
         } else {
             Rlog.e(RILJ_LOG_TAG, "Unsupported SetupDataCallResult " + dcResult);
             return null;
         }
 
-        // Process address
-        List<LinkAddress> laList = new ArrayList<>();
-        if (addresses != null) {
-            for (String address : addresses) {
-                address = address.trim();
-                if (address.isEmpty()) continue;
-
-                try {
-                    LinkAddress la;
-                    // Check if the address contains prefix length. If yes, LinkAddress
-                    // can parse that.
-                    if (address.split("/").length == 2) {
-                        la = new LinkAddress(address);
-                    } else {
-                        InetAddress ia = NetworkUtils.numericToInetAddress(address);
-                        la = new LinkAddress(ia, (ia instanceof Inet4Address) ? 32 : 128);
-                    }
-
-                    laList.add(la);
-                } catch (IllegalArgumentException e) {
-                    Rlog.e(RILJ_LOG_TAG, "Unknown address: " + address, e);
-                }
-            }
-        }
-
         // Process dns
         List<InetAddress> dnsList = new ArrayList<>();
         if (dnses != null) {
@@ -6048,7 +6866,7 @@
                 dns = dns.trim();
                 InetAddress ia;
                 try {
-                    ia = NetworkUtils.numericToInetAddress(dns);
+                    ia = InetAddresses.parseNumericAddress(dns);
                     dnsList.add(ia);
                 } catch (IllegalArgumentException e) {
                     Rlog.e(RILJ_LOG_TAG, "Unknown dns: " + dns, e);
@@ -6063,7 +6881,7 @@
                 gateway = gateway.trim();
                 InetAddress ia;
                 try {
-                    ia = NetworkUtils.numericToInetAddress(gateway);
+                    ia = InetAddresses.parseNumericAddress(gateway);
                     gatewayList.add(ia);
                 } catch (IllegalArgumentException e) {
                     Rlog.e(RILJ_LOG_TAG, "Unknown gateway: " + gateway, e);
@@ -6078,7 +6896,7 @@
                 pcscf = pcscf.trim();
                 InetAddress ia;
                 try {
-                    ia = NetworkUtils.numericToInetAddress(pcscf);
+                    ia = InetAddresses.parseNumericAddress(pcscf);
                     pcscfList.add(ia);
                 } catch (IllegalArgumentException e) {
                     Rlog.e(RILJ_LOG_TAG, "Unknown pcscf: " + pcscf, e);
@@ -6086,8 +6904,21 @@
             }
         }
 
-        return new DataCallResponse(cause, suggestedRetryTime, cid, active, protocolType, ifname,
-                laList, dnsList, gatewayList, pcscfList, mtu);
+        return new DataCallResponse.Builder()
+                .setCause(cause)
+                .setSuggestedRetryTime(suggestedRetryTime)
+                .setId(cid)
+                .setLinkStatus(active)
+                .setProtocolType(protocolType)
+                .setInterfaceName(ifname)
+                .setAddresses(laList)
+                .setDnsAddresses(dnsList)
+                .setGatewayAddresses(gatewayList)
+                .setPcscfAddresses(pcscfList)
+                .setMtu(mtu)
+                .setMtuV4(mtuV4)
+                .setMtuV6(mtuV6)
+                .build();
     }
 
     /**
diff --git a/src/java/com/android/internal/telephony/RILRequest.java b/src/java/com/android/internal/telephony/RILRequest.java
index 2d8f952..6e2bafe 100644
--- a/src/java/com/android/internal/telephony/RILRequest.java
+++ b/src/java/com/android/internal/telephony/RILRequest.java
@@ -16,15 +16,16 @@
 
 package com.android.internal.telephony;
 
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
 import android.os.AsyncResult;
 import android.os.Message;
 import android.os.SystemClock;
 import android.os.WorkSource;
 import android.os.WorkSource.WorkChain;
-import android.telephony.Rlog;
 
-import java.util.ArrayList;
+import com.android.telephony.Rlog;
+
+import java.util.List;
 import java.util.Random;
 import java.util.concurrent.atomic.AtomicInteger;
 
@@ -56,6 +57,8 @@
     String mClientId;
     // time in ms when RIL request was made
     long mStartTimeMs;
+    /** Argument list for radio HAL fallback method call */
+    Object[] mArguments;
 
     public int getSerial() {
         return mSerial;
@@ -133,6 +136,25 @@
     }
 
     /**
+     * Retrieves a new RILRequest instance from the pool and sets the clientId
+     *
+     * @param request RIL_REQUEST_*
+     * @param result sent when operation completes
+     * @param workSource WorkSource to track the client
+     * @param args The list of parameters used to call the fallback HAL method
+     * @return a RILRequest instance from the pool.
+     */
+    // @VisibleForTesting
+    public static RILRequest obtain(int request, Message result, WorkSource workSource,
+            Object... args) {
+        RILRequest rr = obtain(request, result, workSource);
+
+        rr.mArguments = args;
+
+        return rr;
+    }
+
+    /**
      * Generate a String client ID from the WorkSource.
      */
     // @VisibleForTesting
@@ -142,13 +164,13 @@
         }
 
         if (mWorkSource.size() > 0) {
-            return mWorkSource.get(0) + ":" + mWorkSource.getName(0);
+            return mWorkSource.getUid(0) + ":" + mWorkSource.getPackageName(0);
         }
 
-        final ArrayList<WorkChain> workChains = mWorkSource.getWorkChains();
+        final List<WorkChain> workChains = mWorkSource.getWorkChains();
         if (workChains != null && !workChains.isEmpty()) {
             final WorkChain workChain = workChains.get(0);
-            return workChain.getAttributionUid() + ":" + workChain.getTags()[0];
+            return workChain.toString();
         }
 
         return null;
@@ -174,6 +196,7 @@
                                 + serialString());
                     }
                 }
+                mArguments = null;
             }
         }
     }
diff --git a/src/java/com/android/internal/telephony/RadioBugDetector.java b/src/java/com/android/internal/telephony/RadioBugDetector.java
index b699ca6..9fe0172 100644
--- a/src/java/com/android/internal/telephony/RadioBugDetector.java
+++ b/src/java/com/android/internal/telephony/RadioBugDetector.java
@@ -17,13 +17,15 @@
 package com.android.internal.telephony;
 
 import android.content.Context;
-import android.content.Intent;
 import android.hardware.radio.V1_0.RadioError;
 import android.provider.Settings;
+import android.telephony.AnomalyReporter;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.telephony.Rlog;
 
 import java.util.HashMap;
+import java.util.UUID;
 
 /**
  * This class aims to detect radio bug based on wakelock timeout and system error.
@@ -31,6 +33,8 @@
  * {@hide}
  */
 public class RadioBugDetector {
+    private static final String TAG = "RadioBugDetector";
+
     /** Radio error constants */
     private static final int RADIO_BUG_NONE = 0x00;
     private static final int RADIO_BUG_REPETITIVE_WAKELOCK_TIMEOUT_ERROR = 0x01;
@@ -126,11 +130,12 @@
         if (mRadioBugStatus == RADIO_BUG_NONE) {
             mRadioBugStatus = isSystemError ? RADIO_BUG_REPETITIVE_SYSTEM_ERROR :
                     RADIO_BUG_REPETITIVE_WAKELOCK_TIMEOUT_ERROR;
-            Intent intent = new Intent(TelephonyIntents.ACTION_REPORT_RADIO_BUG);
-            intent.addFlags(Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
-            intent.putExtra(TelephonyIntents.EXTRA_SLOT_ID, mSlotId);
-            intent.putExtra(TelephonyIntents.EXTRA_RADIO_BUG_TYPE, mRadioBugStatus);
-            mContext.sendBroadcast(intent, android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE);
+            String message = "Repeated radio error " + mRadioBugStatus + " on slot " + mSlotId;
+            Rlog.d(TAG, message);
+            // Using fixed UUID to avoid duplicate bugreport notification
+            AnomalyReporter.reportAnomaly(
+                    UUID.fromString("d264ead0-3f05-11ea-b77f-2e728ce88125"),
+                    message);
         }
     }
 
diff --git a/src/java/com/android/internal/telephony/RadioCapability.java b/src/java/com/android/internal/telephony/RadioCapability.java
index 2b2615b..3475201 100644
--- a/src/java/com/android/internal/telephony/RadioCapability.java
+++ b/src/java/com/android/internal/telephony/RadioCapability.java
@@ -16,7 +16,7 @@
 
 package com.android.internal.telephony;
 
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
 import android.telephony.TelephonyManager;
 
 /**
diff --git a/src/java/com/android/internal/telephony/RadioConfig.java b/src/java/com/android/internal/telephony/RadioConfig.java
index a2d4e5b..3ee4d48 100644
--- a/src/java/com/android/internal/telephony/RadioConfig.java
+++ b/src/java/com/android/internal/telephony/RadioConfig.java
@@ -38,10 +38,10 @@
 import android.os.Registrant;
 import android.os.RemoteException;
 import android.os.WorkSource;
-import android.telephony.Rlog;
 import android.util.SparseArray;
 
 import com.android.internal.telephony.uicc.IccSlotStatus;
+import com.android.telephony.Rlog;
 
 import java.util.ArrayList;
 import java.util.Arrays;
diff --git a/src/java/com/android/internal/telephony/RadioConfigIndication.java b/src/java/com/android/internal/telephony/RadioConfigIndication.java
index 39af57b..639272c 100644
--- a/src/java/com/android/internal/telephony/RadioConfigIndication.java
+++ b/src/java/com/android/internal/telephony/RadioConfigIndication.java
@@ -18,9 +18,9 @@
 
 import android.hardware.radio.config.V1_2.IRadioConfigIndication;
 import android.os.AsyncResult;
-import android.telephony.Rlog;
 
 import com.android.internal.telephony.uicc.IccSlotStatus;
+import com.android.telephony.Rlog;
 
 import java.util.ArrayList;
 
diff --git a/src/java/com/android/internal/telephony/RadioConfigResponse.java b/src/java/com/android/internal/telephony/RadioConfigResponse.java
index 475f866..8e509de 100644
--- a/src/java/com/android/internal/telephony/RadioConfigResponse.java
+++ b/src/java/com/android/internal/telephony/RadioConfigResponse.java
@@ -22,7 +22,7 @@
 import android.hardware.radio.config.V1_2.IRadioConfigResponse;
 import android.telephony.ModemInfo;
 import android.telephony.PhoneCapability;
-import android.telephony.Rlog;
+import com.android.telephony.Rlog;
 
 import com.android.internal.telephony.uicc.IccSlotStatus;
 
diff --git a/src/java/com/android/internal/telephony/RadioIndication.java b/src/java/com/android/internal/telephony/RadioIndication.java
index 6beab09..bf8ae5f 100644
--- a/src/java/com/android/internal/telephony/RadioIndication.java
+++ b/src/java/com/android/internal/telephony/RadioIndication.java
@@ -63,6 +63,7 @@
 import static com.android.internal.telephony.RILConstants.RIL_UNSOL_STK_PROACTIVE_COMMAND;
 import static com.android.internal.telephony.RILConstants.RIL_UNSOL_STK_SESSION_END;
 import static com.android.internal.telephony.RILConstants.RIL_UNSOL_SUPP_SVC_NOTIFICATION;
+import static com.android.internal.telephony.RILConstants.RIL_UNSOL_UICC_APPLICATIONS_ENABLEMENT_CHANGED;
 import static com.android.internal.telephony.RILConstants.RIL_UNSOL_UICC_SUBSCRIPTION_STATUS_CHANGED;
 import static com.android.internal.telephony.RILConstants.RIL_UNSOL_VOICE_RADIO_TECH_CHANGED;
 import static com.android.internal.telephony.RILConstants.RIL_UNSOl_CDMA_PRL_CHANGED;
@@ -83,11 +84,15 @@
 import android.hardware.radio.V1_0.StkCcUnsolSsResult;
 import android.hardware.radio.V1_0.SuppSvcNotification;
 import android.hardware.radio.V1_2.CellConnectionStatus;
-import android.hardware.radio.V1_4.IRadioIndication;
-import android.hardware.radio.V1_4.RadioFrequencyInfo.hidl_discriminator;
+import android.hardware.radio.V1_5.IRadioIndication;
 import android.os.AsyncResult;
-import android.os.SystemProperties;
+import android.sysprop.TelephonyProperties;
+import android.telephony.Annotation.RadioPowerState;
+import android.telephony.AnomalyReporter;
+import android.telephony.BarringInfo;
+import android.telephony.CellIdentity;
 import android.telephony.CellInfo;
+import android.telephony.NetworkRegistrationInfo;
 import android.telephony.PcoData;
 import android.telephony.PhysicalChannelConfig;
 import android.telephony.ServiceState;
@@ -96,6 +101,7 @@
 import android.telephony.TelephonyManager;
 import android.telephony.data.DataCallResponse;
 import android.telephony.emergency.EmergencyNumber;
+import android.text.TextUtils;
 
 import com.android.internal.telephony.cdma.CdmaCallWaitingNotification;
 import com.android.internal.telephony.cdma.CdmaInformationRecords;
@@ -108,6 +114,7 @@
 
 import java.util.ArrayList;
 import java.util.List;
+import java.util.UUID;
 
 public class RadioIndication extends IRadioIndication.Stub {
     RIL mRil;
@@ -210,8 +217,7 @@
         result[0] = nitzTime;
         result[1] = receivedTime;
 
-        boolean ignoreNitz = SystemProperties.getBoolean(
-                TelephonyProperties.PROPERTY_IGNORE_NITZ, false);
+        boolean ignoreNitz = TelephonyProperties.ignore_nitz().orElse(false);
 
         if (ignoreNitz) {
             if (RIL.RILJ_LOGD) mRil.riljLog("ignoring UNSOL_NITZ_TIME_RECEIVED");
@@ -325,6 +331,10 @@
 
         if (RIL.RILJ_LOGD) mRil.unsljLogRet(RIL_UNSOL_EMERGENCY_NUMBER_LIST, response);
 
+        // Cache emergency number list from last indication.
+        mRil.cacheEmergencyNumberListIndication(response);
+
+        // Notify emergency number list from radio to registrants
         mRil.mEmergencyNumberListRegistrants.notifyRegistrants(
                 new AsyncResult(null, response, null));
     }
@@ -332,25 +342,20 @@
     /** Indicates current data call list. */
     public void dataCallListChanged(int indicationType,
             ArrayList<android.hardware.radio.V1_0.SetupDataCallResult> dcList) {
-        mRil.processIndication(indicationType);
-
-        if (RIL.RILJ_LOGD) mRil.unsljLogRet(RIL_UNSOL_DATA_CALL_LIST_CHANGED, dcList);
-
-        ArrayList<DataCallResponse> response = RIL.convertDataCallResultList(dcList);
-        mRil.mDataCallListChangedRegistrants.notifyRegistrants(
-                new AsyncResult(null, response, null));
+        responseDataCallListChanged(indicationType, dcList);
     }
 
     /** Indicates current data call list with radio HAL 1.4. */
     public void dataCallListChanged_1_4(int indicationType,
             ArrayList<android.hardware.radio.V1_4.SetupDataCallResult> dcList) {
-        mRil.processIndication(indicationType);
+        responseDataCallListChanged(indicationType, dcList);
 
-        if (RIL.RILJ_LOGD) mRil.unsljLogRet(RIL_UNSOL_DATA_CALL_LIST_CHANGED, dcList);
+    }
 
-        ArrayList<DataCallResponse> response = RIL.convertDataCallResultList(dcList);
-        mRil.mDataCallListChangedRegistrants.notifyRegistrants(
-                new AsyncResult(null, response, null));
+    /** Indicates current data call list with radio HAL 1.5. */
+    public void dataCallListChanged_1_5(int indicationType,
+            ArrayList<android.hardware.radio.V1_5.SetupDataCallResult> dcList) {
+        responseDataCallListChanged(indicationType, dcList);
     }
 
     public void suppSvcNotify(int indicationType, SuppSvcNotification suppSvcNotification) {
@@ -760,6 +765,29 @@
         mRil.mRilCellInfoListRegistrants.notifyRegistrants(new AsyncResult(null, response, null));
     }
 
+    /** Get unsolicited message for cellInfoList using HAL V1_5 */
+    public void cellInfoList_1_5(int indicationType,
+            ArrayList<android.hardware.radio.V1_5.CellInfo> records) {
+        mRil.processIndication(indicationType);
+
+        ArrayList<CellInfo> response = RIL.convertHalCellInfoList_1_5(records);
+
+        if (RIL.RILJ_LOGD) mRil.unsljLogRet(RIL_UNSOL_CELL_INFO_LIST, response);
+
+        mRil.mRilCellInfoListRegistrants.notifyRegistrants(new AsyncResult(null, response, null));
+    }
+
+    /** Get unsolicited message for uicc applications enablement changes. */
+    public void uiccApplicationsEnablementChanged(int indicationType, boolean enabled) {
+        mRil.processIndication(indicationType);
+
+        if (RIL.RILJ_LOGD) {
+            mRil.unsljLogRet(RIL_UNSOL_UICC_APPLICATIONS_ENABLEMENT_CHANGED, enabled);
+        }
+
+        mRil.mUiccApplicationsEnablementRegistrants.notifyResult(enabled);
+    }
+
     /** Incremental network scan results */
     public void networkScanResult(int indicationType,
                                   android.hardware.radio.V1_1.NetworkScanResult result) {
@@ -778,6 +806,12 @@
         responseNetworkScan_1_4(indicationType, result);
     }
 
+    /** Incremental network scan results with HAL V1_5 */
+    public void networkScanResult_1_5(int indicationType,
+            android.hardware.radio.V1_5.NetworkScanResult result) {
+        responseNetworkScan_1_5(indicationType, result);
+    }
+
     public void imsNetworkStateChanged(int indicationType) {
         mRil.processIndication(indicationType);
 
@@ -961,10 +995,78 @@
     }
 
     /**
-     * @param stateInt
-     * @return {@link TelephonyManager.RadioPowerState RadioPowerState}
+     * Indicate that a registration failure has occurred.
+     *
+     * @param cellIdentity a CellIdentity the CellIdentity of the Cell
+     * @param chosenPlmn a 5 or 6 digit alphanumeric string indicating the PLMN on which
+     *        registration failed
+     * @param domain the domain of the failed procedure: CS, PS, or both
+     * @param causeCode the primary failure cause code of the procedure
+     * @param additionalCauseCode an additional cause code if applicable
      */
-    private @TelephonyManager.RadioPowerState int getRadioStateFromInt(int stateInt) {
+    public void registrationFailed(int indicationType,
+            android.hardware.radio.V1_5.CellIdentity cellIdentity, String chosenPlmn,
+            @NetworkRegistrationInfo.Domain int domain,
+            int causeCode, int additionalCauseCode) {
+        mRil.processIndication(indicationType);
+
+        if (cellIdentity == null
+                || TextUtils.isEmpty(chosenPlmn)
+                || (domain & NetworkRegistrationInfo.DOMAIN_CS_PS) == 0
+                || (domain & ~NetworkRegistrationInfo.DOMAIN_CS_PS) != 0
+                || causeCode < 0 || additionalCauseCode < 0
+                || (causeCode == Integer.MAX_VALUE && additionalCauseCode == Integer.MAX_VALUE)) {
+            AnomalyReporter.reportAnomaly(
+                    UUID.fromString("f16e5703-6105-4341-9eb3-e68189156eb4"),
+                            "Invalid registrationFailed indication");
+
+            mRil.riljLoge("Invalid registrationFailed indication");
+            return;
+        }
+
+        CellIdentity ci = CellIdentity.create(cellIdentity);
+
+        mRil.mRegistrationFailedRegistrant.notifyRegistrant(
+                new AsyncResult(
+                        null,
+                        new RegistrationFailedEvent(ci, chosenPlmn, domain,
+                                causeCode, additionalCauseCode),
+                        null));
+    }
+
+    /**
+     * Indicate that BarringInfo has changed for the current cell and user.
+     *
+     * @param cellIdentity a CellIdentity the CellIdentity of the Cell
+     * @param barringInfos the updated barring information from the current cell, filtered for the
+     *        current PLMN and access class / access category.
+     */
+    public void barringInfoChanged(int indicationType,
+            android.hardware.radio.V1_5.CellIdentity cellIdentity,
+            ArrayList<android.hardware.radio.V1_5.BarringInfo> barringInfos) {
+        mRil.processIndication(indicationType);
+
+        if (cellIdentity == null || barringInfos == null) {
+            AnomalyReporter.reportAnomaly(
+                    UUID.fromString("645b16bb-c930-4c1c-9c5d-568696542e05"),
+                            "Invalid barringInfoChanged indication");
+
+            mRil.riljLoge("Invalid barringInfoChanged indication");
+            return;
+        }
+
+        CellIdentity ci = CellIdentity.create(cellIdentity);
+        BarringInfo cbi = BarringInfo.create(cellIdentity, barringInfos);
+
+        mRil.mBarringInfoChangedRegistrants.notifyRegistrants(
+                new AsyncResult(null, cbi, null));
+    }
+
+    /**
+     * @param stateInt
+     * @return {@link RadioPowerState RadioPowerState}
+     */
+    private @RadioPowerState int getRadioStateFromInt(int stateInt) {
         int state;
 
         switch(stateInt) {
@@ -994,10 +1096,10 @@
             android.hardware.radio.V1_4.PhysicalChannelConfig config) {
 
         switch (config.rfInfo.getDiscriminator()) {
-            case hidl_discriminator.range:
+            case android.hardware.radio.V1_4.RadioFrequencyInfo.hidl_discriminator.range:
                 builder.setFrequencyRange(config.rfInfo.range());
                 break;
-            case hidl_discriminator.channelNumber:
+            case android.hardware.radio.V1_4.RadioFrequencyInfo.hidl_discriminator.channelNumber:
                 builder.setChannelNumber(config.rfInfo.channelNumber());
                 break;
             default:
@@ -1085,4 +1187,24 @@
         if (RIL.RILJ_LOGD) mRil.unsljLogRet(RIL_UNSOL_NETWORK_SCAN_RESULT, nsr);
         mRil.mRilNetworkScanResultRegistrants.notifyRegistrants(new AsyncResult(null, nsr, null));
     }
+
+    private void responseNetworkScan_1_5(int indicationType,
+            android.hardware.radio.V1_5.NetworkScanResult result) {
+        mRil.processIndication(indicationType);
+
+        ArrayList<CellInfo> cellInfos = RIL.convertHalCellInfoList_1_5(result.networkInfos);
+        NetworkScanResult nsr = new NetworkScanResult(result.status, result.error, cellInfos);
+        if (RIL.RILJ_LOGD) mRil.unsljLogRet(RIL_UNSOL_NETWORK_SCAN_RESULT, nsr);
+        mRil.mRilNetworkScanResultRegistrants.notifyRegistrants(new AsyncResult(null, nsr, null));
+    }
+
+    private void responseDataCallListChanged(int indicationType, List<?> dcList) {
+        mRil.processIndication(indicationType);
+
+        if (RIL.RILJ_LOGD) mRil.unsljLogRet(RIL_UNSOL_DATA_CALL_LIST_CHANGED, dcList);
+
+        ArrayList<DataCallResponse> response = RIL.convertDataCallResultList(dcList);
+        mRil.mDataCallListChangedRegistrants.notifyRegistrants(
+                new AsyncResult(null, response, null));
+    }
 }
diff --git a/src/java/com/android/internal/telephony/RadioResponse.java b/src/java/com/android/internal/telephony/RadioResponse.java
index a36b7a6..0afbd13 100644
--- a/src/java/com/android/internal/telephony/RadioResponse.java
+++ b/src/java/com/android/internal/telephony/RadioResponse.java
@@ -34,16 +34,18 @@
 import android.hardware.radio.V1_0.SendSmsResult;
 import android.hardware.radio.V1_0.VoiceRegStateResult;
 import android.hardware.radio.V1_4.CarrierRestrictionsWithPriority;
-import android.hardware.radio.V1_4.IRadioResponse;
 import android.hardware.radio.V1_4.SimLockMultiSimPolicy;
+import android.hardware.radio.V1_5.IRadioResponse;
 import android.os.AsyncResult;
 import android.os.Message;
 import android.os.SystemClock;
 import android.service.carrier.CarrierIdentifier;
+import android.telephony.BarringInfo;
 import android.telephony.CarrierRestrictionRules;
 import android.telephony.CellInfo;
 import android.telephony.ModemActivityInfo;
 import android.telephony.NeighboringCellInfo;
+import android.telephony.NetworkScanRequest;
 import android.telephony.PhoneNumberUtils;
 import android.telephony.RadioAccessFamily;
 import android.telephony.SignalStrength;
@@ -127,6 +129,15 @@
 
     /**
      * @param responseInfo Response info struct containing response type, serial no. and error
+     * @param cardStatus ICC card status as defined by CardStatus in 1.5/types.hal
+     */
+    public void getIccCardStatusResponse_1_5(RadioResponseInfo responseInfo,
+            android.hardware.radio.V1_5.CardStatus cardStatus) {
+        responseIccCardStatus_1_5(responseInfo, cardStatus);
+    }
+
+    /**
+     * @param responseInfo Response info struct containing response type, serial no. and error
      * @param remainingAttempts Number of retries remaining, must be equal to -1 if unknown.
      */
     public void supplyIccPinForAppResponse(RadioResponseInfo responseInfo, int remainingAttempts) {
@@ -182,6 +193,18 @@
         responseInts(responseInfo, retriesRemaining);
     }
 
+
+    /**
+     * @param info Response info struct containing response type, serial no. and error
+     * @param persoType SIM Personalisation type
+     * @param remainingRetries postiive values indicates number of retries remaining,
+     * must be equal to -1 if number of retries is infinite.
+     */
+    public void supplySimDepersonalizationResponse(RadioResponseInfo info,
+            int persoType, int remainingRetries) {
+        responseInts(info, persoType, remainingRetries);
+    }
+
     /**
      * @param responseInfo Response info struct containing response type, serial no. and error
      * @param calls Current call list
@@ -329,6 +352,36 @@
 
     /**
      * @param responseInfo Response info struct containing response type, serial no. and error
+     * @param voiceRegResponse Current Voice registration response as defined by VoiceRegStateResult
+     *        in 1.5/types.hal
+     */
+    public void getVoiceRegistrationStateResponse_1_5(RadioResponseInfo responseInfo,
+            android.hardware.radio.V1_5.RegStateResult voiceRegResponse) {
+        RILRequest rr = mRil.processResponse(responseInfo);
+        if (rr == null) {
+            return;
+        }
+
+        if (responseInfo.error == RadioError.REQUEST_NOT_SUPPORTED) {
+            // Move the data needed for fallback call from rr which will be released soon
+            final int request = rr.getRequest();
+            final Message result = rr.getResult();
+
+            mRil.mRilHandler.post(() -> {
+                mRil.setCompatVersion(request, RIL.RADIO_HAL_VERSION_1_4);
+                mRil.getVoiceRegistrationState(result);
+            });
+
+            mRil.processResponseFallback(rr, responseInfo, voiceRegResponse);
+            return;
+        } else if (responseInfo.error == RadioError.NONE) {
+            sendMessageResponse(rr.mResult, voiceRegResponse);
+        }
+        mRil.processResponseDone(rr, responseInfo, voiceRegResponse);
+    }
+
+    /**
+     * @param responseInfo Response info struct containing response type, serial no. and error
      * @param dataRegResponse Current Data registration response as defined by DataRegStateResult in
      *        types.hal
      */
@@ -378,6 +431,35 @@
         }
     }
 
+    /**
+     * @param responseInfo Response info struct containing response type, serial no. and error
+     * @param dataRegResponse Current Data registration response as defined by DataRegStateResult in
+     *        1.5/types.hal
+     */
+    public void getDataRegistrationStateResponse_1_5(RadioResponseInfo responseInfo,
+            android.hardware.radio.V1_5.RegStateResult dataRegResponse) {
+        RILRequest rr = mRil.processResponse(responseInfo);
+        if (rr == null) {
+            return;
+        }
+
+        if (responseInfo.error == RadioError.REQUEST_NOT_SUPPORTED) {
+            // Move the data needed for fallback call from rr which will be released soon
+            final int request = rr.getRequest();
+            final Message result = rr.getResult();
+
+            mRil.mRilHandler.post(() -> {
+                mRil.setCompatVersion(request, RIL.RADIO_HAL_VERSION_1_4);
+                mRil.getDataRegistrationState(result);
+            });
+
+            mRil.processResponseFallback(rr, responseInfo, dataRegResponse);
+            return;
+        } else if (responseInfo.error == RadioError.NONE) {
+            sendMessageResponse(rr.mResult, dataRegResponse);
+        }
+        mRil.processResponseDone(rr, responseInfo, dataRegResponse);
+    }
 
     /**
      * @param responseInfo Response info struct containing response type, serial no. and error
@@ -446,6 +528,16 @@
 
     /**
      * @param responseInfo Response info struct containing response type, serial no. and error
+     * @param setupDataCallResult Response to data call setup as defined by setupDataCallResult in
+     *                            1.5/types.hal
+     */
+    public void setupDataCallResponse_1_5(RadioResponseInfo responseInfo,
+            android.hardware.radio.V1_5.SetupDataCallResult setupDataCallResult) {
+        responseSetupDataCall(responseInfo, setupDataCallResult);
+    }
+
+    /**
+     * @param responseInfo Response info struct containing response type, serial no. and error
      * @param iccIo ICC io operation response as defined by IccIoResult in types.hal
      */
     public void iccIOForAppResponse(RadioResponseInfo responseInfo,
@@ -596,6 +688,13 @@
     }
 
     /**
+     * @param responseInfo Response info struct containing response type, serial no. and error
+     */
+    public void setNetworkSelectionModeManualResponse_1_5(RadioResponseInfo responseInfo) {
+        responseVoid(responseInfo);
+    }
+
+    /**
      *
      * @param responseInfo Response info struct containing response type, serial no. and error
      * @param networkInfos List of network operator information as OperatorInfos defined in
@@ -612,17 +711,26 @@
      * @param responseInfo Response info struct containing response type, serial no. and error
      */
     public void startNetworkScanResponse(RadioResponseInfo responseInfo) {
-        responseScanStatus(responseInfo);
+        responseScanStatus(responseInfo, null /*fallbackHalVersion*/);
     }
 
     /**
      * The same method as startNetworkScanResponse, except disallowing error codes
-     * OPERATION_NOT_ALLOWED and REQUEST_NOT_SUPPORTED.
+     * OPERATION_NOT_ALLOWED.
      *
      * @param responseInfo Response info struct containing response type, serial no. and error
      */
     public void startNetworkScanResponse_1_4(RadioResponseInfo responseInfo) {
-        responseScanStatus(responseInfo);
+        responseScanStatus(responseInfo, null /*fallbackHalVersion*/);
+    }
+
+    /**
+     * The same method as startNetworkScanResponse_1_4.
+     *
+     * @param responseInfo Response info struct containing response type, serial no. and error
+     */
+    public void startNetworkScanResponse_1_5(RadioResponseInfo responseInfo) {
+        responseScanStatus(responseInfo, RIL.RADIO_HAL_VERSION_1_4);
     }
 
     /**
@@ -630,7 +738,7 @@
      * @param responseInfo Response info struct containing response type, serial no. and error
      */
     public void stopNetworkScanResponse(RadioResponseInfo responseInfo) {
-        responseScanStatus(responseInfo);
+        responseScanStatus(responseInfo, null /*fallbackHalVersion*/);
     }
 
     /**
@@ -708,6 +816,16 @@
         responseDataCallList(responseInfo, dataCallResultList);
     }
 
+    /**
+     * @param responseInfo Response info struct containing response type, serial no. and error
+     * @param dataCallResultList Response to get data call list as defined by setupDataCallResult in
+     *                           1.5/types.hal
+     */
+    public void getDataCallListResponse_1_5(RadioResponseInfo responseInfo,
+            ArrayList<android.hardware.radio.V1_5.SetupDataCallResult> dataCallResultList) {
+        responseDataCallList(responseInfo, dataCallResultList);
+    }
+
     public void sendOemRilRequestRawResponse(RadioResponseInfo responseInfo,
                                              ArrayList<Byte> var2) {}
 
@@ -919,6 +1037,15 @@
     }
 
     /**
+     *
+     * @param responseInfo Response info struct containing response type, serial no. and error
+     * @param sms Sms result struct as defined by SendSmsResult in types.hal
+     */
+    public void sendCdmaSMSExpectMoreResponse(RadioResponseInfo responseInfo, SendSmsResult sms) {
+        responseSms(responseInfo, sms);
+    }
+
+    /**
      * @param responseInfo Response info struct containing response type, serial no. and error
      */
     public void acknowledgeLastIncomingCdmaSmsResponse(RadioResponseInfo responseInfo) {
@@ -1124,6 +1251,16 @@
     }
 
     /**
+     * @param responseInfo Response info struct containing response type, serial no. and error.
+     * @param cellInfo List of current cell information known to radio.
+     */
+    public void getCellInfoListResponse_1_5(
+            RadioResponseInfo responseInfo,
+            ArrayList<android.hardware.radio.V1_5.CellInfo> cellInfo) {
+        responseCellInfoList_1_5(responseInfo, cellInfo);
+    }
+
+    /**
      * @param responseInfo Response info struct containing response type, serial no. and error
      */
     public void setCellInfoListRateResponse(RadioResponseInfo responseInfo) {
@@ -1138,6 +1275,13 @@
     }
 
     /**
+     * @param responseInfo Response info struct containing response type, serial no. and error
+     */
+    public void setInitialAttachApnResponse_1_5(RadioResponseInfo responseInfo) {
+        responseVoid(responseInfo);
+    }
+
+    /**
      *
      * @param responseInfo Response info struct containing response type, serial no. and error
      * @param isRegistered false = not registered, true = registered
@@ -1262,7 +1406,19 @@
     public void requestIccSimAuthenticationResponse(RadioResponseInfo responseInfo,
                                                     android.hardware.radio.V1_0.IccIoResult
                                                             result) {
-        responseICC_IOBase64(responseInfo, result);
+        RILRequest rr = mRil.processResponse(responseInfo);
+
+        if (rr != null) {
+            IccIoResult ret = new IccIoResult(
+                    result.sw1,
+                    result.sw2,
+                    TextUtils.isEmpty(result.simResponse)
+                            ? null : result.simResponse.getBytes());
+            if (responseInfo.error == RadioError.NONE) {
+                sendMessageResponse(rr.mResult, ret);
+            }
+            mRil.processResponseDone(rr, responseInfo, ret);
+        }
     }
 
     /**
@@ -1275,6 +1431,13 @@
     /**
      * @param responseInfo Response info struct containing response type, serial no. and error
      */
+    public void setDataProfileResponse_1_5(RadioResponseInfo responseInfo) {
+        responseVoid(responseInfo);
+    }
+
+    /**
+     * @param responseInfo Response info struct containing response type, serial no. and error
+     */
     public void requestShutdownResponse(RadioResponseInfo responseInfo) {
         responseVoid(responseInfo);
     }
@@ -1438,6 +1601,13 @@
     /**
      * @param responseInfo Response info struct containing response type, serial no. and error
      */
+    public void setIndicationFilterResponse_1_5(RadioResponseInfo responseInfo) {
+        responseVoid(responseInfo);
+    }
+
+    /**
+     * @param responseInfo Response info struct containing response type, serial no. and error
+     */
     public void setSimCardPowerResponse(RadioResponseInfo responseInfo) {
         responseVoid(responseInfo);
     }
@@ -1452,6 +1622,13 @@
     /**
      * @param responseInfo Response info struct containing response type, serial no. and error
      */
+    public void setSignalStrengthReportingCriteriaResponse_1_5(RadioResponseInfo responseInfo) {
+        responseVoid(responseInfo);
+    }
+
+    /**
+     * @param responseInfo Response info struct containing response type, serial no. and error
+     */
     public void setLinkCapacityReportingCriteriaResponse(RadioResponseInfo responseInfo) {
         responseVoid(responseInfo);
     }
@@ -1459,6 +1636,13 @@
     /**
      * @param responseInfo Response info struct containing response type, serial no. and error
      */
+    public void setLinkCapacityReportingCriteriaResponse_1_5(RadioResponseInfo responseInfo) {
+        responseVoid(responseInfo);
+    }
+
+    /**
+     * @param responseInfo Response info struct containing response type, serial no. and error
+     */
     public void setSimCardPowerResponse_1_1(RadioResponseInfo responseInfo) {
         responseVoid(responseInfo);
     }
@@ -1570,6 +1754,48 @@
         return iccCardStatus;
     }
 
+    private IccCardStatus convertHalCardStatus_1_5(
+            android.hardware.radio.V1_5.CardStatus cardStatus) {
+        IccCardStatus iccCardStatus = new IccCardStatus();
+        iccCardStatus.setCardState(cardStatus.base.base.base.cardState);
+        iccCardStatus.setUniversalPinState(cardStatus.base.base.base.universalPinState);
+        iccCardStatus.mGsmUmtsSubscriptionAppIndex =
+                cardStatus.base.base.base.gsmUmtsSubscriptionAppIndex;
+        iccCardStatus.mCdmaSubscriptionAppIndex =
+                cardStatus.base.base.base.cdmaSubscriptionAppIndex;
+        iccCardStatus.mImsSubscriptionAppIndex =
+                cardStatus.base.base.base.imsSubscriptionAppIndex;
+        iccCardStatus.physicalSlotIndex = cardStatus.base.base.physicalSlotId;
+        iccCardStatus.atr = cardStatus.base.base.atr;
+        iccCardStatus.iccid = cardStatus.base.base.iccid;
+        iccCardStatus.eid = cardStatus.base.eid;
+        int numApplications = cardStatus.applications.size();
+
+        // limit to maximum allowed applications
+        if (numApplications
+                > com.android.internal.telephony.uicc.IccCardStatus.CARD_MAX_APPS) {
+            numApplications =
+                    com.android.internal.telephony.uicc.IccCardStatus.CARD_MAX_APPS;
+        }
+        iccCardStatus.mApplications = new IccCardApplicationStatus[numApplications];
+        for (int i = 0; i < numApplications; i++) {
+            android.hardware.radio.V1_5.AppStatus rilAppStatus = cardStatus.applications.get(i);
+            IccCardApplicationStatus appStatus = new IccCardApplicationStatus();
+            appStatus.app_type       = appStatus.AppTypeFromRILInt(rilAppStatus.base.appType);
+            appStatus.app_state      = appStatus.AppStateFromRILInt(rilAppStatus.base.appState);
+            appStatus.perso_substate = appStatus.PersoSubstateFromRILInt(
+                    rilAppStatus.persoSubstate);
+            appStatus.aid            = rilAppStatus.base.aidPtr;
+            appStatus.app_label      = rilAppStatus.base.appLabelPtr;
+            appStatus.pin1_replaced  = rilAppStatus.base.pin1Replaced;
+            appStatus.pin1           = appStatus.PinStateFromRILInt(rilAppStatus.base.pin1);
+            appStatus.pin2           = appStatus.PinStateFromRILInt(rilAppStatus.base.pin2);
+            iccCardStatus.mApplications[i] = appStatus;
+            mRil.riljLog("IccCardApplicationStatus " + i + ":" + appStatus.toString());
+        }
+        return iccCardStatus;
+    }
+
     private void responseIccCardStatus(RadioResponseInfo responseInfo, CardStatus cardStatus) {
         RILRequest rr = mRil.processResponse(responseInfo);
 
@@ -1618,6 +1844,20 @@
         }
     }
 
+    private void responseIccCardStatus_1_5(RadioResponseInfo responseInfo,
+            android.hardware.radio.V1_5.CardStatus cardStatus) {
+        RILRequest rr = mRil.processResponse(responseInfo);
+
+        if (rr != null) {
+            IccCardStatus iccCardStatus = convertHalCardStatus_1_5(cardStatus);
+            mRil.riljLog("responseIccCardStatus: from HIDL: " + iccCardStatus);
+            if (responseInfo.error == RadioError.NONE) {
+                sendMessageResponse(rr.mResult, iccCardStatus);
+            }
+            mRil.processResponseDone(rr, responseInfo, iccCardStatus);
+        }
+    }
+
     /**
      * @param responseInfo Response info struct containing response type, serial no. and error
      */
@@ -2006,18 +2246,37 @@
         }
     }
 
-    private void responseScanStatus(RadioResponseInfo responseInfo) {
+    private void responseScanStatus(RadioResponseInfo responseInfo, HalVersion fallbackHalVersion) {
         RILRequest rr = mRil.processResponse(responseInfo);
-
-        if (rr != null) {
-            NetworkScanResult nsr = null;
-            if (responseInfo.error == RadioError.NONE) {
-                nsr = new NetworkScanResult(
-                        NetworkScanResult.SCAN_STATUS_PARTIAL, RadioError.NONE, null);
-                sendMessageResponse(rr.mResult, nsr);
-            }
-            mRil.processResponseDone(rr, responseInfo, nsr);
+        if (rr == null) {
+            return;
         }
+
+        final boolean needFallback = responseInfo.error == RadioError.REQUEST_NOT_SUPPORTED
+                && fallbackHalVersion != null && rr.mArguments != null && rr.mArguments.length > 0
+                && rr.mArguments[0] instanceof NetworkScanRequest;
+        if (needFallback) {
+            // Move the data needed for fallback call from rr which will be released soon
+            final int request = rr.getRequest();
+            final Message result = rr.getResult();
+            final NetworkScanRequest scanRequest = (NetworkScanRequest) rr.mArguments[0];
+
+            mRil.mRilHandler.post(() -> {
+                mRil.setCompatVersion(request, RIL.RADIO_HAL_VERSION_1_4);
+                mRil.startNetworkScan(scanRequest, result);
+            });
+
+            mRil.processResponseFallback(rr, responseInfo, null);
+            return;
+        }
+
+        NetworkScanResult nsr = null;
+        if (responseInfo.error == RadioError.NONE) {
+            nsr = new NetworkScanResult(
+                    NetworkScanResult.SCAN_STATUS_PARTIAL, RadioError.NONE, null);
+            sendMessageResponse(rr.mResult, nsr);
+        }
+        mRil.processResponseDone(rr, responseInfo, nsr);
     }
 
     private void responseDataCallList(RadioResponseInfo responseInfo,
@@ -2171,6 +2430,19 @@
         }
     }
 
+    private void responseCellInfoList_1_5(RadioResponseInfo responseInfo,
+            ArrayList<android.hardware.radio.V1_5.CellInfo> cellInfo) {
+        RILRequest rr = mRil.processResponse(responseInfo);
+
+        if (rr != null) {
+            ArrayList<CellInfo> ret = RIL.convertHalCellInfoList_1_5(cellInfo);
+            if (responseInfo.error == RadioError.NONE) {
+                sendMessageResponse(rr.mResult, ret);
+            }
+            mRil.processResponseDone(rr, responseInfo, ret);
+        }
+    }
+
     private void responseActivityData(RadioResponseInfo responseInfo,
                                       ActivityStatsInfo activityInfo) {
         RILRequest rr = mRil.processResponse(responseInfo);
@@ -2186,10 +2458,10 @@
                 }
                 final int rxModeTimeMs = activityInfo.rxModeTimeMs;
                 ret = new ModemActivityInfo(SystemClock.elapsedRealtime(), sleepModeTimeMs,
-                        idleModeTimeMs, txModeTimeMs, rxModeTimeMs, 0);
+                        idleModeTimeMs, txModeTimeMs, rxModeTimeMs);
             } else {
                 ret = new ModemActivityInfo(0, 0, 0, new int [ModemActivityInfo.TX_POWER_LEVELS],
-                        0, 0);
+                        0);
                 responseInfo.error = RadioError.NONE;
             }
             sendMessageResponse(rr.mResult, ret);
@@ -2211,24 +2483,6 @@
         }
     }
 
-    private void responseICC_IOBase64(RadioResponseInfo responseInfo,
-                                      android.hardware.radio.V1_0.IccIoResult result) {
-        RILRequest rr = mRil.processResponse(responseInfo);
-
-        if (rr != null) {
-            IccIoResult ret = new IccIoResult(
-                    result.sw1,
-                    result.sw2,
-                    (!(result.simResponse).equals(""))
-                            ? android.util.Base64.decode(result.simResponse,
-                            android.util.Base64.DEFAULT) : (byte[]) null);
-            if (responseInfo.error == RadioError.NONE) {
-                sendMessageResponse(rr.mResult, ret);
-            }
-            mRil.processResponseDone(rr, responseInfo, ret);
-        }
-    }
-
     private void responseRadioCapability(RadioResponseInfo responseInfo,
                                          android.hardware.radio.V1_0.RadioCapability rc) {
         RILRequest rr = mRil.processResponse(responseInfo);
@@ -2356,4 +2610,72 @@
     public void setSystemSelectionChannelsResponse(RadioResponseInfo responseInfo) {
         responseVoid(responseInfo);
     }
+
+    /**
+     * @param responseInfo Response info struct containing response type, serial no. and error.
+     */
+    public void enableUiccApplicationsResponse(RadioResponseInfo responseInfo) {
+        responseVoid(responseInfo);
+    }
+
+    /**
+     * @param responseInfo Response info struct containing response type, serial no. and error.
+     * @param enabled whether Uicc applications are enabled.
+     */
+    public void areUiccApplicationsEnabledResponse(RadioResponseInfo responseInfo,
+            boolean enabled) {
+        RILRequest rr = mRil.processResponse(responseInfo);
+
+        if (rr != null) {
+            if (responseInfo.error == RadioError.NONE) {
+                sendMessageResponse(rr.mResult, enabled);
+            }
+            mRil.processResponseDone(rr, responseInfo, enabled);
+        }
+    }
+
+    /**
+     * @param info Response info struct containing response type, serial no. and error.
+     */
+    public void setRadioPowerResponse_1_5(RadioResponseInfo info) {
+        responseVoid(info);
+    }
+
+    /**
+     * @param info Response info struct containing response type, serial no. and error.
+     */
+    public void setSystemSelectionChannelsResponse_1_5(RadioResponseInfo info) {
+        responseVoid(info);
+    }
+
+    /**
+     * @param responseInfo Response info struct containing response type, serial no. and error.
+     * @param cellIdentity CellIdentity for the barringInfos.
+     * @param barringInfos List of BarringInfo for all the barring service types.
+     */
+    public void getBarringInfoResponse(RadioResponseInfo responseInfo,
+            android.hardware.radio.V1_5.CellIdentity cellIdentity,
+            ArrayList<android.hardware.radio.V1_5.BarringInfo> barringInfos) {
+        RILRequest rr = mRil.processResponse(responseInfo);
+
+        if (rr != null) {
+            BarringInfo bi = BarringInfo.create(cellIdentity, barringInfos);
+            if (responseInfo.error == RadioError.NONE) {
+                sendMessageResponse(rr.mResult, bi);
+                // notify all registrants for the possible barring info change
+                mRil.mBarringInfoChangedRegistrants.notifyRegistrants(
+                        new AsyncResult(null, bi, null));
+            }
+            mRil.processResponseDone(rr, responseInfo, bi);
+        }
+    }
+
+    /**
+     * @param responseInfo Response info struct containing response type, serial no. and error
+     * @param sms Response to sms sent as defined by SendSmsResult in types.hal
+     */
+    public void sendCdmaSmsExpectMoreResponse(RadioResponseInfo responseInfo,
+            SendSmsResult sms) {
+        responseSms(responseInfo, sms);
+    }
 }
diff --git a/src/java/com/android/internal/telephony/RatRatcheter.java b/src/java/com/android/internal/telephony/RatRatcheter.java
index 9e9bd4f..4d9cc3e 100644
--- a/src/java/com/android/internal/telephony/RatRatcheter.java
+++ b/src/java/com/android/internal/telephony/RatRatcheter.java
@@ -20,17 +20,19 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
+import android.content.pm.PackageManager;
 import android.os.PersistableBundle;
 import android.os.UserHandle;
 import android.telephony.AccessNetworkConstants;
+import android.telephony.Annotation.NetworkType;
 import android.telephony.CarrierConfigManager;
 import android.telephony.NetworkRegistrationInfo;
-import android.telephony.Rlog;
 import android.telephony.ServiceState;
-import android.telephony.TelephonyManager.NetworkType;
 import android.util.SparseArray;
 import android.util.SparseIntArray;
 
+import com.android.telephony.Rlog;
+
 import java.util.Arrays;
 
 /**
@@ -83,8 +85,14 @@
 
         IntentFilter intentFilter = new IntentFilter();
         intentFilter.addAction(CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED);
-        phone.getContext().registerReceiverAsUser(mConfigChangedReceiver, UserHandle.ALL,
-                intentFilter, null, null);
+        try {
+            Context contextAsUser = phone.getContext().createPackageContextAsUser(
+                phone.getContext().getPackageName(), 0, UserHandle.ALL);
+            contextAsUser.registerReceiver(mConfigChangedReceiver,
+                intentFilter, null /* broadcastPermission */, null);
+        } catch (PackageManager.NameNotFoundException e) {
+            Rlog.e(LOG_TAG, "Package name not found: " + e.getMessage());
+        }
         resetRatFamilyMap();
     }
 
@@ -114,9 +122,6 @@
     /** Ratchets RATs and cell bandwidths if oldSS and newSS have the same RAT family. */
     public void ratchet(@NonNull ServiceState oldSS, @NonNull ServiceState newSS,
                         boolean locationChange) {
-        if (!locationChange && isSameRatFamily(oldSS, newSS)) {
-            updateBandwidths(oldSS.getCellBandwidths(), newSS);
-        }
         // temporarily disable rat ratchet on location change.
         if (locationChange) {
             mVoiceRatchetEnabled = false;
@@ -124,6 +129,13 @@
             return;
         }
 
+        // Different rat family, don't need rat ratchet and update cell bandwidths.
+        if (!isSameRatFamily(oldSS, newSS)) {
+            return;
+        }
+
+        updateBandwidths(oldSS.getCellBandwidths(), newSS);
+
         boolean newUsingCA = oldSS.isUsingCarrierAggregation()
                 || newSS.isUsingCarrierAggregation()
                 || newSS.getCellBandwidths().length > 1;
@@ -171,6 +183,20 @@
                             AccessNetworkConstants.TRANSPORT_TYPE_WWAN)
                             .getAccessNetworkTechnology());
 
+            // The api getAccessNetworkTechnology@NetworkRegistrationInfo always returns LTE though
+            // data rat is LTE CA. Because it uses mIsUsingCarrierAggregation to indicate whether
+            // it is LTE CA or not. However, we need its actual data rat to check if they are the
+            // same family. So convert it to LTE CA.
+            if (dataRat1 == ServiceState.RIL_RADIO_TECHNOLOGY_LTE
+                    && ss1.isUsingCarrierAggregation()) {
+                dataRat1 = ServiceState.RIL_RADIO_TECHNOLOGY_LTE_CA;
+            }
+
+            if (dataRat2 == ServiceState.RIL_RADIO_TECHNOLOGY_LTE
+                    && ss2.isUsingCarrierAggregation()) {
+                dataRat2 = ServiceState.RIL_RADIO_TECHNOLOGY_LTE_CA;
+            }
+
             if (dataRat1 == dataRat2) return true;
             if (mRatFamilyMap.get(dataRat1) == null) {
                 return false;
@@ -196,7 +222,7 @@
             final CarrierConfigManager configManager = (CarrierConfigManager)
                     mPhone.getContext().getSystemService(Context.CARRIER_CONFIG_SERVICE);
             if (configManager == null) return;
-            PersistableBundle b = configManager.getConfig();
+            PersistableBundle b = configManager.getConfigForSubId(mPhone.getSubId());
             if (b == null) return;
 
             // Reads an array of strings, eg:
diff --git a/src/java/com/android/internal/telephony/RegistrationFailedEvent.java b/src/java/com/android/internal/telephony/RegistrationFailedEvent.java
new file mode 100644
index 0000000..35807ca
--- /dev/null
+++ b/src/java/com/android/internal/telephony/RegistrationFailedEvent.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2019 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;
+
+import android.annotation.NonNull;
+import android.telephony.CellIdentity;
+import android.telephony.NetworkRegistrationInfo;
+
+/**
+ * Event fired to notify of a registration failure.
+ */
+public class RegistrationFailedEvent {
+    /** The Cell Identity of the cell on which registration failed */
+    public final CellIdentity cellIdentity;
+
+    /** The PLMN for that cell on which registration failed */
+    public final String chosenPlmn;
+
+    /** The registration domain(s) for this registration attempt */
+    @NetworkRegistrationInfo.Domain public final int domain;
+
+    /** The registration cause code */
+    public final int causeCode;
+
+    /** The additional cause code in case of a combined procedure */
+    public final int additionalCauseCode;
+
+    /** Constructor for this event */
+    public RegistrationFailedEvent(@NonNull CellIdentity cellIdentity,
+            @NonNull String chosenPlmn, int domain, int causeCode, int additionalCauseCode) {
+        this.cellIdentity = cellIdentity;
+        this.chosenPlmn = chosenPlmn;
+        this.domain = domain;
+        this.causeCode = causeCode;
+        this.additionalCauseCode = additionalCauseCode;
+    }
+
+    @Override
+    public String toString() {
+        return new StringBuilder()
+                .append("{CellIdentity=")
+                .append(cellIdentity)
+                .append(", chosenPlmn=")
+                .append(chosenPlmn)
+                .append(", domain=")
+                .append(domain)
+                .append(", causeCode=")
+                .append(causeCode)
+                .append(", additionalCauseCode=")
+                .append(additionalCauseCode)
+                .toString();
+    }
+}
diff --git a/src/java/com/android/internal/telephony/RetryManager.java b/src/java/com/android/internal/telephony/RetryManager.java
index 87d6cf5..09485a8 100644
--- a/src/java/com/android/internal/telephony/RetryManager.java
+++ b/src/java/com/android/internal/telephony/RetryManager.java
@@ -16,17 +16,19 @@
 
 package com.android.internal.telephony;
 
-import android.annotation.UnsupportedAppUsage;
+import android.annotation.Nullable;
+import android.compat.annotation.UnsupportedAppUsage;
 import android.content.Context;
-import android.os.Build;
 import android.os.PersistableBundle;
 import android.os.SystemProperties;
 import android.telephony.CarrierConfigManager;
-import android.telephony.Rlog;
 import android.telephony.data.ApnSetting;
 import android.text.TextUtils;
 import android.util.Pair;
 
+import com.android.internal.telephony.util.TelephonyUtils;
+import com.android.telephony.Rlog;
+
 import java.util.ArrayList;
 import java.util.Random;
 
@@ -332,7 +334,7 @@
         String otherConfigString = null;
 
         try {
-            if (Build.IS_DEBUGGABLE) {
+            if (TelephonyUtils.IS_DEBUGGABLE) {
                 // Using system properties is easier for testing from command line.
                 String config = SystemProperties.get("test.data_retry_config");
                 if (!TextUtils.isEmpty(config)) {
@@ -486,10 +488,9 @@
 
     /**
      * Get the next APN setting for data call setup.
-     * @return APN setting to try
+     * @return APN setting to try. {@code null} if cannot find any APN,
      */
-    public ApnSetting getNextApnSetting() {
-
+    public @Nullable ApnSetting getNextApnSetting() {
         if (mWaitingApns == null || mWaitingApns.size() == 0) {
             log("Waiting APN list is null or empty.");
             return null;
@@ -497,9 +498,10 @@
 
         // If the modem had suggested a retry delay, we should retry the current APN again
         // (up to MAX_SAME_APN_RETRY times) instead of getting the next APN setting from
-        // our own list.
-        if (mModemSuggestedDelay != NO_SUGGESTED_RETRY_DELAY &&
-                mSameApnRetryCount < MAX_SAME_APN_RETRY) {
+        // our own list. If the APN waiting list has been reset before a setup data responses
+        // arrive (i.e. mCurrentApnIndex=-1), then ignore the modem suggested retry.
+        if (mCurrentApnIndex != -1 && mModemSuggestedDelay != NO_SUGGESTED_RETRY_DELAY
+                && mSameApnRetryCount < MAX_SAME_APN_RETRY) {
             mSameApnRetryCount++;
             return mWaitingApns.get(mCurrentApnIndex);
         }
@@ -516,8 +518,10 @@
                 break;
             }
 
-            // If we've already cycled through all the APNs, that means there is no APN we can try
-            if (index == mCurrentApnIndex) return null;
+            // If all APNs have permanently failed, bail out.
+            if (mWaitingApns.stream().allMatch(ApnSetting::getPermanentFailed)) {
+                return null;
+            }
         }
 
         mCurrentApnIndex = index;
@@ -565,9 +569,8 @@
                 break;
             }
 
-            // If we've already cycled through all the APNs, that means all APNs have
-            // permanently failed
-            if (index == mCurrentApnIndex) {
+            // If all APNs have permanently failed, bail out.
+            if (mWaitingApns.stream().allMatch(ApnSetting::getPermanentFailed)) {
                 log("All APNs have permanently failed.");
                 return NO_RETRY;
             }
@@ -663,6 +666,10 @@
      * @param delay The delay in milliseconds
      */
     public void setModemSuggestedDelay(long delay) {
+        if (mCurrentApnIndex == -1) {
+            log("Waiting APN list has been reset. Ignore the value from modem.");
+            return;
+        }
         mModemSuggestedDelay = delay;
     }
 
diff --git a/src/java/com/android/internal/telephony/RilWakelockInfo.java b/src/java/com/android/internal/telephony/RilWakelockInfo.java
index 5d9e54b..a5aea58 100644
--- a/src/java/com/android/internal/telephony/RilWakelockInfo.java
+++ b/src/java/com/android/internal/telephony/RilWakelockInfo.java
@@ -17,10 +17,10 @@
 package com.android.internal.telephony;
 
 import android.annotation.TargetApi;
-import android.os.Build;
-import android.telephony.Rlog;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.telephony.util.TelephonyUtils;
+import com.android.telephony.Rlog;
 
 @TargetApi(8)
 public class RilWakelockInfo {
@@ -57,7 +57,7 @@
 
     private int validateConcurrentRequests(int concurrentRequests) {
         if(concurrentRequests <= 0) {
-            if(Build.IS_DEBUGGABLE) {
+            if (TelephonyUtils.IS_DEBUGGABLE) {
                 IllegalArgumentException e = new IllegalArgumentException(
                     "concurrentRequests should always be greater than 0.");
                 Rlog.e(LOG_TAG, e.toString());
diff --git a/src/java/com/android/internal/telephony/SMSDispatcher.java b/src/java/com/android/internal/telephony/SMSDispatcher.java
index d6d5687..c7f8c1b 100644
--- a/src/java/com/android/internal/telephony/SMSDispatcher.java
+++ b/src/java/com/android/internal/telephony/SMSDispatcher.java
@@ -17,9 +17,9 @@
 package com.android.internal.telephony;
 
 import static android.Manifest.permission.SEND_SMS_NO_CONFIRMATION;
-import static android.telephony.SmsManager.RESULT_ERROR_FDN_CHECK_FAILURE;
 import static android.telephony.SmsManager.RESULT_ERROR_GENERIC_FAILURE;
 import static android.telephony.SmsManager.RESULT_ERROR_LIMIT_EXCEEDED;
+import static android.telephony.SmsManager.RESULT_ERROR_NONE;
 import static android.telephony.SmsManager.RESULT_ERROR_NO_SERVICE;
 import static android.telephony.SmsManager.RESULT_ERROR_NULL_PDU;
 import static android.telephony.SmsManager.RESULT_ERROR_RADIO_OFF;
@@ -28,14 +28,14 @@
 
 import static com.android.internal.telephony.IccSmsInterfaceManager.SMS_MESSAGE_PERIOD_NOT_SPECIFIED;
 import static com.android.internal.telephony.IccSmsInterfaceManager.SMS_MESSAGE_PRIORITY_NOT_SPECIFIED;
+import static com.android.internal.telephony.SmsResponse.NO_ERROR_CODE;
 
-import android.annotation.Nullable;
-import android.annotation.UnsupportedAppUsage;
 import android.annotation.UserIdInt;
 import android.app.Activity;
 import android.app.AlertDialog;
 import android.app.PendingIntent;
 import android.app.PendingIntent.CanceledException;
+import android.compat.annotation.UnsupportedAppUsage;
 import android.content.ContentResolver;
 import android.content.ContentValues;
 import android.content.Context;
@@ -43,11 +43,9 @@
 import android.content.Intent;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageInfo;
-import android.content.pm.PackageItemInfo;
 import android.content.pm.PackageManager;
 import android.content.res.Resources;
 import android.database.ContentObserver;
-import android.database.sqlite.SqliteWrapper;
 import android.net.Uri;
 import android.os.AsyncResult;
 import android.os.Binder;
@@ -55,18 +53,15 @@
 import android.os.Message;
 import android.os.PersistableBundle;
 import android.os.Process;
-import android.os.RemoteException;
 import android.os.UserHandle;
 import android.provider.Settings;
 import android.provider.Telephony;
 import android.provider.Telephony.Sms;
 import android.service.carrier.CarrierMessagingService;
-import android.service.carrier.ICarrierMessagingCallback;
-import android.service.carrier.ICarrierMessagingService;
+import android.service.carrier.CarrierMessagingServiceWrapper;
+import android.service.carrier.CarrierMessagingServiceWrapper.CarrierMessagingCallbackWrapper;
 import android.telephony.CarrierConfigManager;
-import android.telephony.CarrierMessagingServiceManager;
 import android.telephony.PhoneNumberUtils;
-import android.telephony.Rlog;
 import android.telephony.ServiceState;
 import android.telephony.SmsManager;
 import android.telephony.TelephonyManager;
@@ -89,6 +84,7 @@
 import com.android.internal.telephony.cdma.sms.UserData;
 import com.android.internal.telephony.uicc.UiccCard;
 import com.android.internal.telephony.uicc.UiccController;
+import com.android.telephony.Rlog;
 
 import java.util.ArrayList;
 import java.util.HashMap;
@@ -101,6 +97,7 @@
     static final String TAG = "SMSDispatcher";    // accessed from inner class
     static final boolean DBG = false;
     private static final String SEND_NEXT_MSG_EXTRA = "SendNextMsg";
+    private static final String MESSAGE_ID_EXTRA = "MessageId";
     protected static final String MAP_KEY_PDU = "pdu";
     protected static final String MAP_KEY_SMSC = "smsc";
     protected static final String MAP_KEY_DEST_ADDR = "destAddr";
@@ -125,10 +122,13 @@
     private static final int EVENT_SEND_LIMIT_REACHED_CONFIRMATION = 4;
 
     /** Send the user confirmed SMS */
-    static final int EVENT_SEND_CONFIRMED_SMS = 5;  // accessed from inner class
+    static final int EVENT_SEND_CONFIRMED_SMS = 5; // accessed from inner class
 
     /** Don't send SMS (user did not confirm). */
-    static final int EVENT_STOP_SENDING = 7;        // accessed from inner class
+    static final int EVENT_STOP_SENDING = 6; // accessed from inner class
+
+    /** Don't send SMS for this app (User had already denied eariler.) */
+    static final int EVENT_SENDING_NOT_ALLOWED = 7;
 
     /** Confirmation required for third-party apps sending to an SMS short code. */
     private static final int EVENT_CONFIRM_SEND_TO_POSSIBLE_PREMIUM_SHORT_CODE = 8;
@@ -160,8 +160,6 @@
     private static final int MAX_SEND_RETRIES = 3;
     /** Delay before next send attempt on a failed SMS, in milliseconds. */
     private static final int SEND_RETRY_DELAY = 2000;
-    /** single part SMS */
-    private static final int SINGLE_PART_SMS = 1;
     /** Message sending queue limit */
     private static final int MO_MSG_QUEUE_LIMIT = 5;
 
@@ -290,56 +288,61 @@
             break;
 
         case EVENT_SEND_LIMIT_REACHED_CONFIRMATION:
-            handleReachSentLimit((SmsTracker)(msg.obj));
+            handleReachSentLimit((SmsTracker[]) (msg.obj));
             break;
 
         case EVENT_CONFIRM_SEND_TO_POSSIBLE_PREMIUM_SHORT_CODE:
-            handleConfirmShortCode(false, (SmsTracker)(msg.obj));
+            handleConfirmShortCode(false, (SmsTracker[]) (msg.obj));
             break;
 
         case EVENT_CONFIRM_SEND_TO_PREMIUM_SHORT_CODE:
-            handleConfirmShortCode(true, (SmsTracker)(msg.obj));
+            handleConfirmShortCode(true, (SmsTracker[]) (msg.obj));
             break;
 
         case EVENT_SEND_CONFIRMED_SMS:
         {
-            SmsTracker tracker = (SmsTracker) msg.obj;
-            if (tracker.isMultipart()) {
-                sendMultipartSms(tracker);
-            } else {
-                if (mPendingTrackerCount > 1) {
-                    tracker.mExpectMore = true;
-                } else {
-                    tracker.mExpectMore = false;
-                }
+            SmsTracker[] trackers = (SmsTracker[]) msg.obj;
+            for (SmsTracker tracker : trackers) {
                 sendSms(tracker);
             }
             mPendingTrackerCount--;
             break;
         }
 
+        case EVENT_SENDING_NOT_ALLOWED:
+        {
+            SmsTracker[] trackers = (SmsTracker[]) msg.obj;
+            Rlog.d(TAG, "SMSDispatcher: EVENT_SENDING_NOT_ALLOWED - "
+                    + "sending SHORT_CODE_NEVER_ALLOWED error code.");
+            handleSmsTrackersFailure(
+                    trackers, RESULT_ERROR_SHORT_CODE_NEVER_ALLOWED, NO_ERROR_CODE);
+            break;
+        }
+
         case EVENT_STOP_SENDING:
         {
-            SmsTracker tracker = (SmsTracker) msg.obj;
+            SmsTracker[] trackers = (SmsTracker[]) msg.obj;
+            int error;
             if (msg.arg1 == ConfirmDialogListener.SHORT_CODE_MSG) {
                 if (msg.arg2 == ConfirmDialogListener.NEVER_ALLOW) {
-                    tracker.onFailed(mContext,
-                            RESULT_ERROR_SHORT_CODE_NEVER_ALLOWED, 0/*errorCode*/);
+                    error = RESULT_ERROR_SHORT_CODE_NEVER_ALLOWED;
                     Rlog.d(TAG, "SMSDispatcher: EVENT_STOP_SENDING - "
                             + "sending SHORT_CODE_NEVER_ALLOWED error code.");
                 } else {
-                    tracker.onFailed(mContext,
-                            RESULT_ERROR_SHORT_CODE_NOT_ALLOWED, 0/*errorCode*/);
+                    error = RESULT_ERROR_SHORT_CODE_NOT_ALLOWED;
                     Rlog.d(TAG, "SMSDispatcher: EVENT_STOP_SENDING - "
                             + "sending SHORT_CODE_NOT_ALLOWED error code.");
                 }
             } else if (msg.arg1 == ConfirmDialogListener.RATE_LIMIT) {
-                tracker.onFailed(mContext, RESULT_ERROR_LIMIT_EXCEEDED, 0/*errorCode*/);
+                error = RESULT_ERROR_LIMIT_EXCEEDED;
                 Rlog.d(TAG, "SMSDispatcher: EVENT_STOP_SENDING - "
                         + "sending LIMIT_EXCEEDED error code.");
             } else {
-                Rlog.e(TAG, "SMSDispatcher: EVENT_STOP_SENDING - unexpected cases.");
+                    error = SmsManager.RESULT_UNEXPECTED_EVENT_STOP_SENDING;
+                    Rlog.e(TAG, "SMSDispatcher: EVENT_STOP_SENDING - unexpected cases.");
             }
+
+            handleSmsTrackersFailure(trackers, error, NO_ERROR_CODE);
             mPendingTrackerCount--;
             break;
         }
@@ -356,7 +359,7 @@
     /**
      * Use the carrier messaging service to send a data or text SMS.
      */
-    protected abstract class SmsSender extends CarrierMessagingServiceManager {
+    protected abstract class SmsSender extends CarrierMessagingServiceWrapper {
         protected final SmsTracker mTracker;
         // Initialized in sendSmsByCarrierApp
         protected volatile SmsSenderCallback mSenderCallback;
@@ -379,13 +382,6 @@
         }
     }
 
-    private static int getSendSmsFlag(@Nullable PendingIntent deliveryIntent) {
-        if (deliveryIntent == null) {
-            return 0;
-        }
-        return CarrierMessagingService.SEND_FLAG_REQUEST_DELIVERY_STATUS;
-    }
-
     /**
      * Use the carrier messaging service to send a text SMS.
      */
@@ -395,16 +391,21 @@
         }
 
         @Override
-        protected void onServiceReady(ICarrierMessagingService carrierMessagingService) {
+        public void onServiceReady() {
             HashMap<String, Object> map = mTracker.getData();
             String text = (String) map.get(MAP_KEY_TEXT);
 
             if (text != null) {
                 try {
-                    carrierMessagingService.sendTextSms(text, getSubId(),
-                            mTracker.mDestAddress, getSendSmsFlag(mTracker.mDeliveryIntent),
+                    sendTextSms(
+                            text,
+                            getSubId(),
+                            mTracker.mDestAddress,
+                            (mTracker.mDeliveryIntent != null)
+                                    ? CarrierMessagingService.SEND_FLAG_REQUEST_DELIVERY_STATUS
+                                    : 0,
                             mSenderCallback);
-                } catch (RemoteException e) {
+                } catch (RuntimeException e) {
                     Rlog.e(TAG, "Exception sending the SMS: " + e);
                     mSenderCallback.onSendSmsComplete(
                             CarrierMessagingService.SEND_STATUS_RETRY_ON_CARRIER_NETWORK,
@@ -427,18 +428,25 @@
         }
 
         @Override
-        protected void onServiceReady(ICarrierMessagingService carrierMessagingService) {
+        public void onServiceReady() {
             HashMap<String, Object> map = mTracker.getData();
             byte[] data = (byte[]) map.get(MAP_KEY_DATA);
             int destPort = (int) map.get(MAP_KEY_DEST_PORT);
 
             if (data != null) {
                 try {
-                    carrierMessagingService.sendDataSms(data, getSubId(),
-                            mTracker.mDestAddress, destPort,
-                            getSendSmsFlag(mTracker.mDeliveryIntent), mSenderCallback);
-                } catch (RemoteException e) {
-                    Rlog.e(TAG, "Exception sending the SMS: " + e);
+                    sendDataSms(
+                            data,
+                            getSubId(),
+                            mTracker.mDestAddress,
+                            destPort,
+                            (mTracker.mDeliveryIntent != null)
+                                    ? CarrierMessagingService.SEND_FLAG_REQUEST_DELIVERY_STATUS
+                                    : 0,
+                            mSenderCallback);
+                } catch (RuntimeException e) {
+                    Rlog.e(TAG, "Exception sending the SMS: " + e
+                            + " id: " + mTracker.mMessageId);
                     mSenderCallback.onSendSmsComplete(
                             CarrierMessagingService.SEND_STATUS_RETRY_ON_CARRIER_NETWORK,
                             0 /* messageRef */);
@@ -455,7 +463,8 @@
      * Callback for TextSmsSender and DataSmsSender from the carrier messaging service.
      * Once the result is ready, the carrier messaging service connection is disposed.
      */
-    protected final class SmsSenderCallback extends ICarrierMessagingCallback.Stub {
+    protected final class SmsSenderCallback extends
+            CarrierMessagingServiceWrapper.CarrierMessagingCallbackWrapper {
         private final SmsSender mSmsSender;
 
         public SmsSenderCallback(SmsSender smsSender) {
@@ -505,37 +514,40 @@
             return;
         }
 
-        SmsResponse smsResponse = new SmsResponse(
-                messageRef, null /* ackPdu */, -1 /* unknown error code */);
+        SmsResponse smsResponse = new SmsResponse(messageRef, null /* ackPdu */, NO_ERROR_CODE);
 
         switch (result) {
-        case CarrierMessagingService.SEND_STATUS_OK:
-            Rlog.d(TAG, "Sending SMS by IP succeeded.");
-            sendMessage(obtainMessage(EVENT_SEND_SMS_COMPLETE,
-                                      new AsyncResult(tracker,
-                                                      smsResponse,
-                                                      null /* exception*/ )));
-            break;
-        case CarrierMessagingService.SEND_STATUS_ERROR:
-            Rlog.d(TAG, "Sending SMS by IP failed.");
-            sendMessage(obtainMessage(EVENT_SEND_SMS_COMPLETE,
-                    new AsyncResult(tracker, smsResponse,
-                            new CommandException(CommandException.Error.GENERIC_FAILURE))));
-            break;
-        case CarrierMessagingService.SEND_STATUS_RETRY_ON_CARRIER_NETWORK:
-            Rlog.d(TAG, "Sending SMS by IP failed. Retry on carrier network.");
-            sendSubmitPdu(tracker);
-            break;
-        default:
-            Rlog.d(TAG, "Unknown result " + result + " Retry on carrier network.");
-            sendSubmitPdu(tracker);
+            case CarrierMessagingService.SEND_STATUS_OK:
+                Rlog.d(TAG, "Sending SMS by IP succeeded."
+                        + " id: " + tracker.mMessageId);
+                sendMessage(obtainMessage(EVENT_SEND_SMS_COMPLETE,
+                                          new AsyncResult(tracker,
+                                                          smsResponse,
+                                                          null /* exception*/)));
+                break;
+            case CarrierMessagingService.SEND_STATUS_ERROR:
+                Rlog.d(TAG, "Sending SMS by IP failed."
+                        + " id: " + tracker.mMessageId);
+                sendMessage(obtainMessage(EVENT_SEND_SMS_COMPLETE,
+                        new AsyncResult(tracker, smsResponse,
+                                new CommandException(CommandException.Error.GENERIC_FAILURE))));
+                break;
+            case CarrierMessagingService.SEND_STATUS_RETRY_ON_CARRIER_NETWORK:
+                Rlog.d(TAG, "Sending SMS by IP failed. Retry on carrier network."
+                        + " id: " + tracker.mMessageId);
+                sendSubmitPdu(tracker);
+                break;
+            default:
+                Rlog.d(TAG, "Unknown result " + result + " Retry on carrier network."
+                        + " id: " + tracker.mMessageId);
+                sendSubmitPdu(tracker);
         }
     }
 
     /**
      * Use the carrier messaging service to send a multipart text SMS.
      */
-    private final class MultipartSmsSender extends CarrierMessagingServiceManager {
+    private final class MultipartSmsSender extends CarrierMessagingServiceWrapper {
         private final List<String> mParts;
         public final SmsTracker[] mTrackers;
         // Initialized in sendSmsByCarrierApp
@@ -561,12 +573,25 @@
         }
 
         @Override
-        protected void onServiceReady(ICarrierMessagingService carrierMessagingService) {
+        public void onServiceReady() {
+            boolean statusReportRequested = false;
+            for (SmsTracker tracker : mTrackers) {
+                if (tracker.mDeliveryIntent != null) {
+                    statusReportRequested = true;
+                    break;
+                }
+            }
+
             try {
-                carrierMessagingService.sendMultipartTextSms(
-                        mParts, getSubId(), mTrackers[0].mDestAddress,
-                        getSendSmsFlag(mTrackers[0].mDeliveryIntent), mSenderCallback);
-            } catch (RemoteException e) {
+                sendMultipartTextSms(
+                        mParts,
+                        getSubId(),
+                        mTrackers[0].mDestAddress,
+                        statusReportRequested
+                                ? CarrierMessagingService.SEND_FLAG_REQUEST_DELIVERY_STATUS
+                                : 0,
+                        mSenderCallback);
+            } catch (RuntimeException e) {
                 Rlog.e(TAG, "Exception sending the SMS: " + e);
                 mSenderCallback.onSendMultipartSmsComplete(
                         CarrierMessagingService.SEND_STATUS_RETRY_ON_CARRIER_NETWORK,
@@ -579,7 +604,7 @@
      * Callback for MultipartSmsSender from the carrier messaging service.
      * Once the result is ready, the carrier messaging service connection is disposed.
      */
-    private final class MultipartSmsSenderCallback extends ICarrierMessagingCallback.Stub {
+    private final class MultipartSmsSenderCallback extends CarrierMessagingCallbackWrapper {
         private final MultipartSmsSender mSmsSender;
 
         MultipartSmsSenderCallback(MultipartSmsSender smsSender) {
@@ -634,16 +659,20 @@
         }
     }
 
-    /**
-     * Send an SMS PDU. Usually just calls {@link sendRawPdu}.
-     */
+    /** Send a single SMS PDU. */
     @UnsupportedAppUsage
     private void sendSubmitPdu(SmsTracker tracker) {
+        sendSubmitPdu(new SmsTracker[] {tracker});
+    }
+
+    /** Send a multi-part SMS PDU. Usually just calls {@link sendRawPdu}. */
+    private void sendSubmitPdu(SmsTracker[] trackers) {
         if (shouldBlockSmsForEcbm()) {
             Rlog.d(TAG, "Block SMS in Emergency Callback mode");
-            tracker.onFailed(mContext, SmsManager.RESULT_ERROR_NO_SERVICE, 0/*errorCode*/);
+            handleSmsTrackersFailure(trackers, SmsManager.RESULT_SMS_BLOCKED_DURING_EMERGENCY,
+                    NO_ERROR_CODE);
         } else {
-            sendRawPdu(tracker);
+            sendRawPdu(trackers);
         }
     }
 
@@ -672,7 +701,10 @@
         }
 
         if (ar.exception == null) {
-            if (DBG) Rlog.d(TAG, "SMS send complete. Broadcasting intent: " + sentIntent);
+            if (DBG) {
+                Rlog.d(TAG, "SMS send complete. Broadcasting intent: " + sentIntent
+                        + " id: " + tracker.mMessageId);
+            }
 
             if (tracker.mDeliveryIntent != null) {
                 // Expecting a status report.  Add it to the list.
@@ -681,7 +713,10 @@
             tracker.onSent(mContext);
             mPhone.notifySmsSent(tracker.mDestAddress);
         } else {
-            if (DBG) Rlog.d(TAG, "SMS send failed");
+            if (DBG) {
+                Rlog.d(TAG, "SMS send failed"
+                        + " id: " + tracker.mMessageId);
+            }
 
             int ss = mPhone.getServiceState().getState();
 
@@ -692,16 +727,17 @@
                 tracker.mRetryCount = MAX_SEND_RETRIES;
 
                 Rlog.d(TAG, "handleSendComplete: Skipping retry: "
-                +" isIms()="+isIms()
-                +" mRetryCount="+tracker.mRetryCount
-                +" mImsRetry="+tracker.mImsRetry
-                +" mMessageRef="+tracker.mMessageRef
-                +" SS= "+mPhone.getServiceState().getState());
+                        + " isIms()=" + isIms()
+                        + " mRetryCount=" + tracker.mRetryCount
+                        + " mImsRetry=" + tracker.mImsRetry
+                        + " mMessageRef=" + tracker.mMessageRef
+                        + " SS= " + mPhone.getServiceState().getState()
+                        + " id=" + tracker.mMessageId);
             }
 
             // if sms over IMS is not supported on data and voice is not available...
             if (!isIms() && ss != ServiceState.STATE_IN_SERVICE) {
-                tracker.onFailed(mContext, getNotInServiceError(ss), 0/*errorCode*/);
+                tracker.onFailed(mContext, getNotInServiceError(ss), NO_ERROR_CODE);
             } else if ((((CommandException)(ar.exception)).getCommandError()
                     == CommandException.Error.SMS_FAIL_RETRY) &&
                    tracker.mRetryCount < MAX_SEND_RETRIES) {
@@ -717,20 +753,66 @@
                 Message retryMsg = obtainMessage(EVENT_SEND_RETRY, tracker);
                 sendMessageDelayed(retryMsg, SEND_RETRY_DELAY);
             } else {
-                int errorCode = 0;
+                int errorCode = NO_ERROR_CODE;
                 if (ar.result != null) {
                     errorCode = ((SmsResponse)ar.result).mErrorCode;
                 }
-                int error = RESULT_ERROR_GENERIC_FAILURE;
-                if (((CommandException)(ar.exception)).getCommandError()
-                        == CommandException.Error.FDN_CHECK_FAILURE) {
-                    error = RESULT_ERROR_FDN_CHECK_FAILURE;
-                }
+                int error = rilErrorToSmsManagerResult(((CommandException) (ar.exception))
+                        .getCommandError());
                 tracker.onFailed(mContext, error, errorCode);
             }
         }
     }
 
+    private static int rilErrorToSmsManagerResult(CommandException.Error rilError) {
+        switch (rilError) {
+            case RADIO_NOT_AVAILABLE:
+                return SmsManager.RESULT_RIL_RADIO_NOT_AVAILABLE;
+            case SMS_FAIL_RETRY:
+                return SmsManager.RESULT_RIL_SMS_SEND_FAIL_RETRY;
+            case NETWORK_REJECT:
+                return SmsManager.RESULT_RIL_NETWORK_REJECT;
+            case INVALID_STATE:
+                return SmsManager.RESULT_RIL_INVALID_STATE;
+            case INVALID_ARGUMENTS:
+                return SmsManager.RESULT_RIL_INVALID_ARGUMENTS;
+            case NO_MEMORY:
+                return SmsManager.RESULT_RIL_NO_MEMORY;
+            case REQUEST_RATE_LIMITED:
+                return SmsManager.RESULT_RIL_REQUEST_RATE_LIMITED;
+            case INVALID_SMS_FORMAT:
+                return SmsManager.RESULT_RIL_INVALID_SMS_FORMAT;
+            case SYSTEM_ERR:
+                return SmsManager.RESULT_RIL_SYSTEM_ERR;
+            case ENCODING_ERR:
+                return SmsManager.RESULT_RIL_ENCODING_ERR;
+            case MODEM_ERR:
+                return SmsManager.RESULT_RIL_MODEM_ERR;
+            case NETWORK_ERR:
+                return SmsManager.RESULT_RIL_NETWORK_ERR;
+            case INTERNAL_ERR:
+                return SmsManager.RESULT_RIL_INTERNAL_ERR;
+            case REQUEST_NOT_SUPPORTED:
+                return SmsManager.RESULT_RIL_REQUEST_NOT_SUPPORTED;
+            case INVALID_MODEM_STATE:
+                return SmsManager.RESULT_RIL_INVALID_MODEM_STATE;
+            case NETWORK_NOT_READY:
+                return SmsManager.RESULT_RIL_NETWORK_NOT_READY;
+            case OPERATION_NOT_ALLOWED:
+                return SmsManager.RESULT_RIL_OPERATION_NOT_ALLOWED;
+            case NO_RESOURCES:
+                return SmsManager.RESULT_RIL_NO_RESOURCES;
+            case REQUEST_CANCELLED:
+                return SmsManager.RESULT_RIL_CANCELLED;
+            case SIM_ABSENT:
+                return SmsManager.RESULT_RIL_SIM_ABSENT;
+            case FDN_CHECK_FAILURE:
+                return SmsManager.RESULT_ERROR_FDN_CHECK_FAILURE;
+            default:
+                return RESULT_ERROR_GENERIC_FAILURE;
+        }
+    }
+
     /**
      * Handles outbound message when the phone is not in service.
      *
@@ -802,7 +884,7 @@
             SmsTracker tracker = getSmsTracker(callingPackage, map, sentIntent, deliveryIntent,
                     getFormat(), null /*messageUri*/, false /*expectMore*/,
                     null /*fullMessageText*/, false /*isText*/,
-                    true /*persistMessage*/, isForVvm);
+                    true /*persistMessage*/, isForVvm, 0L /* messageId */);
 
             if (!sendSmsByCarrierApp(true /* isDataSms */, tracker)) {
                 sendSubmitPdu(tracker);
@@ -857,25 +939,29 @@
      *  Validity Period(Minimum) -> 5 mins
      *  Validity Period(Maximum) -> 635040 mins(i.e.63 weeks).
      *  Any Other values included Negative considered as Invalid Validity Period of the message.
+     * @param messageId An id that uniquely identifies the message requested to be sent.
+     *                 Used for logging and diagnostics purposes. The id may be NULL.
      */
     public void sendText(String destAddr, String scAddr, String text,
                          PendingIntent sentIntent, PendingIntent deliveryIntent, Uri messageUri,
                          String callingPkg, boolean persistMessage, int priority,
-                         boolean expectMore, int validityPeriod, boolean isForVvm) {
-        Rlog.d(TAG, "sendText");
+                         boolean expectMore, int validityPeriod, boolean isForVvm,
+                         long messageId) {
+        Rlog.d(TAG, "sendText id: " + messageId);
         SmsMessageBase.SubmitPduBase pdu = getSubmitPdu(
                 scAddr, destAddr, text, (deliveryIntent != null), null, priority, validityPeriod);
         if (pdu != null) {
             HashMap map = getSmsTrackerMap(destAddr, scAddr, text, pdu);
             SmsTracker tracker = getSmsTracker(callingPkg, map, sentIntent, deliveryIntent,
                     getFormat(), messageUri, expectMore, text, true /*isText*/,
-                    persistMessage, priority, validityPeriod, isForVvm);
+                    persistMessage, priority, validityPeriod, isForVvm, messageId);
 
             if (!sendSmsByCarrierApp(false /* isDataSms */, tracker)) {
                 sendSubmitPdu(tracker);
             }
         } else {
-            Rlog.e(TAG, "SmsDispatcher.sendText(): getSubmitPdu() returned null");
+            Rlog.e(TAG, "SmsDispatcher.sendText(): getSubmitPdu() returned null" + " id: "
+                    + messageId);
             triggerSentIntentForFailure(sentIntent);
         }
     }
@@ -883,7 +969,7 @@
     private void triggerSentIntentForFailure(PendingIntent sentIntent) {
         if (sentIntent != null) {
             try {
-                sentIntent.send(SmsManager.RESULT_ERROR_GENERIC_FAILURE);
+                sentIntent.send(RESULT_ERROR_GENERIC_FAILURE);
             } catch (CanceledException ex) {
                 Rlog.e(TAG, "Intent has been canceled!");
             }
@@ -985,7 +1071,8 @@
     public void sendMultipartText(String destAddr, String scAddr,
             ArrayList<String> parts, ArrayList<PendingIntent> sentIntents,
             ArrayList<PendingIntent> deliveryIntents, Uri messageUri, String callingPkg,
-            boolean persistMessage, int priority, boolean expectMore, int validityPeriod) {
+            boolean persistMessage, int priority, boolean expectMore, int validityPeriod,
+            long messageId) {
         final String fullMessageText = getMultipartMessageText(parts);
         int refNumber = getNextConcatenatedRef() & 0x00FF;
         int encoding = SmsConstants.ENCODING_UNKNOWN;
@@ -1047,7 +1134,7 @@
                 getNewSubmitPduTracker(callingPkg, destAddr, scAddr, parts.get(i), smsHeader,
                         encoding, sentIntent, deliveryIntent, (i == (msgCount - 1)),
                         unsentPartCount, anyPartFailed, messageUri,
-                        fullMessageText, priority, expectMore, validityPeriod);
+                        fullMessageText, priority, expectMore, validityPeriod, messageId);
             if (trackers[i] == null) {
                 triggerSentIntentForFailure(sentIntents);
                 return;
@@ -1057,18 +1144,25 @@
 
         String carrierPackage = getCarrierAppPackageName();
         if (carrierPackage != null) {
-            Rlog.d(TAG, "Found carrier package.");
+            Rlog.d(TAG, "Found carrier package."
+                    + " id: " + getMultiTrackermessageId(trackers));
             MultipartSmsSender smsSender = new MultipartSmsSender(parts, trackers);
             smsSender.sendSmsByCarrierApp(carrierPackage,
                     new MultipartSmsSenderCallback(smsSender));
         } else {
-            Rlog.v(TAG, "No carrier package.");
-            for (SmsTracker tracker : trackers) {
-                sendSubmitPdu(tracker);
-            }
+            Rlog.v(TAG, "No carrier package."
+                    + " id: " + getMultiTrackermessageId(trackers));
+            sendSubmitPdu(trackers);
         }
     }
 
+    private long getMultiTrackermessageId(SmsTracker[] trackers) {
+        if (trackers.length == 0) {
+            return 0L;
+        }
+        return trackers[0].mMessageId;
+    }
+
     /**
      * Create a new SubmitPdu and return the SMS tracker.
      */
@@ -1076,7 +1170,8 @@
             String scAddress, String message, SmsHeader smsHeader, int encoding,
             PendingIntent sentIntent, PendingIntent deliveryIntent, boolean lastPart,
             AtomicInteger unsentPartCount, AtomicBoolean anyPartFailed, Uri messageUri,
-            String fullMessageText, int priority, boolean expectMore, int validityPeriod) {
+            String fullMessageText, int priority, boolean expectMore, int validityPeriod,
+            long messageId) {
         if (isCdmaMo()) {
             UserData uData = new UserData();
             uData.payloadStr = message;
@@ -1105,10 +1200,11 @@
                 return getSmsTracker(callingPackage, map, sentIntent, deliveryIntent,
                         getFormat(), unsentPartCount, anyPartFailed, messageUri, smsHeader,
                         (!lastPart || expectMore), fullMessageText, true /*isText*/,
-                        true /*persistMessage*/, priority, validityPeriod, false /* isForVvm */);
+                        true /*persistMessage*/, priority, validityPeriod, false /* isForVvm */,
+                        messageId);
             } else {
                 Rlog.e(TAG, "CdmaSMSDispatcher.getNewSubmitPduTracker(): getSubmitPdu() returned "
-                        + "null");
+                        + "null" + " id: " + messageId);
                 return null;
             }
         } else {
@@ -1123,18 +1219,19 @@
                 return getSmsTracker(callingPackage, map, sentIntent,
                         deliveryIntent, getFormat(), unsentPartCount, anyPartFailed, messageUri,
                         smsHeader, (!lastPart || expectMore), fullMessageText, true /*isText*/,
-                        false /*persistMessage*/, priority, validityPeriod, false /* isForVvm */);
+                        false /*persistMessage*/, priority, validityPeriod, false /* isForVvm */,
+                        messageId);
             } else {
                 Rlog.e(TAG, "GsmSMSDispatcher.getNewSubmitPduTracker(): getSubmitPdu() returned "
-                        + "null");
+                        + "null" + " id: " + messageId);
                 return null;
             }
         }
     }
 
     /**
-     * Send an SMS
-     * @param tracker will contain:
+     * Send a single or a multi-part SMS
+     * @param trackers each tracker will contain:
      * -smsc the SMSC to send the message through, or NULL for the
      *  default SMSC
      * -pdu the raw PDU to send
@@ -1155,65 +1252,76 @@
      * -param destAddr the destination phone number (for short code confirmation)
      */
     @VisibleForTesting
-    public void sendRawPdu(SmsTracker tracker) {
-        HashMap map = tracker.getData();
-        byte pdu[] = (byte[]) map.get(MAP_KEY_PDU);
-
+    public void sendRawPdu(SmsTracker[] trackers) {
+        int error = RESULT_ERROR_NONE;
+        PackageInfo appInfo = null;
         if (mSmsSendDisabled) {
             Rlog.e(TAG, "Device does not support sending sms.");
-            tracker.onFailed(mContext, RESULT_ERROR_NO_SERVICE, 0/*errorCode*/);
-            return;
+            error = RESULT_ERROR_NO_SERVICE;
+        } else {
+            for (SmsTracker tracker : trackers) {
+                if (tracker.getData().get(MAP_KEY_PDU) == null) {
+                    Rlog.e(TAG, "Empty PDU");
+                    error = RESULT_ERROR_NULL_PDU;
+                    break;
+                }
+            }
+
+            if (error == RESULT_ERROR_NONE) {
+                UserHandle userHandle = UserHandle.of(trackers[0].mUserId);
+                PackageManager pm = mContext.createContextAsUser(userHandle, 0).getPackageManager();
+
+                try {
+                    // Get package info via packagemanager
+                    appInfo =
+                            pm.getPackageInfo(
+                                    trackers[0].getAppPackageName(),
+                                    PackageManager.GET_SIGNATURES);
+                } catch (PackageManager.NameNotFoundException e) {
+                    Rlog.e(TAG, "Can't get calling app package info: refusing to send SMS"
+                            + " id: " + getMultiTrackermessageId(trackers));
+                    error = RESULT_ERROR_GENERIC_FAILURE;
+                }
+            }
         }
 
-        if (pdu == null) {
-            Rlog.e(TAG, "Empty PDU");
-            tracker.onFailed(mContext, RESULT_ERROR_NULL_PDU, 0/*errorCode*/);
-            return;
-        }
-
-        String packageName = tracker.getAppPackageName();
-        PackageManager pm = mContext.getPackageManager();
-
-        // Get package info via packagemanager
-        PackageInfo appInfo;
-        try {
-            appInfo = pm.getPackageInfoAsUser(
-                    packageName, PackageManager.GET_SIGNATURES, tracker.mUserId);
-        } catch (PackageManager.NameNotFoundException e) {
-            Rlog.e(TAG, "Can't get calling app package info: refusing to send SMS");
-            tracker.onFailed(mContext, RESULT_ERROR_GENERIC_FAILURE, 0/*errorCode*/);
+        if (error != RESULT_ERROR_NONE) {
+            handleSmsTrackersFailure(trackers, error, NO_ERROR_CODE);
             return;
         }
 
         // checkDestination() returns true if the destination is not a premium short code or the
         // sending app is approved to send to short codes. Otherwise, a message is sent to our
         // handler with the SmsTracker to request user confirmation before sending.
-        if (checkDestination(tracker)) {
+        if (checkDestination(trackers)) {
             // check for excessive outgoing SMS usage by this app
-            if (!mSmsDispatchersController.getUsageMonitor().check(
-                    appInfo.packageName, SINGLE_PART_SMS)) {
-                sendMessage(obtainMessage(EVENT_SEND_LIMIT_REACHED_CONFIRMATION, tracker));
+            if (!mSmsDispatchersController
+                    .getUsageMonitor()
+                    .check(appInfo.packageName, trackers.length)) {
+                sendMessage(obtainMessage(EVENT_SEND_LIMIT_REACHED_CONFIRMATION, trackers));
                 return;
             }
 
-            sendSms(tracker);
+            for (SmsTracker tracker : trackers) {
+                sendSms(tracker);
+            }
         }
 
-        if (PhoneNumberUtils.isLocalEmergencyNumber(mContext, tracker.mDestAddress)) {
+        if (PhoneNumberUtils.isLocalEmergencyNumber(mContext, trackers[0].mDestAddress)) {
             new AsyncEmergencyContactNotifier(mContext).execute();
         }
     }
 
     /**
-     * Check if destination is a potential premium short code and sender is not pre-approved to
-     * send to short codes.
+     * Check if destination is a potential premium short code and sender is not pre-approved to send
+     * to short codes.
      *
-     * @param tracker the tracker for the SMS to send
+     * @param trackers the trackers for a single or a multi-part SMS to send
      * @return true if the destination is approved; false if user confirmation event was sent
      */
-    boolean checkDestination(SmsTracker tracker) {
+    boolean checkDestination(SmsTracker[] trackers) {
         if (mContext.checkCallingOrSelfPermission(SEND_SMS_NO_CONFIRMATION)
-                == PackageManager.PERMISSION_GRANTED || tracker.mIsForVvm) {
+                == PackageManager.PERMISSION_GRANTED || trackers[0].mIsForVvm) {
             return true;            // app is pre-approved to send to short codes
         } else {
             int rule = mPremiumSmsRule.get();
@@ -1222,26 +1330,34 @@
                 String simCountryIso =
                         mTelephonyManager.getSimCountryIsoForPhone(mPhone.getPhoneId());
                 if (simCountryIso == null || simCountryIso.length() != 2) {
-                    Rlog.e(TAG, "Can't get SIM country Iso: trying network country Iso");
+                    Rlog.e(TAG, "Can't get SIM country Iso: trying network country Iso"
+                            + " id: " + getMultiTrackermessageId(trackers));
                     simCountryIso =
-                            mTelephonyManager.getNetworkCountryIsoForPhone(mPhone.getPhoneId());
+                            mTelephonyManager.getNetworkCountryIso(mPhone.getPhoneId());
                 }
 
-                smsCategory = mSmsDispatchersController.getUsageMonitor().checkDestination(
-                        tracker.mDestAddress, simCountryIso);
+                smsCategory =
+                        mSmsDispatchersController
+                                .getUsageMonitor()
+                                .checkDestination(trackers[0].mDestAddress, simCountryIso);
             }
             if (rule == PREMIUM_RULE_USE_NETWORK || rule == PREMIUM_RULE_USE_BOTH) {
                 String networkCountryIso =
-                        mTelephonyManager.getNetworkCountryIsoForPhone(mPhone.getPhoneId());
+                        mTelephonyManager.getNetworkCountryIso(mPhone.getPhoneId());
                 if (networkCountryIso == null || networkCountryIso.length() != 2) {
-                    Rlog.e(TAG, "Can't get Network country Iso: trying SIM country Iso");
+                    Rlog.e(TAG, "Can't get Network country Iso: trying SIM country Iso"
+                            + " id: " + getMultiTrackermessageId(trackers));
                     networkCountryIso =
                             mTelephonyManager.getSimCountryIsoForPhone(mPhone.getPhoneId());
                 }
 
-                smsCategory = SmsUsageMonitor.mergeShortCodeCategories(smsCategory,
-                        mSmsDispatchersController.getUsageMonitor().checkDestination(
-                                tracker.mDestAddress, networkCountryIso));
+                smsCategory =
+                        SmsUsageMonitor.mergeShortCodeCategories(
+                                smsCategory,
+                                mSmsDispatchersController
+                                        .getUsageMonitor()
+                                        .checkDestination(
+                                                trackers[0].mDestAddress, networkCountryIso));
             }
 
             if (smsCategory == SmsManager.SMS_CATEGORY_NOT_SHORT_CODE
@@ -1252,14 +1368,16 @@
 
             // Do not allow any premium sms during SuW
             if (Settings.Global.getInt(mResolver, Settings.Global.DEVICE_PROVISIONED, 0) == 0) {
-                Rlog.e(TAG, "Can't send premium sms during Setup Wizard");
+                Rlog.e(TAG, "Can't send premium sms during Setup Wizard"
+                        + " id: " + getMultiTrackermessageId(trackers));
                 return false;
             }
 
             // Wait for user confirmation unless the user has set permission to always allow/deny
             int premiumSmsPermission =
-                    mSmsDispatchersController.getUsageMonitor().getPremiumSmsPermission(
-                    tracker.getAppPackageName());
+                    mSmsDispatchersController
+                            .getUsageMonitor()
+                            .getPremiumSmsPermission(trackers[0].getAppPackageName());
             if (premiumSmsPermission == SmsUsageMonitor.PREMIUM_SMS_PERMISSION_UNKNOWN) {
                 // First time trying to send to premium SMS.
                 premiumSmsPermission = SmsUsageMonitor.PREMIUM_SMS_PERMISSION_ASK_USER;
@@ -1267,14 +1385,14 @@
 
             switch (premiumSmsPermission) {
                 case SmsUsageMonitor.PREMIUM_SMS_PERMISSION_ALWAYS_ALLOW:
-                    Rlog.d(TAG, "User approved this app to send to premium SMS");
+                    Rlog.d(TAG, "User approved this app to send to premium SMS"
+                            + " id: " + getMultiTrackermessageId(trackers));
                     return true;
 
                 case SmsUsageMonitor.PREMIUM_SMS_PERMISSION_NEVER_ALLOW:
-                    Rlog.w(TAG, "User denied this app from sending to premium SMS");
-                    Message msg = obtainMessage(EVENT_STOP_SENDING, tracker);
-                    msg.arg1 = ConfirmDialogListener.SHORT_CODE_MSG;
-                    msg.arg2 = ConfirmDialogListener.NEVER_ALLOW;
+                    Rlog.w(TAG, "User denied this app from sending to premium SMS"
+                            + " id: " + getMultiTrackermessageId(trackers));
+                    Message msg = obtainMessage(EVENT_SENDING_NOT_ALLOWED, trackers);
                     sendMessage(msg);
                     return false;   // reject this message
 
@@ -1286,23 +1404,27 @@
                     } else {
                         event = EVENT_CONFIRM_SEND_TO_PREMIUM_SHORT_CODE;
                     }
-                    sendMessage(obtainMessage(event, tracker));
+                    sendMessage(obtainMessage(event, trackers));
                     return false;   // wait for user confirmation
             }
         }
     }
 
     /**
-     * Deny sending an SMS if the outgoing queue limit is reached. Used when the message
-     * must be confirmed by the user due to excessive usage or potential premium SMS detected.
-     * @param tracker the SmsTracker for the message to send
+     * Deny sending a single or a multi-part SMS if the outgoing queue limit is reached. Used when
+     * the message must be confirmed by the user due to excessive usage or potential premium SMS
+     * detected.
+     *
+     * @param trackers the SmsTracker array for the message to send
      * @return true if the message was denied; false to continue with send confirmation
      */
-    private boolean denyIfQueueLimitReached(SmsTracker tracker) {
+    private boolean denyIfQueueLimitReached(SmsTracker[] trackers) {
+        // one SmsTracker array is treated as one message for checking queue limit.
         if (mPendingTrackerCount >= MO_MSG_QUEUE_LIMIT) {
             // Deny sending message when the queue limit is reached.
-            Rlog.e(TAG, "Denied because queue limit reached");
-            tracker.onFailed(mContext, RESULT_ERROR_LIMIT_EXCEEDED, 0/*errorCode*/);
+            Rlog.e(TAG, "Denied because queue limit reached"
+                    + " id: " + getMultiTrackermessageId(trackers));
+            handleSmsTrackersFailure(trackers, RESULT_ERROR_LIMIT_EXCEEDED, NO_ERROR_CODE);
             return true;
         }
         mPendingTrackerCount++;
@@ -1317,10 +1439,9 @@
     private CharSequence getAppLabel(String appPackage, @UserIdInt int userId) {
         PackageManager pm = mContext.getPackageManager();
         try {
-            ApplicationInfo appInfo = pm.getApplicationInfoAsUser(appPackage, 0, userId);
-            return appInfo.loadSafeLabel(pm, PackageItemInfo.DEFAULT_MAX_LABEL_SIZE_PX,
-                    PackageItemInfo.SAFE_LABEL_FLAG_TRIM
-                            | PackageItemInfo.SAFE_LABEL_FLAG_FIRST_LINE);
+            ApplicationInfo appInfo = pm.getApplicationInfoAsUser(appPackage, 0,
+                UserHandle.getUserHandleForUid(userId));
+            return appInfo.loadSafeLabel(pm);
         } catch (PackageManager.NameNotFoundException e) {
             Rlog.e(TAG, "PackageManager Name Not Found for package " + appPackage);
             return appPackage;  // fall back to package name if we can't get app label
@@ -1329,20 +1450,21 @@
 
     /**
      * Post an alert when SMS needs confirmation due to excessive usage.
-     * @param tracker an SmsTracker for the current message.
+     *
+     * @param trackers the SmsTracker array for the current message.
      */
-    protected void handleReachSentLimit(SmsTracker tracker) {
-        if (denyIfQueueLimitReached(tracker)) {
+    protected void handleReachSentLimit(SmsTracker[] trackers) {
+        if (denyIfQueueLimitReached(trackers)) {
             return;     // queue limit reached; error was returned to caller
         }
 
-        CharSequence appLabel = getAppLabel(tracker.getAppPackageName(), tracker.mUserId);
+        CharSequence appLabel = getAppLabel(trackers[0].getAppPackageName(), trackers[0].mUserId);
         Resources r = Resources.getSystem();
         Spanned messageText = Html.fromHtml(r.getString(R.string.sms_control_message, appLabel));
 
         // Construct ConfirmDialogListenter for Rate Limit handling
-        ConfirmDialogListener listener = new ConfirmDialogListener(tracker, null,
-                ConfirmDialogListener.RATE_LIMIT);
+        ConfirmDialogListener listener =
+                new ConfirmDialogListener(trackers, null, ConfirmDialogListener.RATE_LIMIT);
 
         AlertDialog d = new AlertDialog.Builder(mContext)
                 .setTitle(R.string.sms_control_title)
@@ -1359,12 +1481,13 @@
 
     /**
      * Post an alert for user confirmation when sending to a potential short code.
+     *
      * @param isPremium true if the destination is known to be a premium short code
-     * @param tracker the SmsTracker for the current message.
+     * @param trackers the SmsTracker array for the current message.
      */
     @UnsupportedAppUsage
-    protected void handleConfirmShortCode(boolean isPremium, SmsTracker tracker) {
-        if (denyIfQueueLimitReached(tracker)) {
+    protected void handleConfirmShortCode(boolean isPremium, SmsTracker[] trackers) {
+        if (denyIfQueueLimitReached(trackers)) {
             return;     // queue limit reached; error was returned to caller
         }
 
@@ -1375,20 +1498,26 @@
             detailsId = R.string.sms_short_code_details;
         }
 
-        CharSequence appLabel = getAppLabel(tracker.getAppPackageName(), tracker.mUserId);
+        CharSequence appLabel = getAppLabel(trackers[0].getAppPackageName(), trackers[0].mUserId);
         Resources r = Resources.getSystem();
-        Spanned messageText = Html.fromHtml(r.getString(R.string.sms_short_code_confirm_message,
-                appLabel, tracker.mDestAddress));
+        Spanned messageText =
+                Html.fromHtml(
+                        r.getString(
+                                R.string.sms_short_code_confirm_message,
+                                appLabel,
+                                trackers[0].mDestAddress));
 
         LayoutInflater inflater = (LayoutInflater) mContext.getSystemService(
                 Context.LAYOUT_INFLATER_SERVICE);
         View layout = inflater.inflate(R.layout.sms_short_code_confirmation_dialog, null);
 
         // Construct ConfirmDialogListenter for short code message sending
-        ConfirmDialogListener listener = new ConfirmDialogListener(tracker,
-                (TextView) layout.findViewById(R.id.sms_short_code_remember_undo_instruction),
-                ConfirmDialogListener.SHORT_CODE_MSG);
-
+        ConfirmDialogListener listener =
+                new ConfirmDialogListener(
+                        trackers,
+                        (TextView)
+                                layout.findViewById(R.id.sms_short_code_remember_undo_instruction),
+                        ConfirmDialogListener.SHORT_CODE_MSG);
 
         TextView messageView = (TextView) layout.findViewById(R.id.sms_short_code_confirm_message);
         messageView.setText(messageText);
@@ -1435,47 +1564,15 @@
         if (mSmsDispatchersController != null) {
             mSmsDispatchersController.sendRetrySms(tracker);
         } else {
-            Rlog.e(TAG, mSmsDispatchersController + " is null. Retry failed");
+            Rlog.e(TAG, mSmsDispatchersController + " is null. Retry failed"
+                    + " id: " + tracker.mMessageId);
         }
     }
 
-    /**
-     * Send the multi-part SMS based on multipart Sms tracker
-     *
-     * @param tracker holds the multipart Sms tracker ready to be sent
-     */
-    @UnsupportedAppUsage
-    private void sendMultipartSms(SmsTracker tracker) {
-        ArrayList<String> parts;
-        ArrayList<PendingIntent> sentIntents;
-        ArrayList<PendingIntent> deliveryIntents;
-
-        HashMap<String, Object> map = tracker.getData();
-
-        String destinationAddress = (String) map.get("destination");
-        String scAddress = (String) map.get("scaddress");
-
-        parts = (ArrayList<String>) map.get("parts");
-        sentIntents = (ArrayList<PendingIntent>) map.get("sentIntents");
-        deliveryIntents = (ArrayList<PendingIntent>) map.get("deliveryIntents");
-
-        // check if in service
-        int ss = mPhone.getServiceState().getState();
-        // if sms over IMS is not supported on data and voice is not available...
-        if (!isIms() && ss != ServiceState.STATE_IN_SERVICE) {
-            for (int i = 0, count = parts.size(); i < count; i++) {
-                PendingIntent sentIntent = null;
-                if (sentIntents != null && sentIntents.size() > i) {
-                    sentIntent = sentIntents.get(i);
-                }
-                handleNotInService(ss, sentIntent);
-            }
-            return;
+    private void handleSmsTrackersFailure(SmsTracker[] trackers, int error, int errorCode) {
+        for (SmsTracker tracker : trackers) {
+            tracker.onFailed(mContext, error, errorCode);
         }
-
-        sendMultipartText(destinationAddress, scAddress, parts, sentIntents, deliveryIntents,
-                null/*messageUri*/, null/*callingPkg*/, tracker.mPersistMessage, tracker.mPriority,
-                tracker.mExpectMore, tracker.mValidityPeriod);
     }
 
     /**
@@ -1538,12 +1635,14 @@
 
         private final boolean mIsForVvm;
 
+        public final long mMessageId;
+
         private SmsTracker(HashMap<String, Object> data, PendingIntent sentIntent,
                 PendingIntent deliveryIntent, PackageInfo appInfo, String destAddr, String format,
                 AtomicInteger unsentPartCount, AtomicBoolean anyPartFailed, Uri messageUri,
                 SmsHeader smsHeader, boolean expectMore, String fullMessageText, int subId,
                 boolean isText, boolean persistMessage, int userId, int priority,
-                int validityPeriod, boolean isForVvm) {
+                int validityPeriod, boolean isForVvm, long messageId) {
             mData = data;
             mSentIntent = sentIntent;
             mDeliveryIntent = deliveryIntent;
@@ -1567,15 +1666,7 @@
             mPriority = priority;
             mValidityPeriod = validityPeriod;
             mIsForVvm = isForVvm;
-        }
-
-        /**
-         * Returns whether this tracker holds a multi-part SMS.
-         * @return true if the tracker holds a multi-part SMS; false otherwise
-         */
-        @UnsupportedAppUsage
-        boolean isMultipart() {
-            return mData.containsKey("parts");
+            mMessageId = messageId;
         }
 
         public HashMap<String, Object> getData() {
@@ -1599,8 +1690,7 @@
                 // If we wrote this message in writeSentMessage, update it now
                 ContentValues values = new ContentValues(1);
                 values.put(Sms.STATUS, status);
-                SqliteWrapper.update(context, context.getContentResolver(),
-                        mMessageUri, values, null, null);
+                context.getContentResolver().update(mMessageUri, values, null, null);
             }
         }
 
@@ -1620,7 +1710,7 @@
             values.put(Sms.ERROR_CODE, errorCode);
             final long identity = Binder.clearCallingIdentity();
             try {
-                if (SqliteWrapper.update(context, context.getContentResolver(), mMessageUri, values,
+                if (context.getContentResolver().update(mMessageUri, values,
                         null/*where*/, null/*selectionArgs*/) != 1) {
                     Rlog.e(TAG, "Failed to move message to " + messageType);
                 }
@@ -1661,7 +1751,7 @@
             if (mDeliveryIntent != null) {
                 values.put(Sms.STATUS, Telephony.Sms.STATUS_PENDING);
             }
-            if (errorCode != 0) {
+            if (errorCode != NO_ERROR_CODE) {
                 values.put(Sms.ERROR_CODE, errorCode);
             }
             final long identity = Binder.clearCallingIdentity();
@@ -1730,16 +1820,22 @@
                         // Pass this to SMS apps so that they know where it is stored
                         fillIn.putExtra("uri", mMessageUri.toString());
                     }
-                    if (errorCode != 0) {
+                    if (errorCode != NO_ERROR_CODE) {
                         fillIn.putExtra("errorCode", errorCode);
                     }
                     if (mUnsentPartCount != null && isSinglePartOrLastPart) {
                         // Is multipart and last part
                         fillIn.putExtra(SEND_NEXT_MSG_EXTRA, true);
                     }
+                    if (mMessageId != 0L) {
+                        // Send the id back to the caller so they can verify the message id
+                        // with the one they passed to SmsManager.
+                        fillIn.putExtra(MESSAGE_ID_EXTRA, mMessageId);
+                    }
                     mSentIntent.send(context, error, fillIn);
                 } catch (CanceledException ex) {
-                    Rlog.e(TAG, "Failed to send result");
+                    Rlog.e(TAG, "Failed to send result"
+                            + " id: " + mMessageId);
                 }
             }
         }
@@ -1761,7 +1857,7 @@
                 if (mAnyPartFailed != null && mAnyPartFailed.get()) {
                     messageType = Sms.MESSAGE_TYPE_FAILED;
                 }
-                persistOrUpdateMessage(context, messageType, 0/*errorCode*/);
+                persistOrUpdateMessage(context, messageType, NO_ERROR_CODE);
             }
             if (mSentIntent != null) {
                 try {
@@ -1787,16 +1883,15 @@
             PendingIntent sentIntent, PendingIntent deliveryIntent, String format,
             AtomicInteger unsentPartCount, AtomicBoolean anyPartFailed, Uri messageUri,
             SmsHeader smsHeader, boolean expectMore, String fullMessageText, boolean isText,
-            boolean persistMessage, int priority, int validityPeriod, boolean isForVvm) {
-        // Get calling app package name via UID from Binder call
-        PackageManager pm = mContext.getPackageManager();
-
+            boolean persistMessage, int priority, int validityPeriod, boolean isForVvm,
+            long messageId) {
         // Get package info via packagemanager
-        final int userId = UserHandle.getCallingUserId();
+        UserHandle callingUser = UserHandle.getUserHandleForUid(Binder.getCallingUid());
+        final int userId = callingUser.getIdentifier();
+        PackageManager pm = mContext.createContextAsUser(callingUser, 0).getPackageManager();
         PackageInfo appInfo = null;
         try {
-            appInfo = pm.getPackageInfoAsUser(
-                    callingPackage, PackageManager.GET_SIGNATURES, userId);
+            appInfo = pm.getPackageInfo(callingPackage, PackageManager.GET_SIGNATURES);
         } catch (PackageManager.NameNotFoundException e) {
             // error will be logged in sendRawPdu
         }
@@ -1806,27 +1901,28 @@
         return new SmsTracker(data, sentIntent, deliveryIntent, appInfo, destAddr, format,
                 unsentPartCount, anyPartFailed, messageUri, smsHeader, expectMore,
                 fullMessageText, getSubId(), isText, persistMessage, userId, priority,
-                validityPeriod, isForVvm);
+                validityPeriod, isForVvm, messageId);
     }
 
     protected SmsTracker getSmsTracker(String callingPackage, HashMap<String, Object> data,
             PendingIntent sentIntent, PendingIntent deliveryIntent, String format, Uri messageUri,
             boolean expectMore, String fullMessageText, boolean isText, boolean persistMessage,
-            boolean isForVvm) {
+            boolean isForVvm, long messageId) {
         return getSmsTracker(callingPackage, data, sentIntent, deliveryIntent, format,
                 null/*unsentPartCount*/, null/*anyPartFailed*/, messageUri, null/*smsHeader*/,
                 expectMore, fullMessageText, isText, persistMessage,
-                SMS_MESSAGE_PRIORITY_NOT_SPECIFIED, SMS_MESSAGE_PERIOD_NOT_SPECIFIED, isForVvm);
+                SMS_MESSAGE_PRIORITY_NOT_SPECIFIED, SMS_MESSAGE_PERIOD_NOT_SPECIFIED, isForVvm,
+                messageId);
     }
 
     protected SmsTracker getSmsTracker(String callingPackage, HashMap<String, Object> data,
             PendingIntent sentIntent, PendingIntent deliveryIntent, String format, Uri messageUri,
             boolean expectMore, String fullMessageText, boolean isText, boolean persistMessage,
-            int priority, int validityPeriod, boolean isForVvm) {
+            int priority, int validityPeriod, boolean isForVvm, long messageId) {
         return getSmsTracker(callingPackage, data, sentIntent, deliveryIntent, format,
                 null/*unsentPartCount*/, null/*anyPartFailed*/, messageUri, null/*smsHeader*/,
                 expectMore, fullMessageText, isText, persistMessage, priority, validityPeriod,
-                isForVvm);
+                isForVvm, messageId);
     }
 
     protected HashMap<String, Object> getSmsTrackerMap(String destAddr, String scAddr,
@@ -1859,7 +1955,7 @@
             implements DialogInterface.OnClickListener, DialogInterface.OnCancelListener,
             CompoundButton.OnCheckedChangeListener {
 
-        private final SmsTracker mTracker;
+        private final SmsTracker[] mTrackers;
         @UnsupportedAppUsage
         private Button mPositiveButton;
         @UnsupportedAppUsage
@@ -1872,8 +1968,8 @@
         private static final int RATE_LIMIT = 1; // Rate Limit Exceeded
         private static final int NEVER_ALLOW = 1; // Never Allow
 
-        ConfirmDialogListener(SmsTracker tracker, TextView textView, int confirmationType) {
-            mTracker = tracker;
+        ConfirmDialogListener(SmsTracker[] trackers, TextView textView, int confirmationType) {
+            mTrackers = trackers;
             mRememberUndoInstruction = textView;
             mConfirmationType = confirmationType;
         }
@@ -1895,20 +1991,24 @@
             if (which == DialogInterface.BUTTON_POSITIVE) {
                 Rlog.d(TAG, "CONFIRM sending SMS");
                 // XXX this is lossy- apps can have more than one signature
-                EventLog.writeEvent(EventLogTags.EXP_DET_SMS_SENT_BY_USER,
-                                    mTracker.mAppInfo.applicationInfo == null ?
-                                    -1 : mTracker.mAppInfo.applicationInfo.uid);
-                sendMessage(obtainMessage(EVENT_SEND_CONFIRMED_SMS, mTracker));
+                EventLog.writeEvent(
+                        EventLogTags.EXP_DET_SMS_SENT_BY_USER,
+                        mTrackers[0].mAppInfo.applicationInfo == null
+                                ? -1
+                                : mTrackers[0].mAppInfo.applicationInfo.uid);
+                sendMessage(obtainMessage(EVENT_SEND_CONFIRMED_SMS, mTrackers));
                 if (mRememberChoice) {
                     newSmsPermission = SmsUsageMonitor.PREMIUM_SMS_PERMISSION_ALWAYS_ALLOW;
                 }
             } else if (which == DialogInterface.BUTTON_NEGATIVE) {
                 Rlog.d(TAG, "DENY sending SMS");
                 // XXX this is lossy- apps can have more than one signature
-                EventLog.writeEvent(EventLogTags.EXP_DET_SMS_DENIED_BY_USER,
-                                    mTracker.mAppInfo.applicationInfo == null ?
-                                    -1 :  mTracker.mAppInfo.applicationInfo.uid);
-                Message msg = obtainMessage(EVENT_STOP_SENDING, mTracker);
+                EventLog.writeEvent(
+                        EventLogTags.EXP_DET_SMS_DENIED_BY_USER,
+                        mTrackers[0].mAppInfo.applicationInfo == null
+                                ? -1
+                                : mTrackers[0].mAppInfo.applicationInfo.uid);
+                Message msg = obtainMessage(EVENT_STOP_SENDING, mTrackers);
                 msg.arg1 = mConfirmationType;
                 if (mRememberChoice) {
                     newSmsPermission = SmsUsageMonitor.PREMIUM_SMS_PERMISSION_NEVER_ALLOW;
@@ -1916,14 +2016,14 @@
                 }
                 sendMessage(msg);
             }
-            mSmsDispatchersController.setPremiumSmsPermission(mTracker.getAppPackageName(),
-                    newSmsPermission);
+            mSmsDispatchersController.setPremiumSmsPermission(
+                    mTrackers[0].getAppPackageName(), newSmsPermission);
         }
 
         @Override
         public void onCancel(DialogInterface dialog) {
             Rlog.d(TAG, "dialog dismissed: don't send SMS");
-            Message msg = obtainMessage(EVENT_STOP_SENDING, mTracker);
+            Message msg = obtainMessage(EVENT_STOP_SENDING, mTrackers);
             msg.arg1 = mConfirmationType;
             sendMessage(msg);
         }
@@ -1936,8 +2036,8 @@
                 mPositiveButton.setText(R.string.sms_short_code_confirm_always_allow);
                 mNegativeButton.setText(R.string.sms_short_code_confirm_never_allow);
                 if (mRememberUndoInstruction != null) {
-                    mRememberUndoInstruction.
-                            setText(R.string.sms_short_code_remember_undo_instruction);
+                    mRememberUndoInstruction
+                            .setText(R.string.sms_short_code_remember_undo_instruction);
                     mRememberUndoInstruction.setPadding(0,0,0,32);
                 }
             } else {
@@ -2004,7 +2104,7 @@
         try {
             PackageManager pm = mContext.getPackageManager();
             ApplicationInfo ai = pm.getApplicationInfo(getCarrierAppPackageName(), 0);
-            if (!UserHandle.isSameApp(ai.uid, Binder.getCallingUid())) {
+            if (UserHandle.getAppId(ai.uid) != UserHandle.getAppId(Binder.getCallingUid())) {
                 throw new SecurityException("Caller is not phone or carrier app!");
             }
         } catch (PackageManager.NameNotFoundException re) {
diff --git a/src/java/com/android/internal/telephony/ServiceStateTracker.java b/src/java/com/android/internal/telephony/ServiceStateTracker.java
index f9a6e34..b61f41f 100755
--- a/src/java/com/android/internal/telephony/ServiceStateTracker.java
+++ b/src/java/com/android/internal/telephony/ServiceStateTracker.java
@@ -16,7 +16,6 @@
 
 package com.android.internal.telephony;
 
-import static android.provider.Telephony.ServiceStateTable.getContentValuesForServiceState;
 import static android.provider.Telephony.ServiceStateTable.getUriForSubscriptionId;
 
 import static com.android.internal.telephony.CarrierActionAgent.CARRIER_ACTION_SET_RADIO_ENABLED;
@@ -25,13 +24,15 @@
 
 import android.annotation.IntDef;
 import android.annotation.NonNull;
-import android.annotation.UnsupportedAppUsage;
+import android.annotation.Nullable;
 import android.app.AlarmManager;
 import android.app.Notification;
 import android.app.NotificationManager;
 import android.app.PendingIntent;
+import android.compat.annotation.UnsupportedAppUsage;
 import android.content.BroadcastReceiver;
 import android.content.ContentResolver;
+import android.content.ContentValues;
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
@@ -41,18 +42,20 @@
 import android.net.NetworkCapabilities;
 import android.os.AsyncResult;
 import android.os.BaseBundle;
-import android.os.Build;
 import android.os.Handler;
 import android.os.Message;
+import android.os.Parcel;
 import android.os.PersistableBundle;
 import android.os.Registrant;
 import android.os.RegistrantList;
 import android.os.SystemClock;
 import android.os.SystemProperties;
+import android.os.TimestampedValue;
 import android.os.UserHandle;
 import android.os.WorkSource;
 import android.preference.PreferenceManager;
 import android.provider.Settings;
+import android.sysprop.TelephonyProperties;
 import android.telephony.AccessNetworkConstants;
 import android.telephony.AccessNetworkConstants.AccessNetworkType;
 import android.telephony.AccessNetworkConstants.TransportType;
@@ -64,29 +67,26 @@
 import android.telephony.CellIdentityTdscdma;
 import android.telephony.CellIdentityWcdma;
 import android.telephony.CellInfo;
-import android.telephony.CellLocation;
+import android.telephony.CellSignalStrengthLte;
+import android.telephony.CellSignalStrengthNr;
 import android.telephony.DataSpecificRegistrationInfo;
 import android.telephony.NetworkRegistrationInfo;
 import android.telephony.PhysicalChannelConfig;
-import android.telephony.Rlog;
 import android.telephony.ServiceState;
 import android.telephony.ServiceState.RilRadioTechnology;
 import android.telephony.SignalStrength;
+import android.telephony.SignalThresholdInfo;
 import android.telephony.SubscriptionInfo;
 import android.telephony.SubscriptionManager;
 import android.telephony.SubscriptionManager.OnSubscriptionsChangedListener;
 import android.telephony.TelephonyManager;
 import android.telephony.VoiceSpecificRegistrationInfo;
-import android.telephony.cdma.CdmaCellLocation;
-import android.telephony.gsm.GsmCellLocation;
 import android.text.TextUtils;
 import android.util.EventLog;
 import android.util.LocalLog;
 import android.util.Pair;
 import android.util.SparseArray;
 import android.util.SparseBooleanArray;
-import android.util.StatsLog;
-import android.util.TimestampedValue;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.telephony.cdma.CdmaSubscriptionSourceManager;
@@ -107,9 +107,11 @@
 import com.android.internal.telephony.uicc.UiccCardApplication;
 import com.android.internal.telephony.uicc.UiccController;
 import com.android.internal.telephony.uicc.UiccProfile;
+import com.android.internal.telephony.util.ArrayUtils;
 import com.android.internal.telephony.util.NotificationChannelController;
-import com.android.internal.util.ArrayUtils;
+import com.android.internal.telephony.util.TelephonyUtils;
 import com.android.internal.util.IndentingPrintWriter;
+import com.android.telephony.Rlog;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
@@ -117,17 +119,18 @@
 import java.lang.annotation.RetentionPolicy;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Collections;
+import java.util.Comparator;
 import java.util.HashSet;
 import java.util.LinkedList;
 import java.util.List;
-import java.util.Objects;
 import java.util.Set;
-import java.util.TimeZone;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicInteger;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 import java.util.regex.PatternSyntaxException;
+import java.util.stream.Collectors;
 
 /**
  * {@hide}
@@ -280,6 +283,16 @@
     protected static final int EVENT_PHYSICAL_CHANNEL_CONFIG           = 55;
     protected static final int EVENT_CELL_LOCATION_RESPONSE            = 56;
     protected static final int EVENT_CARRIER_CONFIG_CHANGED            = 57;
+    private static final int EVENT_POLL_STATE_REQUEST                  = 58;
+
+    /**
+     * The current service state.
+     *
+     * This is a column name in {@link android.provider.Telephony.ServiceStateTable}.
+     *
+     * Copied from packages/services/Telephony/src/com/android/phone/ServiceStateProvider.java
+     */
+    private static final String SERVICE_STATE = "service_state";
 
     @Retention(RetentionPolicy.SOURCE)
     @IntDef(prefix = {"CARRIER_NAME_DISPLAY_BITMASK"},
@@ -387,9 +400,7 @@
                             com.android.internal.R.bool.skip_restoring_network_selection);
                     mPhone.sendSubscriptionSettings(restoreSelection);
 
-                    mPhone.setSystemProperty(TelephonyProperties.PROPERTY_DATA_NETWORK_TYPE,
-                            ServiceState.rilRadioTechnologyToString(
-                                    mSS.getRilDataRadioTechnology()));
+                    setDataNetworkTypeForPhone(mSS.getRilDataRadioTechnology());
 
                     if (mSpnUpdatePending) {
                         mSubscriptionController.setPlmnSpn(mPhone.getPhoneId(), mCurShowPlmn,
@@ -437,12 +448,19 @@
 
     //Common
     @UnsupportedAppUsage
-    private final GsmCdmaPhone mPhone;
+    protected final GsmCdmaPhone mPhone;
 
     private CellIdentity mCellIdentity;
-    private CellIdentity mNewCellIdentity;
     private static final int MS_PER_HOUR = 60 * 60 * 1000;
     private final NitzStateMachine mNitzState;
+
+    /**
+     * Holds the last NITZ signal received. Used only for trying to determine an MCC from a CDMA
+     * SID.
+     */
+    @Nullable
+    private NitzData mLastNitzData;
+
     private final EriManager mEriManager;
     @UnsupportedAppUsage
     private final ContentResolver mCr;
@@ -467,15 +485,15 @@
     private int mNewRejectCode;
 
     /**
-     * GSM roaming status solely based on TS 27.007 7.2 CREG. Only used by
+     * GSM voice roaming status solely based on TS 27.007 7.2 CREG. Only used by
      * handlePollStateResult to store CREG roaming result.
      */
-    private boolean mGsmRoaming = false;
+    private boolean mGsmVoiceRoaming = false;
     /**
-     * Data roaming status solely based on TS 27.007 10.1.19 CGREG. Only used by
+     * Gsm data roaming status solely based on TS 27.007 10.1.19 CGREG. Only used by
      * handlePollStateResult to store CGREG roaming result.
      */
-    private boolean mDataRoaming = false;
+    private boolean mGsmDataRoaming = false;
     /**
      * Mark when service state is in emergency call only mode
      */
@@ -600,8 +618,9 @@
                 .makeEriManager(mPhone, EriManager.ERI_FROM_XML);
 
         mRatRatcheter = new RatRatcheter(mPhone);
-        mVoiceCapable = mPhone.getContext().getResources().getBoolean(
-                com.android.internal.R.bool.config_voice_capable);
+        mVoiceCapable = ((TelephonyManager) mPhone.getContext()
+                .getSystemService(Context.TELEPHONY_SERVICE))
+                .isVoiceCapable();
         mUiccController = UiccController.getInstance();
 
         mUiccController.registerForIccChanged(this, EVENT_ICC_CHANGED, null);
@@ -692,7 +711,7 @@
         }
 
         // If we are previously in service, we need to notify that we are out of service now.
-        if (mSS != null && mSS.getVoiceRegState() == ServiceState.STATE_IN_SERVICE) {
+        if (mSS != null && mSS.getState() == ServiceState.STATE_IN_SERVICE) {
             mNetworkDetachedRegistrants.notifyRegistrants();
         }
 
@@ -721,9 +740,9 @@
         mMin = null;
         mPrlVersion = null;
         mIsMinInfoReady = false;
-        mNitzState.handleNetworkCountryCodeUnavailable();
+        mLastNitzData = null;
+        mNitzState.handleNetworkUnavailable();
         mCellIdentity = null;
-        mNewCellIdentity = null;
         mSignalStrengthUpdatedTime = System.currentTimeMillis();
 
         //cancel any pending pollstate request on voice tech switching
@@ -758,8 +777,7 @@
         // on fields like mIsSubscriptionFromRuim (which is updated above)
         onUpdateIccAvailability();
 
-        mPhone.setSystemProperty(TelephonyProperties.PROPERTY_DATA_NETWORK_TYPE,
-                ServiceState.rilRadioTechnologyToString(ServiceState.RIL_RADIO_TECHNOLOGY_UNKNOWN));
+        setDataNetworkTypeForPhone(ServiceState.RIL_RADIO_TECHNOLOGY_UNKNOWN);
         // Query signal strength from the modem after service tracker is created (i.e. boot up,
         // switching between GSM and CDMA phone), because the unsolicited signal strength
         // information might come late or even never come. This will get the accurate signal
@@ -806,6 +824,10 @@
     }
     public boolean getPowerStateFromCarrier() { return !mRadioDisabledByCarrier; }
 
+    public List<PhysicalChannelConfig> getPhysicalChannelConfigList() {
+        return mLastPhysicalChannelConfigList;
+    }
+
     private SignalStrength mLastSignalStrength = null;
     @UnsupportedAppUsage
     protected boolean notifySignalStrength() {
@@ -830,35 +852,46 @@
      */
     protected void notifyVoiceRegStateRilRadioTechnologyChanged() {
         int rat = mSS.getRilVoiceRadioTechnology();
-        int vrs = mSS.getVoiceRegState();
+        int vrs = mSS.getState();
         if (DBG) log("notifyVoiceRegStateRilRadioTechnologyChanged: vrs=" + vrs + " rat=" + rat);
 
         mVoiceRegStateOrRatChangedRegistrants.notifyResult(new Pair<Integer, Integer>(vrs, rat));
     }
 
     /**
+     * Get registration info
+     *
+     * @param transport The transport type
+     * @return Pair of registration info including {@link ServiceState.RegState} and
+     * {@link RilRadioTechnology}.
+     *
+     */
+    @Nullable
+    private Pair<Integer, Integer> getRegistrationInfo(@TransportType int transport) {
+        NetworkRegistrationInfo nrs = mSS.getNetworkRegistrationInfo(
+                NetworkRegistrationInfo.DOMAIN_PS, transport);
+        if (nrs != null) {
+            int rat = ServiceState.networkTypeToRilRadioTechnology(
+                    nrs.getAccessNetworkTechnology());
+            int drs = regCodeToServiceState(nrs.getRegistrationState());
+            return new Pair<>(drs, rat);
+        }
+        return null;
+    }
+
+    /**
      * Notify all mDataConnectionRatChangeRegistrants using an
      * AsyncResult in msg.obj where AsyncResult#result contains the
      * new RAT as an Integer Object.
      */
-    protected void notifyDataRegStateRilRadioTechnologyChanged(int transport) {
-        NetworkRegistrationInfo nrs = mSS.getNetworkRegistrationInfo(
-                NetworkRegistrationInfo.DOMAIN_PS, transport);
-        if (nrs != null) {
-            int rat = ServiceState.networkTypeToRilRadioTechnology(
-                    nrs.getAccessNetworkTechnology());
-            int drs = regCodeToServiceState(nrs.getRegistrationState());
-            if (DBG) {
-                log("notifyDataRegStateRilRadioTechnologyChanged: drs=" + drs + " rat=" + rat);
-            }
-
-            RegistrantList registrantList = mDataRegStateOrRatChangedRegistrants.get(transport);
-            if (registrantList != null) {
-                registrantList.notifyResult(new Pair<>(drs, rat));
+    protected void notifyDataRegStateRilRadioTechnologyChanged(@TransportType int transport) {
+        RegistrantList registrantList = mDataRegStateOrRatChangedRegistrants.get(transport);
+        if (registrantList != null) {
+            Pair<Integer, Integer> registrationInfo = getRegistrationInfo(transport);
+            if (registrationInfo != null) {
+                registrantList.notifyResult(registrationInfo);
             }
         }
-        mPhone.setSystemProperty(TelephonyProperties.PROPERTY_DATA_NETWORK_TYPE,
-                ServiceState.rilRadioTechnologyToString(mSS.getRilDataRadioTechnology()));
     }
 
     /**
@@ -869,21 +902,21 @@
     protected void useDataRegStateForDataOnlyDevices() {
         if (mVoiceCapable == false) {
             if (DBG) {
-                log("useDataRegStateForDataOnlyDevice: VoiceRegState=" + mNewSS.getVoiceRegState()
-                    + " DataRegState=" + mNewSS.getDataRegState());
+                log("useDataRegStateForDataOnlyDevice: VoiceRegState=" + mNewSS.getState()
+                        + " DataRegState=" + mNewSS.getDataRegistrationState());
             }
             // TODO: Consider not lying and instead have callers know the difference.
-            mNewSS.setVoiceRegState(mNewSS.getDataRegState());
+            mNewSS.setVoiceRegState(mNewSS.getDataRegistrationState());
         }
     }
 
     @UnsupportedAppUsage
     protected void updatePhoneObject() {
-        if (mPhone.getContext().getResources().
-                getBoolean(com.android.internal.R.bool.config_switch_phone_on_voice_reg_state_change)) {
+        if (mPhone.getContext().getResources().getBoolean(
+                com.android.internal.R.bool.config_switch_phone_on_voice_reg_state_change)) {
             // If the phone is not registered on a network, no need to update.
-            boolean isRegistered = mSS.getVoiceRegState() == ServiceState.STATE_IN_SERVICE ||
-                    mSS.getVoiceRegState() == ServiceState.STATE_EMERGENCY_ONLY;
+            boolean isRegistered = mSS.getState() == ServiceState.STATE_IN_SERVICE
+                    || mSS.getState() == ServiceState.STATE_EMERGENCY_ONLY;
             if (!isRegistered) {
                 log("updatePhoneObject: Ignore update");
                 return;
@@ -992,11 +1025,28 @@
                 obtainMessage(EVENT_GET_PREFERRED_NETWORK_TYPE, onComplete));
     }
 
-    public void
-    setRadioPower(boolean power) {
-        mDesiredPowerState = power;
+    /**
+     * Turn on or off radio power.
+     */
+    public final void setRadioPower(boolean power) {
+        setRadioPower(power, false, false, false);
+    }
 
-        setPowerStateToDesired();
+    /**
+     * Turn on or off radio power with option to specify whether it's for emergency call.
+     * More details check {@link PhoneInternalInterface#setRadioPower(
+     * boolean, boolean, boolean, boolean)}.
+     */
+    public void setRadioPower(boolean power, boolean forEmergencyCall,
+            boolean isSelectedPhoneForEmergencyCall, boolean forceApply) {
+        log("setRadioPower forEmergencyCall " + forEmergencyCall + " forceApply " + forceApply);
+        if (power == mDesiredPowerState && !forceApply) {
+            log("setRadioPower mDesiredPowerState is already " + power + " Do nothing.");
+            return;
+        }
+
+        mDesiredPowerState = power;
+        setPowerStateToDesired(forEmergencyCall, isSelectedPhoneForEmergencyCall, forceApply);
     }
 
     /**
@@ -1005,7 +1055,14 @@
      * @param enable indicate if radio power is enabled or disabled from carrier action.
      */
     public void setRadioPowerFromCarrier(boolean enable) {
-        mRadioDisabledByCarrier = !enable;
+        boolean disableByCarrier = !enable;
+        if (mRadioDisabledByCarrier == disableByCarrier) {
+            log("setRadioPowerFromCarrier mRadioDisabledByCarrier is already "
+                    + disableByCarrier + " Do nothing.");
+            return;
+        }
+
+        mRadioDisabledByCarrier = disableByCarrier;
         setPowerStateToDesired();
     }
 
@@ -1131,20 +1188,24 @@
                         }
                     }
                 } else {
-                    // If we receive an empty message, it's probably a timeout; if there is no
-                    // pending request, drop it.
-                    if (!mIsPendingCellInfoRequest) break;
-                    // If there is a request pending, we still need to check whether it's a timeout
-                    // for the current request of whether it's leftover from a previous request.
-                    final long curTime = SystemClock.elapsedRealtime();
-                    if ((curTime - mLastCellInfoReqTime) <  CELL_INFO_LIST_QUERY_TIMEOUT) {
-                        break;
+                    synchronized (mPendingCellInfoRequests) {
+                        // If we receive an empty message, it's probably a timeout; if there is no
+                        // pending request, drop it.
+                        if (!mIsPendingCellInfoRequest) break;
+                        // If there is a request pending, we still need to check whether it's a
+                        // timeout for the current request of whether it's leftover from a
+                        // previous request.
+                        final long curTime = SystemClock.elapsedRealtime();
+                        if ((curTime - mLastCellInfoReqTime) <  CELL_INFO_LIST_QUERY_TIMEOUT) {
+                            break;
+                        }
+                        // We've received a legitimate timeout, so something has gone terribly
+                        // wrong.
+                        loge("Timeout waiting for CellInfo; (everybody panic)!");
+                        mLastCellInfoList = null;
+                        // Since the timeout is applicable, fall through and update all synchronous
+                        // callers with the failure.
                     }
-                    // We've received a legitimate timeout, so something has gone terribly wrong.
-                    loge("Timeout waiting for CellInfo; (everybody panic)!");
-                    mLastCellInfoList = null;
-                    // Since the timeout is applicable, fall through and update all synchronous
-                    // callers with the failure.
                 }
                 synchronized (mPendingCellInfoRequests) {
                     // If we have pending requests, then service them. Note that in case of a
@@ -1193,7 +1254,7 @@
                         SubscriptionManager.INVALID_SUBSCRIPTION_ID);
                 mPrevSubId = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
                 mIsSimReady = true;
-                pollState();
+                pollStateInternal(false);
                 // Signal strength polling stops when radio is off
                 queueNextSignalStrengthPoll();
                 break;
@@ -1210,11 +1271,11 @@
                 // This will do nothing in the 'radio not available' case
                 setPowerStateToDesired();
                 // These events are modem triggered, so pollState() needs to be forced
-                modemTriggeredPollState();
+                pollStateInternal(true);
                 break;
 
             case EVENT_NETWORK_STATE_CHANGED:
-                modemTriggeredPollState();
+                pollStateInternal(true);
                 break;
 
             case EVENT_GET_SIGNAL_STRENGTH:
@@ -1238,7 +1299,7 @@
                             .getCellIdentity();
                     updateOperatorNameForCellIdentity(cellIdentity);
                     mCellIdentity = cellIdentity;
-                    mPhone.notifyLocationChanged(getCellLocation());
+                    mPhone.notifyLocationChanged(getCellIdentity());
                 }
 
                 // Release any temporary cell lock, which could have been
@@ -1351,7 +1412,7 @@
 
             case EVENT_CHECK_REPORT_GPRS:
                 if (mPhone.isPhoneTypeGsm() && mSS != null &&
-                        !isGprsConsistent(mSS.getDataRegState(), mSS.getVoiceRegState())) {
+                        !isGprsConsistent(mSS.getDataRegistrationState(), mSS.getState())) {
 
                     // Can't register data service while voice service is ok
                     // i.e. CREG is ok while CGREG is not
@@ -1420,7 +1481,7 @@
                 if (mPhone.getLteOnCdmaMode() == PhoneConstants.LTE_ON_CDMA_TRUE) {
                     // Subscription will be read from SIM I/O
                     if (DBG) log("Receive EVENT_RUIM_READY");
-                    pollState();
+                    pollStateInternal(false);
                 } else {
                     if (DBG) log("Receive EVENT_RUIM_READY and Send Request getCDMASubscription.");
                     getSubscriptionInfoAndStartPollingThreads();
@@ -1513,7 +1574,7 @@
                         // SID/NID/PRL is loaded. Poll service state
                         // again to update to the roaming state with
                         // the latest variables.
-                        pollState();
+                        pollStateInternal(false);
                     }
                 }
                 break;
@@ -1558,20 +1619,23 @@
                     mPhone.notifyPhysicalChannelConfiguration(list);
                     mLastPhysicalChannelConfigList = list;
                     boolean hasChanged = false;
-                    if (updateNrFrequencyRangeFromPhysicalChannelConfigs(list, mSS)) {
-                        mNrFrequencyChangedRegistrants.notifyRegistrants();
-                        hasChanged = true;
-                    }
                     if (updateNrStateFromPhysicalChannelConfigs(list, mSS)) {
                         mNrStateChangedRegistrants.notifyRegistrants();
                         hasChanged = true;
                     }
+                    if (updateNrFrequencyRangeFromPhysicalChannelConfigs(list, mSS)) {
+                        mNrFrequencyChangedRegistrants.notifyRegistrants();
+                        hasChanged = true;
+                    }
                     hasChanged |= RatRatcheter
                             .updateBandwidths(getBandwidthsFromConfigs(list), mSS);
 
                     // Notify NR frequency, NR connection status or bandwidths changed.
                     if (hasChanged) {
                         mPhone.notifyServiceStateChanged(mSS);
+                        TelephonyMetrics.getInstance().writeServiceStateChanged(
+                                mPhone.getPhoneId(), mSS);
+                        mPhone.getVoiceCallSessionStats().onServiceStateChanged(mSS);
                     }
                 }
                 break;
@@ -1579,15 +1643,15 @@
             case EVENT_CELL_LOCATION_RESPONSE:
                 ar = (AsyncResult) msg.obj;
                 if (ar == null) {
-                    loge("Invalid null response to getCellLocation!");
+                    loge("Invalid null response to getCellIdentity!");
                     break;
                 }
                 // This response means that the correct CellInfo is already cached; thus we
                 // can rely on the last cell info to already contain any cell info that is
                 // available, which means that we can return the result of the existing
-                // getCellLocation() function without any additional processing here.
+                // getCellIdentity() function without any additional processing here.
                 Message rspRspMsg = (Message) ar.userObj;
-                AsyncResult.forMessage(rspRspMsg, getCellLocation(), ar.exception);
+                AsyncResult.forMessage(rspRspMsg, getCellIdentity(), ar.exception);
                 rspRspMsg.sendToTarget();
                 break;
 
@@ -1595,6 +1659,10 @@
                 onCarrierConfigChanged();
                 break;
 
+            case EVENT_POLL_STATE_REQUEST:
+                pollStateInternal(false);
+                break;
+
             default:
                 log("Unhandled message with number: " + msg.what);
                 break;
@@ -1673,9 +1741,9 @@
      */
     public String getImsi() {
         // TODO: When RUIM is enabled, IMSI will come from RUIM not build-time props.
-        String operatorNumeric = ((TelephonyManager) mPhone.getContext().
-                getSystemService(Context.TELEPHONY_SERVICE)).
-                getSimOperatorNumericForPhone(mPhone.getPhoneId());
+        String operatorNumeric = ((TelephonyManager) mPhone.getContext()
+                .getSystemService(Context.TELEPHONY_SERVICE))
+                .getSimOperatorNumericForPhone(mPhone.getPhoneId());
 
         if (!TextUtils.isEmpty(operatorNumeric) && getCdmaMin() != null) {
             return (operatorNumeric + getCdmaMin());
@@ -1771,6 +1839,11 @@
         }
     }
 
+    public void onAirplaneModeChanged(boolean isAirplaneModeOn) {
+        mLastNitzData = null;
+        mNitzState.handleAirplaneModeChanged(isAirplaneModeOn);
+    }
+
     protected Phone getPhone() {
         return mPhone;
     }
@@ -1839,7 +1912,7 @@
                 final int dataRat = getRilDataRadioTechnologyForWwan(mNewSS);
                 if (ServiceState.isCdma(dataRat)) {
                     final boolean isVoiceInService =
-                            (mNewSS.getVoiceRegState() == ServiceState.STATE_IN_SERVICE);
+                            (mNewSS.getState() == ServiceState.STATE_IN_SERVICE);
                     if (isVoiceInService) {
                         boolean isVoiceRoaming = mNewSS.getVoiceRoaming();
                         if (mNewSS.getDataRoaming() != isVoiceRoaming) {
@@ -1878,9 +1951,9 @@
                         // Use default
                         mNewSS.setCdmaRoamingIndicator(mDefaultRoamingIndicator);
                     } else if (namMatch && !mIsInPrl) {
-                        // TODO this will be removed when we handle roaming on LTE on CDMA+LTE phones
-                        if (ServiceState.isLte(mNewSS.getRilVoiceRadioTechnology())) {
-                            log("Turn off roaming indicator as voice is LTE");
+                        // TODO: remove when we handle roaming on LTE/NR on CDMA+LTE phones
+                        if (ServiceState.isPsOnlyTech(mNewSS.getRilVoiceRadioTechnology())) {
+                            log("Turn off roaming indicator as voice is LTE or NR");
                             mNewSS.setCdmaRoamingIndicator(EriInfo.ROAMING_INDICATOR_OFF);
                         } else {
                             mNewSS.setCdmaRoamingIndicator(EriInfo.ROAMING_INDICATOR_FLASH);
@@ -1933,15 +2006,6 @@
         return cdmaRoaming && !isSameOperatorNameFromSimAndSS(s);
     }
 
-    private boolean isNrStateChanged(
-            NetworkRegistrationInfo oldRegState, NetworkRegistrationInfo newRegState) {
-        if (oldRegState == null || newRegState == null) {
-            return oldRegState != newRegState;
-        }
-
-        return oldRegState.getNrState() != newRegState.getNrState();
-    }
-
     private boolean updateNrFrequencyRangeFromPhysicalChannelConfigs(
             List<PhysicalChannelConfig> physicalChannelConfigs, ServiceState ss) {
         int newFrequencyRange = ServiceState.FREQUENCY_RANGE_UNKNOWN;
@@ -1986,18 +2050,16 @@
             }
         }
 
-        int newNrState = regInfo.getNrState();
+        int oldNrState = regInfo.getNrState();
+        int newNrState = oldNrState;
         if (hasNrSecondaryServingCell) {
-            if (regInfo.getNrState() == NetworkRegistrationInfo.NR_STATE_NOT_RESTRICTED) {
-                newNrState = NetworkRegistrationInfo.NR_STATE_CONNECTED;
-            }
+            newNrState = NetworkRegistrationInfo.NR_STATE_CONNECTED;
         } else {
-            if (regInfo.getNrState() == NetworkRegistrationInfo.NR_STATE_CONNECTED) {
-                newNrState = NetworkRegistrationInfo.NR_STATE_NOT_RESTRICTED;
-            }
+            regInfo.updateNrState();
+            newNrState = regInfo.getNrState();
         }
 
-        boolean hasChanged = newNrState != regInfo.getNrState();
+        boolean hasChanged = newNrState != oldNrState;
         regInfo.setNrState(newNrState);
         ss.addNetworkRegistrationInfo(regInfo);
         return hasChanged;
@@ -2043,7 +2105,7 @@
         }
     }
 
-    void handlePollStateResultMessage(int what, AsyncResult ar) {
+    protected void handlePollStateResultMessage(int what, AsyncResult ar) {
         int ints[];
         switch (what) {
             case EVENT_POLL_STATE_CS_CELLULAR_REGISTRATION: {
@@ -2066,11 +2128,8 @@
                 mEmergencyOnly = networkRegState.isEmergencyEnabled();
                 if (mPhone.isPhoneTypeGsm()) {
 
-                    mGsmRoaming = regCodeIsRoaming(registrationState);
+                    mGsmVoiceRoaming = regCodeIsRoaming(registrationState);
                     mNewRejectCode = reasonForDenial;
-
-                    boolean isVoiceCapable = mPhone.getContext().getResources()
-                            .getBoolean(com.android.internal.R.bool.config_voice_capable);
                 } else {
                     int roamingIndicator = voiceSpecificStates.roamingIndicator;
 
@@ -2114,8 +2173,6 @@
                     }
                 }
 
-                mNewCellIdentity = networkRegState.getCellIdentity();
-
                 if (DBG) {
                     log("handlePollStateResultMessage: CS cellular. " + networkRegState);
                 }
@@ -2141,6 +2198,7 @@
                 int serviceState = regCodeToServiceState(registrationState);
                 int newDataRat = ServiceState.networkTypeToRilRadioTechnology(
                         networkRegState.getAccessNetworkTechnology());
+                boolean nrHasChanged = false;
 
                 if (DBG) {
                     log("handlePollStateResultMessage: PS cellular. " + networkRegState);
@@ -2151,33 +2209,47 @@
                 // (2 or more cells) to a new cell if they camp for emergency service only.
                 if (serviceState == ServiceState.STATE_OUT_OF_SERVICE) {
                     mLastPhysicalChannelConfigList = null;
-                    updateNrFrequencyRangeFromPhysicalChannelConfigs(null, mNewSS);
                 }
-                updateNrStateFromPhysicalChannelConfigs(mLastPhysicalChannelConfigList, mNewSS);
+                nrHasChanged |= updateNrFrequencyRangeFromPhysicalChannelConfigs(
+                        mLastPhysicalChannelConfigList, mNewSS);
+                nrHasChanged |= updateNrStateFromPhysicalChannelConfigs(
+                        mLastPhysicalChannelConfigList, mNewSS);
                 setPhyCellInfoFromCellIdentity(mNewSS, networkRegState.getCellIdentity());
 
+                if (nrHasChanged) {
+                    TelephonyMetrics.getInstance().writeServiceStateChanged(
+                            mPhone.getPhoneId(), mSS);
+                    mPhone.getVoiceCallSessionStats().onServiceStateChanged(mSS);
+                }
+
                 if (mPhone.isPhoneTypeGsm()) {
 
                     mNewReasonDataDenied = networkRegState.getRejectCause();
                     mNewMaxDataCalls = dataSpecificStates.maxDataCalls;
-                    mDataRoaming = regCodeIsRoaming(registrationState);
+                    mGsmDataRoaming = regCodeIsRoaming(registrationState);
+                    // Save the data roaming state reported by modem registration before resource
+                    // overlay or carrier config possibly overrides it.
+                    mNewSS.setDataRoamingFromRegistration(mGsmDataRoaming);
                 } else if (mPhone.isPhoneTypeCdma()) {
-
                     boolean isDataRoaming = regCodeIsRoaming(registrationState);
                     mNewSS.setDataRoaming(isDataRoaming);
+                    // Save the data roaming state reported by modem registration before resource
+                    // overlay or carrier config possibly overrides it.
+                    mNewSS.setDataRoamingFromRegistration(isDataRoaming);
                 } else {
 
                     // If the unsolicited signal strength comes just before data RAT family changes
-                    // (i.e. from UNKNOWN to LTE, CDMA to LTE, LTE to CDMA), the signal bar might
-                    // display the wrong information until the next unsolicited signal strength
-                    // information coming from the modem, which might take a long time to come or
-                    // even not come at all.  In order to provide the best user experience, we
-                    // query the latest signal information so it will show up on the UI on time.
-                    int oldDataRAT = mSS.getRilDataRadioTechnology();
+                    // (i.e. from UNKNOWN to LTE/NR, CDMA to LTE/NR, LTE/NR to CDMA), the signal bar
+                    // might display the wrong information until the next unsolicited signal
+                    // strength information coming from the modem, which might take a long time to
+                    // come or even not come at all.  In order to provide the best user experience,
+                    // we query the latest signal information so it will show up on the UI on time.
+                    int oldDataRAT = getRilDataRadioTechnologyForWwan(mSS);
                     if (((oldDataRAT == ServiceState.RIL_RADIO_TECHNOLOGY_UNKNOWN)
                             && (newDataRat != ServiceState.RIL_RADIO_TECHNOLOGY_UNKNOWN))
-                            || (ServiceState.isCdma(oldDataRAT) && ServiceState.isLte(newDataRat))
-                            || (ServiceState.isLte(oldDataRAT)
+                            || (ServiceState.isCdma(oldDataRAT)
+                            && ServiceState.isPsOnlyTech(newDataRat))
+                            || (ServiceState.isPsOnlyTech(oldDataRAT)
                             && ServiceState.isCdma(newDataRat))) {
                         mCi.getSignalStrength(obtainMessage(EVENT_GET_SIGNAL_STRENGTH));
                     }
@@ -2185,6 +2257,9 @@
                     // voice roaming state in done while handling EVENT_POLL_STATE_REGISTRATION_CDMA
                     boolean isDataRoaming = regCodeIsRoaming(registrationState);
                     mNewSS.setDataRoaming(isDataRoaming);
+                    // Save the data roaming state reported by modem registration before resource
+                    // overlay or carrier config possibly overrides it.
+                    mNewSS.setDataRoamingFromRegistration(isDataRoaming);
                 }
 
                 updateServiceStateLteEarfcnBoost(mNewSS,
@@ -2193,8 +2268,6 @@
             }
 
             case EVENT_POLL_STATE_OPERATOR: {
-                String brandOverride = getOperatorBrandOverride();
-                mCdnr.updateEfForBrandOverride(brandOverride);
                 if (mPhone.isPhoneTypeGsm()) {
                     String opNames[] = (String[]) ar.result;
 
@@ -2202,6 +2275,8 @@
                         mNewSS.setOperatorAlphaLongRaw(opNames[0]);
                         mNewSS.setOperatorAlphaShortRaw(opNames[1]);
                         // FIXME: Giving brandOverride higher precedence, is this desired?
+                        String brandOverride = getOperatorBrandOverride();
+                        mCdnr.updateEfForBrandOverride(brandOverride);
                         if (brandOverride != null) {
                             log("EVENT_POLL_STATE_OPERATOR: use brandOverride=" + brandOverride);
                             mNewSS.setOperatorName(brandOverride, brandOverride, opNames[2]);
@@ -2231,6 +2306,8 @@
                             // NV device (as opposed to CSIM)
                             mNewSS.setOperatorName(opNames[0], opNames[1], opNames[2]);
                         } else {
+                            String brandOverride = getOperatorBrandOverride();
+                            mCdnr.updateEfForBrandOverride(brandOverride);
                             if (brandOverride != null) {
                                 mNewSS.setOperatorName(brandOverride, brandOverride, opNames[2]);
                             } else {
@@ -2406,21 +2483,16 @@
              * is set to roaming when either is true.
              *
              * There are exceptions for the above rule.
-             * The new SS is not set as roaming while gsm service reports
-             * roaming but indeed it is same operator.
-             * And the operator is considered non roaming.
+             * The new SS is not set as roaming while gsm service or
+             * data service reports roaming but indeed it is same
+             * operator. And the operator is considered non roaming.
              *
              * The test for the operators is to handle special roaming
              * agreements and MVNO's.
              */
-            boolean roaming = (mGsmRoaming || mDataRoaming);
+            boolean roaming = (mGsmVoiceRoaming || mGsmDataRoaming);
 
-            // for IWLAN case, data is home. Only check voice roaming.
-            if (mNewSS.getRilDataRadioTechnology() == ServiceState.RIL_RADIO_TECHNOLOGY_IWLAN) {
-                roaming = mGsmRoaming;
-            }
-
-            if (mGsmRoaming && !isOperatorConsideredRoaming(mNewSS)
+            if (roaming && !isOperatorConsideredRoaming(mNewSS)
                     && (isSameNamedOperators(mNewSS) || isOperatorConsideredNonRoaming(mNewSS))) {
                 log("updateRoamingState: resource override set non roaming.isSameNamedOperators="
                         + isSameNamedOperators(mNewSS) + ",isOperatorConsideredNonRoaming="
@@ -2441,8 +2513,7 @@
                 roaming = true;
             }
 
-            mNewSS.setVoiceRoaming(roaming);
-            mNewSS.setDataRoaming(roaming);
+            mNewSS.setRoaming(roaming);
         } else {
             String systemId = Integer.toString(mNewSS.getCdmaSystemId());
 
@@ -2461,23 +2532,21 @@
                 setRoamingOn();
             }
 
-            if (Build.IS_DEBUGGABLE && SystemProperties.getBoolean(PROP_FORCE_ROAMING, false)) {
-                mNewSS.setVoiceRoaming(true);
-                mNewSS.setDataRoaming(true);
+            if (TelephonyUtils.IS_DEBUGGABLE
+                    && SystemProperties.getBoolean(PROP_FORCE_ROAMING, false)) {
+                mNewSS.setRoaming(true);
             }
         }
     }
 
     private void setRoamingOn() {
-        mNewSS.setVoiceRoaming(true);
-        mNewSS.setDataRoaming(true);
+        mNewSS.setRoaming(true);
         mNewSS.setCdmaEriIconIndex(EriInfo.ROAMING_INDICATOR_ON);
         mNewSS.setCdmaEriIconMode(EriInfo.ROAMING_ICON_MODE_NORMAL);
     }
 
     private void setRoamingOff() {
-        mNewSS.setVoiceRoaming(false);
-        mNewSS.setDataRoaming(false);
+        mNewSS.setRoaming(false);
         mNewSS.setCdmaEriIconIndex(EriInfo.ROAMING_INDICATOR_OFF);
     }
 
@@ -2505,7 +2574,7 @@
 
     private void notifySpnDisplayUpdate(CarrierDisplayNameData data) {
         int subId = mPhone.getSubId();
-        // Update SPN_STRINGS_UPDATED_ACTION IFF any value changes
+        // Update ACTION_SERVICE_PROVIDERS_UPDATED IFF any value changes
         if (mSubId != subId
                 || data.shouldShowPlmn() != mCurShowPlmn
                 || data.shouldShowSpn() != mCurShowSpn
@@ -2526,12 +2595,12 @@
             mCdnrLogs.log(log);
             if (DBG) log("updateSpnDisplay: " + log);
 
-            Intent intent = new Intent(TelephonyIntents.SPN_STRINGS_UPDATED_ACTION);
-            intent.putExtra(TelephonyIntents.EXTRA_SHOW_SPN, data.shouldShowSpn());
-            intent.putExtra(TelephonyIntents.EXTRA_SPN, data.getSpn());
-            intent.putExtra(TelephonyIntents.EXTRA_DATA_SPN, data.getDataSpn());
-            intent.putExtra(TelephonyIntents.EXTRA_SHOW_PLMN, data.shouldShowPlmn());
-            intent.putExtra(TelephonyIntents.EXTRA_PLMN, data.getPlmn());
+            Intent intent = new Intent(TelephonyManager.ACTION_SERVICE_PROVIDERS_UPDATED);
+            intent.putExtra(TelephonyManager.EXTRA_SHOW_SPN, data.shouldShowSpn());
+            intent.putExtra(TelephonyManager.EXTRA_SPN, data.getSpn());
+            intent.putExtra(TelephonyManager.EXTRA_DATA_SPN, data.getDataSpn());
+            intent.putExtra(TelephonyManager.EXTRA_SHOW_PLMN, data.shouldShowPlmn());
+            intent.putExtra(TelephonyManager.EXTRA_PLMN, data.getPlmn());
             SubscriptionManager.putPhoneIdAndSubIdExtra(intent, mPhone.getPhoneId());
             mPhone.getContext().sendStickyBroadcastAsUser(intent, UserHandle.ALL);
 
@@ -2658,12 +2727,14 @@
                 final boolean forceDisplayNoService = shouldForceDisplayNoService() && !mIsSimReady;
                 if (!forceDisplayNoService && Phone.isEmergencyCallOnly()) {
                     // No service but emergency call allowed
-                    plmn = Resources.getSystem().
-                            getText(com.android.internal.R.string.emergency_calls_only).toString();
+                    plmn = Resources.getSystem()
+                            .getText(com.android.internal.R.string.emergency_calls_only).toString();
                 } else {
                     // No service at all
-                    plmn = Resources.getSystem().
-                            getText(com.android.internal.R.string.lockscreen_carrier_default).toString();
+                    plmn = Resources.getSystem()
+                            .getText(
+                                com.android.internal.R.string.lockscreen_carrier_default)
+                            .toString();
                     noService = true;
                 }
                 if (DBG) log("updateSpnDisplay: radio is on but out " +
@@ -2678,8 +2749,9 @@
             } else {
                 // Power off state, such as airplane mode, show plmn as "No service"
                 showPlmn = true;
-                plmn = Resources.getSystem().
-                        getText(com.android.internal.R.string.lockscreen_carrier_default).toString();
+                plmn = Resources.getSystem()
+                        .getText(com.android.internal.R.string.lockscreen_carrier_default)
+                        .toString();
                 if (DBG) log("updateSpnDisplay: radio is off w/ showPlmn="
                         + showPlmn + " plmn=" + plmn);
             }
@@ -2701,7 +2773,7 @@
                 // is satisfied or SPN override is enabled for this carrier.
 
                 // Handle Flight Mode
-                if (mSS.getVoiceRegState() == ServiceState.STATE_POWER_OFF) {
+                if (mSS.getState() == ServiceState.STATE_POWER_OFF) {
                     wfcVoiceSpnFormat = wfcFlightSpnFormat;
                 }
 
@@ -2713,8 +2785,15 @@
             } else if (!TextUtils.isEmpty(plmn) && !TextUtils.isEmpty(wfcVoiceSpnFormat)) {
                 // Show PLMN + Wi-Fi Calling if there is no valid SPN in the above case
                 String originalPlmn = plmn.trim();
+
+                PersistableBundle config = getCarrierConfig();
+                if (mIccRecords != null && config.getBoolean(
+                        CarrierConfigManager.KEY_WFC_CARRIER_NAME_OVERRIDE_BY_PNN_BOOL)) {
+                    originalPlmn = mIccRecords.getPnnHomeName();
+                }
+
                 plmn = String.format(wfcVoiceSpnFormat, originalPlmn);
-            } else if (mSS.getVoiceRegState() == ServiceState.STATE_POWER_OFF
+            } else if (mSS.getState() == ServiceState.STATE_POWER_OFF
                     || (showPlmn && TextUtils.equals(spn, plmn))) {
                 // airplane mode or spn equals plmn, do not show spn
                 spn = null;
@@ -2767,11 +2846,7 @@
     }
 
     /**
-     * Checks whether force to display "no service" to the user based on the current country.
-     *
-     * This method should only be used when SIM is unready.
-     *
-     * @return {@code True} if "no service" should be displayed.
+     * Returns whether out-of-service will be displayed as "no service" to the user.
      */
     public boolean shouldForceDisplayNoService() {
         String[] countriesWithNoService = mPhone.getContext().getResources().getStringArray(
@@ -2789,6 +2864,11 @@
     }
 
     protected void setPowerStateToDesired() {
+        setPowerStateToDesired(false, false, false);
+    }
+
+    protected void setPowerStateToDesired(boolean forEmergencyCall,
+            boolean isSelectedPhoneForEmergencyCall, boolean forceApply) {
         if (DBG) {
             String tmpLog = "mDeviceShuttingDown=" + mDeviceShuttingDown +
                     ", mDesiredPowerState=" + mDesiredPowerState +
@@ -2810,8 +2890,8 @@
 
         // If we want it on and it's off, turn it on
         if (mDesiredPowerState && !mRadioDisabledByCarrier
-                && mCi.getRadioState() == TelephonyManager.RADIO_POWER_OFF) {
-            mCi.setRadioPower(true, null);
+                && (forceApply || mCi.getRadioState() == TelephonyManager.RADIO_POWER_OFF)) {
+            mCi.setRadioPower(true, forEmergencyCall, isSelectedPhoneForEmergencyCall, null);
         } else if ((!mDesiredPowerState || mRadioDisabledByCarrier) && mCi.getRadioState()
                 == TelephonyManager.RADIO_POWER_ON) {
             // If it's on and available and we want it off gracefully
@@ -2917,7 +2997,7 @@
      */
     @UnsupportedAppUsage
     public int getCurrentDataConnectionState() {
-        return mSS.getDataRegState();
+        return mSS.getDataRegistrationState();
     }
 
     /**
@@ -2934,7 +3014,7 @@
             // There are cases where we we would setup data connection even data is not yet
             // attached. In these cases we check voice rat.
             if (radioTechnology == ServiceState.RIL_RADIO_TECHNOLOGY_UNKNOWN
-                    && mSS.getDataRegState() != ServiceState.STATE_IN_SERVICE) {
+                    && mSS.getDataRegistrationState() != ServiceState.STATE_IN_SERVICE) {
                 radioTechnology = mSS.getRilVoiceRadioTechnology();
             }
             // Concurrent voice and data is not allowed for 2G technologies. It's allowed in other
@@ -2989,18 +3069,10 @@
      */
     @UnsupportedAppUsage
     public void pollState() {
-        pollState(false);
-    }
-    /**
-     * We insist on polling even if the radio says its off.
-     * Used when we get a network changed notification
-     * but the radio is off - part of iwlan hack
-     */
-    private void modemTriggeredPollState() {
-        pollState(true);
+        sendEmptyMessage(EVENT_POLL_STATE_REQUEST);
     }
 
-    public void pollState(boolean modemTriggered) {
+    private void pollStateInternal(boolean modemTriggered) {
         mPollingContext = new int[1];
         mPollingContext[0] = 0;
 
@@ -3009,17 +3081,17 @@
         switch (mCi.getRadioState()) {
             case TelephonyManager.RADIO_POWER_UNAVAILABLE:
                 mNewSS.setStateOutOfService();
-                mNewCellIdentity = null;
                 setSignalStrengthDefaultValues();
-                mNitzState.handleNetworkCountryCodeUnavailable();
+                mLastNitzData = null;
+                mNitzState.handleNetworkUnavailable();
                 pollStateDone();
                 break;
 
             case TelephonyManager.RADIO_POWER_OFF:
                 mNewSS.setStateOff();
-                mNewCellIdentity = null;
                 setSignalStrengthDefaultValues();
-                mNitzState.handleNetworkCountryCodeUnavailable();
+                mLastNitzData = null;
+                mNitzState.handleNetworkUnavailable();
                 // don't poll when device is shutting down or the poll was not modemTrigged
                 // (they sent us new radio data) and current network is not IWLAN
                 if (mDeviceShuttingDown ||
@@ -3063,19 +3135,48 @@
         }
     }
 
+    /**
+     * Get the highest-priority CellIdentity for a provided ServiceState.
+     *
+     * Choose a CellIdentity for ServiceState using the following rules:
+     * 1) WWAN only (WLAN is excluded)
+     * 2) Registered > Camped
+     * 3) CS > PS
+     *
+     * @param ss a Non-Null ServiceState object
+     *
+     * @return a list of CellIdentity objects in *decreasing* order of preference.
+     */
+    @VisibleForTesting public static @NonNull List<CellIdentity> getPrioritizedCellIdentities(
+            @NonNull final ServiceState ss) {
+        final List<NetworkRegistrationInfo> regInfos = ss.getNetworkRegistrationInfoList();
+        if (regInfos.isEmpty()) return Collections.emptyList();
+
+        return regInfos.stream()
+            .filter(nri -> nri.getCellIdentity() != null)
+            .filter(nri -> nri.getTransportType() == AccessNetworkConstants.TRANSPORT_TYPE_WWAN)
+            .sorted(Comparator
+                    .comparing(NetworkRegistrationInfo::isRegistered)
+                    .thenComparing((nri) -> nri.getDomain() & NetworkRegistrationInfo.DOMAIN_CS)
+                    .reversed())
+            .map(nri -> nri.getCellIdentity())
+            .distinct()
+            .collect(Collectors.toList());
+    }
+
     private void pollStateDone() {
         if (!mPhone.isPhoneTypeGsm()) {
             updateRoamingState();
         }
 
-        if (Build.IS_DEBUGGABLE && SystemProperties.getBoolean(PROP_FORCE_ROAMING, false)) {
-            mNewSS.setVoiceRoaming(true);
-            mNewSS.setDataRoaming(true);
+        if (TelephonyUtils.IS_DEBUGGABLE
+                && SystemProperties.getBoolean(PROP_FORCE_ROAMING, false)) {
+            mNewSS.setRoaming(true);
         }
         useDataRegStateForDataOnlyDevices();
         processIwlanRegistrationInfo();
 
-        if (Build.IS_DEBUGGABLE && mPhone.mTelephonyTester != null) {
+        if (TelephonyUtils.IS_DEBUGGABLE && mPhone.mTelephonyTester != null) {
             mPhone.mTelephonyTester.overrideServiceState(mNewSS);
         }
 
@@ -3089,16 +3190,16 @@
         }
 
         boolean hasRegistered =
-                mSS.getVoiceRegState() != ServiceState.STATE_IN_SERVICE
-                        && mNewSS.getVoiceRegState() == ServiceState.STATE_IN_SERVICE;
+                mSS.getState() != ServiceState.STATE_IN_SERVICE
+                        && mNewSS.getState() == ServiceState.STATE_IN_SERVICE;
 
         boolean hasDeregistered =
-                mSS.getVoiceRegState() == ServiceState.STATE_IN_SERVICE
-                        && mNewSS.getVoiceRegState() != ServiceState.STATE_IN_SERVICE;
+                mSS.getState() == ServiceState.STATE_IN_SERVICE
+                        && mNewSS.getState() != ServiceState.STATE_IN_SERVICE;
 
         boolean hasAirplaneModeOnChanged =
-                mSS.getVoiceRegState() != ServiceState.STATE_POWER_OFF
-                        && mNewSS.getVoiceRegState() == ServiceState.STATE_POWER_OFF;
+                mSS.getState() != ServiceState.STATE_POWER_OFF
+                        && mNewSS.getState() == ServiceState.STATE_POWER_OFF;
 
         SparseBooleanArray hasDataAttached = new SparseBooleanArray(
                 mTransportManager.getAvailableTransports().length);
@@ -3110,6 +3211,10 @@
                 mTransportManager.getAvailableTransports().length);
         boolean anyDataRegChanged = false;
         boolean anyDataRatChanged = false;
+        boolean hasAlphaRawChanged =
+                mSS.getOperatorAlphaLongRaw() != mNewSS.getOperatorAlphaLongRaw()
+                        || mSS.getOperatorAlphaShortRaw() != mNewSS.getOperatorAlphaShortRaw();
+
         for (int transport : mTransportManager.getAvailableTransports()) {
             NetworkRegistrationInfo oldNrs = mSS.getNetworkRegistrationInfo(
                     NetworkRegistrationInfo.DOMAIN_PS, transport);
@@ -3133,7 +3238,16 @@
                     : TelephonyManager.NETWORK_TYPE_UNKNOWN;
             int newRAT = newNrs != null ? newNrs.getAccessNetworkTechnology()
                     : TelephonyManager.NETWORK_TYPE_UNKNOWN;
-            hasRilDataRadioTechnologyChanged.put(transport, oldRAT != newRAT);
+
+            boolean isOldCA = oldNrs != null ? (oldNrs.getDataSpecificInfo() != null
+                    ? oldNrs.getDataSpecificInfo().isUsingCarrierAggregation() : false) : false;
+            boolean isNewCA = newNrs != null ? (newNrs.getDataSpecificInfo() != null
+                    ? newNrs.getDataSpecificInfo().isUsingCarrierAggregation() : false) : false;
+
+            // If the carrier enable KEY_SHOW_CARRIER_DATA_ICON_PATTERN_STRING and the operator name
+            // match this pattern, the data rat display LteAdvanced indicator.
+            hasRilDataRadioTechnologyChanged.put(transport,
+                    oldRAT != newRAT || isOldCA != isNewCA || hasAlphaRawChanged);
             if (oldRAT != newRAT) {
                 anyDataRatChanged = true;
             }
@@ -3154,26 +3268,25 @@
                 && (mSS.getRilDataRadioTechnology() != mNewSS.getRilDataRadioTechnology());
 
         boolean hasVoiceRegStateChanged =
-                mSS.getVoiceRegState() != mNewSS.getVoiceRegState();
+                mSS.getState() != mNewSS.getState();
 
         boolean hasNrFrequencyRangeChanged =
                 mSS.getNrFrequencyRange() != mNewSS.getNrFrequencyRange();
 
-        boolean hasNrStateChanged = isNrStateChanged(
-                mSS.getNetworkRegistrationInfo(
-                        NetworkRegistrationInfo.DOMAIN_PS, AccessNetworkType.EUTRAN),
-                mNewSS.getNetworkRegistrationInfo(
-                        NetworkRegistrationInfo.DOMAIN_PS, AccessNetworkType.EUTRAN));
+        boolean hasNrStateChanged = mSS.getNrState() != mNewSS.getNrState();
 
-        // TODO: loosen this restriction to exempt fields that are provided through system
-        // information; otherwise, we will get false positives when things like the operator
-        // alphas are provided later - that's better than missing location changes, but
-        // still not ideal.
-        boolean hasLocationChanged = !Objects.equals(mNewCellIdentity, mCellIdentity);
+        final List<CellIdentity> prioritizedCids = getPrioritizedCellIdentities(mNewSS);
+
+        final CellIdentity primaryCellIdentity = prioritizedCids.isEmpty()
+                ? null : prioritizedCids.get(0);
+
+        boolean hasLocationChanged = mCellIdentity == null
+                ? primaryCellIdentity != null : !mCellIdentity.isSameCell(primaryCellIdentity);
 
         // ratchet the new tech up through its rat family but don't drop back down
         // until cell change or device is OOS
-        boolean isDataInService = mNewSS.getDataRegState() == ServiceState.STATE_IN_SERVICE;
+        boolean isDataInService = mNewSS.getDataRegistrationState()
+                == ServiceState.STATE_IN_SERVICE;
         if (isDataInService) {
             mRatRatcheter.ratchet(mSS, mNewSS, hasLocationChanged);
         }
@@ -3199,28 +3312,21 @@
         boolean hasMultiApnSupport = false;
         boolean hasLostMultiApnSupport = false;
         if (mPhone.isPhoneTypeCdmaLte()) {
-            has4gHandoff = mNewSS.getDataRegState() == ServiceState.STATE_IN_SERVICE
-                    && ((ServiceState.isLte(mSS.getRilDataRadioTechnology())
-                    && (mNewSS.getRilDataRadioTechnology()
-                    == ServiceState.RIL_RADIO_TECHNOLOGY_EHRPD))
-                    ||
-                    ((mSS.getRilDataRadioTechnology()
-                            == ServiceState.RIL_RADIO_TECHNOLOGY_EHRPD)
-                            && ServiceState.isLte(mNewSS.getRilDataRadioTechnology())));
+            final int wwanDataRat = getRilDataRadioTechnologyForWwan(mSS);
+            final int newWwanDataRat = getRilDataRadioTechnologyForWwan(mNewSS);
+            has4gHandoff = mNewSS.getDataRegistrationState() == ServiceState.STATE_IN_SERVICE
+                    && ((ServiceState.isPsOnlyTech(wwanDataRat)
+                    && (newWwanDataRat == ServiceState.RIL_RADIO_TECHNOLOGY_EHRPD))
+                    || ((wwanDataRat == ServiceState.RIL_RADIO_TECHNOLOGY_EHRPD)
+                    && ServiceState.isPsOnlyTech(newWwanDataRat)));
 
-            hasMultiApnSupport = ((ServiceState.isLte(mNewSS.getRilDataRadioTechnology())
-                    || (mNewSS.getRilDataRadioTechnology()
-                    == ServiceState.RIL_RADIO_TECHNOLOGY_EHRPD))
-                    &&
-                    (!ServiceState.isLte(mSS.getRilDataRadioTechnology())
-                            && (mSS.getRilDataRadioTechnology()
-                            != ServiceState.RIL_RADIO_TECHNOLOGY_EHRPD)));
+            hasMultiApnSupport = ((ServiceState.isPsOnlyTech(newWwanDataRat)
+                    || (newWwanDataRat == ServiceState.RIL_RADIO_TECHNOLOGY_EHRPD))
+                    && (!ServiceState.isPsOnlyTech(wwanDataRat)
+                    && (wwanDataRat != ServiceState.RIL_RADIO_TECHNOLOGY_EHRPD)));
 
-            hasLostMultiApnSupport =
-                    ((mNewSS.getRilDataRadioTechnology()
-                            >= ServiceState.RIL_RADIO_TECHNOLOGY_IS95A)
-                            && (mNewSS.getRilDataRadioTechnology()
-                            <= ServiceState.RIL_RADIO_TECHNOLOGY_EVDO_A));
+            hasLostMultiApnSupport = ((newWwanDataRat >= ServiceState.RIL_RADIO_TECHNOLOGY_IS95A)
+                    && (newWwanDataRat <= ServiceState.RIL_RADIO_TECHNOLOGY_EVDO_A));
         }
 
         if (DBG) {
@@ -3252,8 +3358,8 @@
         if (hasVoiceRegStateChanged || anyDataRegChanged) {
             EventLog.writeEvent(mPhone.isPhoneTypeGsm() ? EventLogTags.GSM_SERVICE_STATE_CHANGE :
                             EventLogTags.CDMA_SERVICE_STATE_CHANGE,
-                    mSS.getVoiceRegState(), mSS.getDataRegState(),
-                    mNewSS.getVoiceRegState(), mNewSS.getDataRegState());
+                    mSS.getState(), mSS.getDataRegistrationState(),
+                    mNewSS.getState(), mNewSS.getDataRegistrationState());
         }
 
         if (mPhone.isPhoneTypeGsm()) {
@@ -3261,7 +3367,7 @@
             // TODO: we may add filtering to reduce the event logged,
             // i.e. check preferred network setting, only switch to 2G, etc
             if (hasRilVoiceRadioTechnologyChanged) {
-                int cid = getCidFromCellIdentity(mNewCellIdentity);
+                int cid = getCidFromCellIdentity(primaryCellIdentity);
                 // NOTE: this code was previously located after mSS and mNewSS are swapped, so
                 // existing logs were incorrectly using the new state for "network_from"
                 // and STATE_OUT_OF_SERVICE for "network_to". To avoid confusion, use a new log tag
@@ -3280,7 +3386,7 @@
             }
 
             if (hasCssIndicatorChanged) {
-                mPhone.notifyDataConnection();
+                mPhone.notifyAllActiveDataConnections();
             }
 
             mReasonDataDenied = mNewReasonDataDenied;
@@ -3297,10 +3403,7 @@
         // clean slate for next time
         mNewSS.setStateOutOfService();
 
-        // swap mCellIdentity and mNewCellIdentity to put new state in mCellIdentity
-        CellIdentity tempCellId = mCellIdentity;
-        mCellIdentity = mNewCellIdentity;
-        mNewCellIdentity = tempCellId;
+        mCellIdentity = primaryCellIdentity;
 
         if (hasRilVoiceRadioTechnologyChanged) {
             updatePhoneObject();
@@ -3310,7 +3413,7 @@
                 Context.TELEPHONY_SERVICE);
         if (anyDataRatChanged) {
             tm.setDataNetworkTypeForPhone(mPhone.getPhoneId(), mSS.getRilDataRadioTechnology());
-            StatsLog.write(StatsLog.MOBILE_RADIO_TECHNOLOGY_CHANGED,
+            TelephonyStatsLog.write(TelephonyStatsLog.MOBILE_RADIO_TECHNOLOGY_CHANGED,
                     ServiceState.rilRadioTechnologyToNetworkType(
                             mSS.getRilDataRadioTechnology()), mPhone.getPhoneId());
         }
@@ -3322,6 +3425,7 @@
 
         if (hasDeregistered) {
             mNetworkDetachedRegistrants.notifyRegistrants();
+            mNitzState.handleNetworkUnavailable();
         }
 
         if (hasRejectCauseChanged) {
@@ -3358,10 +3462,13 @@
             // incomplete.
             // CellIdentity can return a null MCC and MNC in CDMA
             String localeOperator = operatorNumeric;
-            if (isInvalidOperatorNumeric(operatorNumeric) && (mCellIdentity != null)
-                    && mCellIdentity.getMccString() != null
-                    && mCellIdentity.getMncString() != null) {
-                localeOperator = mCellIdentity.getMccString() + mCellIdentity.getMncString();
+            if (isInvalidOperatorNumeric(operatorNumeric)) {
+                for (CellIdentity cid : prioritizedCids) {
+                    if (!TextUtils.isEmpty(cid.getPlmn())) {
+                        localeOperator = cid.getPlmn();
+                        break;
+                    }
+                }
             }
 
             if (isInvalidOperatorNumeric(localeOperator)) {
@@ -3370,16 +3477,10 @@
                 // operator numeric in locale tracker is null. The async update will allow getting
                 // cell info from the modem instead of using the cached one.
                 mLocaleTracker.updateOperatorNumeric("");
-            } else if (mSS.getRilDataRadioTechnology() != ServiceState.RIL_RADIO_TECHNOLOGY_IWLAN) {
-                // If the device is on IWLAN, modems manufacture a ServiceState with the MCC/MNC of
-                // the SIM as if we were talking to towers. Telephony code then uses that with
-                // mccTable to suggest a timezone. We shouldn't do that if the MCC/MNC is from IWLAN
-
-                // Update IDD.
+            } else {
                 if (!mPhone.isPhoneTypeGsm()) {
                     setOperatorIdd(localeOperator);
                 }
-
                 mLocaleTracker.updateOperatorNumeric(localeOperator);
             }
 
@@ -3399,8 +3500,11 @@
             mPhone.getContext().getContentResolver()
                     .insert(getUriForSubscriptionId(mPhone.getSubId()),
                             getContentValuesForServiceState(mSS));
+        }
 
+        if (hasChanged || hasNrStateChanged) {
             TelephonyMetrics.getInstance().writeServiceStateChanged(mPhone.getPhoneId(), mSS);
+            mPhone.getVoiceCallSessionStats().onServiceStateChanged(mSS);
         }
 
         boolean shouldLogAttachedChange = false;
@@ -3432,8 +3536,9 @@
                     // Update all transports if preference changed so that consumers can be notified
                     // that ServiceState#getRilDataRadioTechnology has changed.
                     || hasDataTransportPreferenceChanged) {
+                setDataNetworkTypeForPhone(mSS.getRilDataRadioTechnology());
                 notifyDataRegStateRilRadioTechnologyChanged(transport);
-                mPhone.notifyDataConnection();
+                mPhone.notifyAllActiveDataConnections();
             }
 
             if (hasDataAttached.get(transport)) {
@@ -3482,11 +3587,19 @@
         }
 
         if (hasLocationChanged) {
-            mPhone.notifyLocationChanged(getCellLocation());
+            mPhone.notifyLocationChanged(getCellIdentity());
+        }
+
+        if (hasNrStateChanged) {
+            mNrStateChangedRegistrants.notifyRegistrants();
+        }
+
+        if (hasNrFrequencyRangeChanged) {
+            mNrFrequencyChangedRegistrants.notifyRegistrants();
         }
 
         if (mPhone.isPhoneTypeGsm()) {
-            if (!isGprsConsistent(mSS.getDataRegState(), mSS.getVoiceRegState())) {
+            if (!isGprsConsistent(mSS.getDataRegistrationState(), mSS.getState())) {
                 if (!mStartedGprsRegCheck && !mReportedGprsNoReg) {
                     mStartedGprsRegCheck = true;
 
@@ -3509,7 +3622,7 @@
             if ((mCi.getRadioState() == TelephonyManager.RADIO_POWER_ON)
                     && (!mIsSubscriptionFromRuim)) {
                 // Now the Phone sees the new ServiceState so it can get the new ERI text
-                if (mSS.getVoiceRegState() == ServiceState.STATE_IN_SERVICE) {
+                if (mSS.getState() == ServiceState.STATE_IN_SERVICE) {
                     eriText = mPhone.getCdmaEriText();
                 } else {
                     // Note that ServiceState.STATE_OUT_OF_SERVICE is valid used for
@@ -3523,22 +3636,22 @@
                     mUiccController.getUiccCard(getPhoneId()).getOperatorBrandOverride() != null;
             if (!hasBrandOverride && (mCi.getRadioState() == TelephonyManager.RADIO_POWER_ON)
                     && (mEriManager.isEriFileLoaded())
-                    && (!ServiceState.isLte(mSS.getRilVoiceRadioTechnology())
+                    && (!ServiceState.isPsOnlyTech(mSS.getRilVoiceRadioTechnology())
                     || mPhone.getContext().getResources().getBoolean(com.android.internal.R
                     .bool.config_LTE_eri_for_network_name))) {
                 // Only when CDMA is in service, ERI will take effect
                 eriText = mSS.getOperatorAlpha();
                 // Now the Phone sees the new ServiceState so it can get the new ERI text
-                if (mSS.getVoiceRegState() == ServiceState.STATE_IN_SERVICE) {
+                if (mSS.getState() == ServiceState.STATE_IN_SERVICE) {
                     eriText = mPhone.getCdmaEriText();
-                } else if (mSS.getVoiceRegState() == ServiceState.STATE_POWER_OFF) {
+                } else if (mSS.getState() == ServiceState.STATE_POWER_OFF) {
                     eriText = getServiceProviderName();
                     if (TextUtils.isEmpty(eriText)) {
                         // Sets operator alpha property by retrieving from
                         // build-time system property
                         eriText = SystemProperties.get("ro.cdma.home.operator.alpha");
                     }
-                } else if (mSS.getDataRegState() != ServiceState.STATE_IN_SERVICE) {
+                } else if (mSS.getDataRegistrationState() != ServiceState.STATE_IN_SERVICE) {
                     // Note that ServiceState.STATE_OUT_OF_SERVICE is valid used
                     // for mRegistrationState 0,2,3 and 4
                     eriText = mPhone.getContext()
@@ -3548,7 +3661,7 @@
 
             if (mUiccApplcation != null && mUiccApplcation.getState() == AppState.APPSTATE_READY &&
                     mIccRecords != null && getCombinedRegState(mSS) == ServiceState.STATE_IN_SERVICE
-                    && !ServiceState.isLte(mSS.getRilVoiceRadioTechnology())) {
+                    && !ServiceState.isPsOnlyTech(mSS.getRilVoiceRadioTechnology())) {
                 // SIM is found on the device. If ERI roaming is OFF, and SID/NID matches
                 // one configured in SIM, use operator name from CSIM record. Note that ERI, SID,
                 // and NID are CDMA only, not applicable to LTE.
@@ -3687,16 +3800,19 @@
 
     @UnsupportedAppUsage
     protected void setOperatorIdd(String operatorNumeric) {
+        if (mPhone.getUnitTestMode()) {
+            return;
+        }
+
         // Retrieve the current country information
-        // with the MCC got from opeatorNumeric.
+        // with the MCC got from operatorNumeric.
         String idd = mHbpcdUtils.getIddByMcc(
                 Integer.parseInt(operatorNumeric.substring(0,3)));
         if (idd != null && !idd.isEmpty()) {
-            mPhone.setGlobalSystemProperty(TelephonyProperties.PROPERTY_OPERATOR_IDP_STRING,
-                    idd);
+            TelephonyProperties.operator_idp_string(idd);
         } else {
             // use default "+", since we don't know the current IDP
-            mPhone.setGlobalSystemProperty(TelephonyProperties.PROPERTY_OPERATOR_IDP_STRING, "+");
+            TelephonyProperties.operator_idp_string("+");
         }
     }
 
@@ -3713,39 +3829,21 @@
             return operatorNumeric;
         }
 
-        // resolve the mcc from sid;
-        // if mNitzState.getSavedTimeZoneId() is null, TimeZone would get the default timeZone,
-        // and the mNitzState.fixTimeZone() couldn't help, because it depends on operator Numeric;
-        // if the sid is conflict and timezone is unavailable, the mcc may be not right.
-        boolean isNitzTimeZone;
-        TimeZone tzone;
-        if (mNitzState.getSavedTimeZoneId() != null) {
-            tzone = TimeZone.getTimeZone(mNitzState.getSavedTimeZoneId());
-            isNitzTimeZone = true;
-        } else {
-            NitzData lastNitzData = mNitzState.getCachedNitzData();
-            if (lastNitzData == null) {
-                tzone = null;
-            } else {
-                tzone = TimeZoneLookupHelper.guessZoneByNitzStatic(lastNitzData);
-                if (ServiceStateTracker.DBG) {
-                    log("fixUnknownMcc(): guessNitzTimeZone returned "
-                            + (tzone == null ? tzone : tzone.getID()));
-                }
-            }
-            isNitzTimeZone = false;
-        }
-
+        // resolve the mcc from sid, using time zone information from the latest NITZ signal when
+        // available.
         int utcOffsetHours = 0;
-        if (tzone != null) {
-            utcOffsetHours = tzone.getRawOffset() / MS_PER_HOUR;
+        boolean isDst = false;
+        boolean isNitzTimeZone = false;
+        NitzData lastNitzData = mLastNitzData;
+        if (lastNitzData != null) {
+            utcOffsetHours = lastNitzData.getLocalOffsetMillis() / MS_PER_HOUR;
+            Integer dstAdjustmentMillis = lastNitzData.getDstAdjustmentMillis();
+            isDst = (dstAdjustmentMillis != null) && (dstAdjustmentMillis != 0);
+            isNitzTimeZone = true;
         }
-
-        NitzData nitzData = mNitzState.getCachedNitzData();
-        boolean isDst = nitzData != null && nitzData.isDst();
         int mcc = mHbpcdUtils.getMcc(sid, utcOffsetHours, (isDst ? 1 : 0), isNitzTimeZone);
         if (mcc > 0) {
-            operatorNumeric = Integer.toString(mcc) + DEFAULT_MNC;
+            operatorNumeric = mcc + DEFAULT_MNC;
         }
         return operatorNumeric;
     }
@@ -3974,9 +4072,9 @@
     }
 
     /**
-     * Get CellLocation from the ServiceState if available or guess from cached CellInfo
+     * Get CellIdentity from the ServiceState if available or guess from cached
      *
-     * Get the CellLocation by first checking if ServiceState has a current CID. If so
+     * Get the CellIdentity by first checking if ServiceState has a current CID. If so
      * then return that info. Otherwise, check the latest List<CellInfo> and return the first GSM or
      * WCDMA result that appears. If no GSM or WCDMA results, then return an LTE result. The
      * behavior is kept consistent for backwards compatibility; (do not apply logic to determine
@@ -3984,18 +4082,19 @@
      *
      * @return the current cell location if known or a non-null "empty" cell location
      */
-    public CellLocation getCellLocation() {
-        if (mCellIdentity != null) return mCellIdentity.asCellLocation();
+    @NonNull
+    public CellIdentity getCellIdentity() {
+        if (mCellIdentity != null) return mCellIdentity;
 
-        CellLocation cl = getCellLocationFromCellInfo(getAllCellInfo());
-        if (cl != null) return cl;
+        CellIdentity ci = getCellIdentityFromCellInfo(getAllCellInfo());
+        if (ci != null) return ci;
 
         return mPhone.getPhoneType() == PhoneConstants.PHONE_TYPE_CDMA
-                ? new CdmaCellLocation() : new GsmCellLocation();
+                ? new CellIdentityCdma() : new CellIdentityGsm();
     }
 
     /**
-     * Get CellLocation from the ServiceState if available or guess from CellInfo
+     * Get CellIdentity from the ServiceState if available or guess from CellInfo
      *
      * Get the CellLocation by first checking if ServiceState has a current CID. If so
      * then return that info. Otherwise, query AllCellInfo and return the first GSM or
@@ -4006,9 +4105,9 @@
      * @param workSource calling WorkSource
      * @param rspMsg the response message which must be non-null
      */
-    public void requestCellLocation(WorkSource workSource, Message rspMsg) {
+    public void requestCellIdentity(WorkSource workSource, Message rspMsg) {
         if (mCellIdentity != null) {
-            AsyncResult.forMessage(rspMsg, mCellIdentity.asCellLocation(), null);
+            AsyncResult.forMessage(rspMsg, mCellIdentity, null);
             rspMsg.sendToTarget();
             return;
         }
@@ -4017,16 +4116,16 @@
         requestAllCellInfo(workSource, cellLocRsp);
     }
 
-    /* Find and return a CellLocation from CellInfo
+    /* Find and return a CellIdentity from CellInfo
      *
      * This method returns the first GSM or WCDMA result that appears in List<CellInfo>. If no GSM
      * or  WCDMA results are found, then it returns an LTE result. The behavior is kept consistent
      * for backwards compatibility; (do not apply logic to determine why the behavior is this way).
      *
-     * @return the current cell location from CellInfo or null
+     * @return the current CellIdentity from CellInfo or null
      */
-    private static CellLocation getCellLocationFromCellInfo(List<CellInfo> info) {
-        CellLocation cl = null;
+    private static CellIdentity getCellIdentityFromCellInfo(List<CellInfo> info) {
+        CellIdentity cl = null;
         if (info != null && info.size() > 0) {
             CellIdentity fallbackLteCid = null; // We prefer not to use LTE
             for (CellInfo ci : info) {
@@ -4036,12 +4135,12 @@
                     continue;
                 }
                 if (getCidFromCellIdentity(c) != -1) {
-                    cl = c.asCellLocation();
+                    cl = c;
                     break;
                 }
             }
             if (cl == null && fallbackLteCid != null) {
-                cl = fallbackLteCid.asCellLocation();
+                cl = fallbackLteCid;
             }
         }
         return cl;
@@ -4057,6 +4156,7 @@
                     + " start=" + start + " delay=" + (start - nitzReceiveTime));
         }
         NitzData newNitzData = NitzData.parse(nitzString);
+        mLastNitzData = newNitzData;
         if (newNitzData != null) {
             try {
                 TimestampedValue<NitzData> nitzSignal =
@@ -4105,7 +4205,8 @@
         Context context = mPhone.getContext();
 
         SubscriptionInfo info = mSubscriptionController
-                .getActiveSubscriptionInfo(mPhone.getSubId(), context.getOpPackageName());
+                .getActiveSubscriptionInfo(mPhone.getSubId(), context.getOpPackageName(),
+                        context.getAttributionTag());
 
         //if subscription is part of a group and non-primary, suppress all notifications
         if (info == null || (info.isOpportunistic() && info.getGroupUuid() != null)) {
@@ -4222,7 +4323,7 @@
                 .setContentTitle(title)
                 .setStyle(new Notification.BigTextStyle().bigText(details))
                 .setContentText(details)
-                .setChannel(NotificationChannelController.CHANNEL_ID_ALERT)
+                .setChannelId(NotificationChannelController.CHANNEL_ID_ALERT)
                 .build();
 
         NotificationManager notificationManager = (NotificationManager)
@@ -4439,7 +4540,10 @@
             mDataRegStateOrRatChangedRegistrants.put(transport, new RegistrantList());
         }
         mDataRegStateOrRatChangedRegistrants.get(transport).add(r);
-        notifyDataRegStateRilRadioTechnologyChanged(transport);
+        Pair<Integer, Integer> registrationInfo = getRegistrationInfo(transport);
+        if (registrationInfo != null) {
+            r.notifyResult(registrationInfo);
+        }
     }
 
     /**
@@ -4464,7 +4568,7 @@
         Registrant r = new Registrant(h, what, obj);
 
         mNetworkAttachedRegistrants.add(r);
-        if (mSS.getVoiceRegState() == ServiceState.STATE_IN_SERVICE) {
+        if (mSS.getState() == ServiceState.STATE_IN_SERVICE) {
             r.notifyRegistrant();
         }
     }
@@ -4483,7 +4587,7 @@
         Registrant r = new Registrant(h, what, obj);
 
         mNetworkDetachedRegistrants.add(r);
-        if (mSS.getVoiceRegState() != ServiceState.STATE_IN_SERVICE) {
+        if (mSS.getState() != ServiceState.STATE_IN_SERVICE) {
             r.notifyRegistrant();
         }
     }
@@ -4714,7 +4818,7 @@
         // Sometimes the network registration information comes before carrier config is ready.
         // For some cases like roaming/non-roaming overriding, we need carrier config. So it's
         // important to poll state again when carrier config is ready.
-        pollState();
+        pollStateInternal(false);
     }
 
     private void updateLteEarfcnLists(PersistableBundle config) {
@@ -4728,12 +4832,44 @@
     }
 
     private void updateReportingCriteria(PersistableBundle config) {
-        mPhone.setSignalStrengthReportingCriteria(
+        int lteMeasurementEnabled = config.getInt(CarrierConfigManager
+                .KEY_PARAMETERS_USED_FOR_LTE_SIGNAL_BAR_INT, CellSignalStrengthLte.USE_RSRP);
+        mPhone.setSignalStrengthReportingCriteria(SignalThresholdInfo.SIGNAL_RSRP,
                 config.getIntArray(CarrierConfigManager.KEY_LTE_RSRP_THRESHOLDS_INT_ARRAY),
-                AccessNetworkType.EUTRAN);
-        mPhone.setSignalStrengthReportingCriteria(
+                AccessNetworkType.EUTRAN,
+                (lteMeasurementEnabled & CellSignalStrengthLte.USE_RSRP) != 0);
+        mPhone.setSignalStrengthReportingCriteria(SignalThresholdInfo.SIGNAL_RSCP,
                 config.getIntArray(CarrierConfigManager.KEY_WCDMA_RSCP_THRESHOLDS_INT_ARRAY),
-                AccessNetworkType.UTRAN);
+                AccessNetworkType.UTRAN, true);
+        mPhone.setSignalStrengthReportingCriteria(SignalThresholdInfo.SIGNAL_RSSI,
+                config.getIntArray(CarrierConfigManager.KEY_GSM_RSSI_THRESHOLDS_INT_ARRAY),
+                AccessNetworkType.GERAN, true);
+
+        if (mPhone.getHalVersion().greaterOrEqual(RIL.RADIO_HAL_VERSION_1_5)) {
+            mPhone.setSignalStrengthReportingCriteria(SignalThresholdInfo.SIGNAL_RSRQ,
+                    config.getIntArray(CarrierConfigManager.KEY_LTE_RSRQ_THRESHOLDS_INT_ARRAY),
+                    AccessNetworkType.EUTRAN,
+                    (lteMeasurementEnabled & CellSignalStrengthLte.USE_RSRQ) != 0);
+            mPhone.setSignalStrengthReportingCriteria(SignalThresholdInfo.SIGNAL_RSSNR,
+                    config.getIntArray(CarrierConfigManager.KEY_LTE_RSSNR_THRESHOLDS_INT_ARRAY),
+                    AccessNetworkType.EUTRAN,
+                    (lteMeasurementEnabled & CellSignalStrengthLte.USE_RSSNR) != 0);
+
+            int measurementEnabled = config.getInt(CarrierConfigManager
+                    .KEY_PARAMETERS_USE_FOR_5G_NR_SIGNAL_BAR_INT, CellSignalStrengthNr.USE_SSRSRP);
+            mPhone.setSignalStrengthReportingCriteria(SignalThresholdInfo.SIGNAL_SSRSRP,
+                    config.getIntArray(CarrierConfigManager.KEY_5G_NR_SSRSRP_THRESHOLDS_INT_ARRAY),
+                    AccessNetworkType.NGRAN,
+                    (measurementEnabled & CellSignalStrengthNr.USE_SSRSRP) != 0);
+            mPhone.setSignalStrengthReportingCriteria(SignalThresholdInfo.SIGNAL_SSRSRQ,
+                    config.getIntArray(CarrierConfigManager.KEY_5G_NR_SSRSRQ_THRESHOLDS_INT_ARRAY),
+                    AccessNetworkType.NGRAN,
+                    (measurementEnabled & CellSignalStrengthNr.USE_SSRSRQ) != 0);
+            mPhone.setSignalStrengthReportingCriteria(SignalThresholdInfo.SIGNAL_SSSINR,
+                    config.getIntArray(CarrierConfigManager.KEY_5G_NR_SSSINR_THRESHOLDS_INT_ARRAY),
+                    AccessNetworkType.NGRAN,
+                    (measurementEnabled & CellSignalStrengthNr.USE_SSSINR) != 0);
+        }
     }
 
     private void updateServiceStateLteEarfcnBoost(ServiceState serviceState, int lteEarfcn) {
@@ -4917,17 +5053,22 @@
         if (!isStale) return false;
 
         List<SubscriptionInfo> subInfoList = SubscriptionController.getInstance()
-                .getActiveSubscriptionInfoList(mPhone.getContext().getOpPackageName());
-        for (SubscriptionInfo info : subInfoList) {
-            // If we have an active opportunistic subscription whose data is IN_SERVICE, we needs
-            // to get signal strength to decide data switching threshold. In this case, we poll
-            // latest signal strength from modem.
-            if (info.isOpportunistic()) {
-                TelephonyManager tm = TelephonyManager.from(mPhone.getContext())
-                        .createForSubscriptionId(info.getSubscriptionId());
-                ServiceState ss = tm.getServiceState();
-                if (ss != null && ss.getDataRegState() == ServiceState.STATE_IN_SERVICE) {
-                    return true;
+                .getActiveSubscriptionInfoList(mPhone.getContext().getOpPackageName(),
+                        mPhone.getContext().getAttributionTag());
+
+        if (!ArrayUtils.isEmpty(subInfoList)) {
+            for (SubscriptionInfo info : subInfoList) {
+                // If we have an active opportunistic subscription whose data is IN_SERVICE,
+                // we need to get signal strength to decide data switching threshold. In this case,
+                // we poll latest signal strength from modem.
+                if (info.isOpportunistic()) {
+                    TelephonyManager tm = TelephonyManager.from(mPhone.getContext())
+                            .createForSubscriptionId(info.getSubscriptionId());
+                    ServiceState ss = tm.getServiceState();
+                    if (ss != null
+                            && ss.getDataRegistrationState() == ServiceState.STATE_IN_SERVICE) {
+                        return true;
+                    }
                 }
             }
         }
@@ -4971,7 +5112,7 @@
         mCi.getCDMASubscription(obtainMessage(EVENT_POLL_STATE_CDMA_SUBSCRIPTION));
 
         // Get Registration Information
-        pollState();
+        pollStateInternal(false);
     }
 
     private void handleCdmaSubscriptionSource(int newSubscriptionSource) {
@@ -5036,7 +5177,6 @@
         pw.println(" mPendingRadioPowerOffAfterDataOff=" + mPendingRadioPowerOffAfterDataOff);
         pw.println(" mPendingRadioPowerOffAfterDataOffTag=" + mPendingRadioPowerOffAfterDataOffTag);
         pw.println(" mCellIdentity=" + Rlog.pii(VDBG, mCellIdentity));
-        pw.println(" mNewCellIdentity=" + Rlog.pii(VDBG, mNewCellIdentity));
         pw.println(" mLastCellInfoReqTime=" + mLastCellInfoReqTime);
         dumpCellInfoList(pw);
         pw.flush();
@@ -5045,11 +5185,12 @@
         pw.println(" mNewMaxDataCalls=" + mNewMaxDataCalls);
         pw.println(" mReasonDataDenied=" + mReasonDataDenied);
         pw.println(" mNewReasonDataDenied=" + mNewReasonDataDenied);
-        pw.println(" mGsmRoaming=" + mGsmRoaming);
-        pw.println(" mDataRoaming=" + mDataRoaming);
+        pw.println(" mGsmVoiceRoaming=" + mGsmVoiceRoaming);
+        pw.println(" mGsmDataRoaming=" + mGsmDataRoaming);
         pw.println(" mEmergencyOnly=" + mEmergencyOnly);
         pw.flush();
         mNitzState.dumpState(pw);
+        pw.println(" mLastNitzData=" + mLastNitzData);
         pw.flush();
         pw.println(" mStartedGprsRegCheck=" + mStartedGprsRegCheck);
         pw.println(" mReportedGprsNoReg=" + mReportedGprsNoReg);
@@ -5196,12 +5337,12 @@
     @UnsupportedAppUsage
     protected void setRoamingType(ServiceState currentServiceState) {
         final boolean isVoiceInService =
-                (currentServiceState.getVoiceRegState() == ServiceState.STATE_IN_SERVICE);
+                (currentServiceState.getState() == ServiceState.STATE_IN_SERVICE);
         if (isVoiceInService) {
             if (currentServiceState.getVoiceRoaming()) {
                 if (mPhone.isPhoneTypeGsm()) {
                     // check roaming type by MCC
-                    if (inSameCountry(currentServiceState.getVoiceOperatorNumeric())) {
+                    if (inSameCountry(currentServiceState.getOperatorNumeric())) {
                         currentServiceState.setVoiceRoamingType(
                                 ServiceState.ROAMING_TYPE_DOMESTIC);
                     } else {
@@ -5211,7 +5352,8 @@
                 } else {
                     // some carrier defines international roaming by indicator
                     int[] intRoamingIndicators = mPhone.getContext().getResources().getIntArray(
-                            com.android.internal.R.array.config_cdma_international_roaming_indicators);
+                            com.android.internal.R.array
+                                    .config_cdma_international_roaming_indicators);
                     if ((intRoamingIndicators != null) && (intRoamingIndicators.length > 0)) {
                         // It's domestic roaming at least now
                         currentServiceState.setVoiceRoamingType(ServiceState.ROAMING_TYPE_DOMESTIC);
@@ -5225,7 +5367,7 @@
                         }
                     } else {
                         // check roaming type by MCC
-                        if (inSameCountry(currentServiceState.getVoiceOperatorNumeric())) {
+                        if (inSameCountry(currentServiceState.getOperatorNumeric())) {
                             currentServiceState.setVoiceRoamingType(
                                     ServiceState.ROAMING_TYPE_DOMESTIC);
                         } else {
@@ -5239,7 +5381,7 @@
             }
         }
         final boolean isDataInService =
-                (currentServiceState.getDataRegState() == ServiceState.STATE_IN_SERVICE);
+                (currentServiceState.getDataRegistrationState() == ServiceState.STATE_IN_SERVICE);
         final int dataRegType = getRilDataRadioTechnologyForWwan(currentServiceState);
         if (isDataInService) {
             if (!currentServiceState.getDataRoaming()) {
@@ -5272,7 +5414,7 @@
                         }
                     } else {
                         // take it as 3GPP roaming
-                        if (inSameCountry(currentServiceState.getDataOperatorNumeric())) {
+                        if (inSameCountry(currentServiceState.getOperatorNumeric())) {
                             currentServiceState.setDataRoamingType(ServiceState.ROAMING_TYPE_DOMESTIC);
                         } else {
                             currentServiceState.setDataRoamingType(
@@ -5436,17 +5578,26 @@
     }
 
     /**
-     * Consider dataRegState if voiceRegState is OOS to determine SPN to be displayed
+     * Consider dataRegState if voiceRegState is OOS to determine SPN to be displayed.
+     * If dataRegState is in service on IWLAN, also check for wifi calling enabled.
      * @param ss service state.
      */
     protected int getCombinedRegState(ServiceState ss) {
-        int regState = ss.getVoiceRegState();
-        int dataRegState = ss.getDataRegState();
+        int regState = ss.getState();
+        int dataRegState = ss.getDataRegistrationState();
         if ((regState == ServiceState.STATE_OUT_OF_SERVICE
                 || regState == ServiceState.STATE_POWER_OFF)
                 && (dataRegState == ServiceState.STATE_IN_SERVICE)) {
-            log("getCombinedRegState: return STATE_IN_SERVICE as Data is in service");
-            regState = dataRegState;
+            if (ss.getDataNetworkType() == TelephonyManager.NETWORK_TYPE_IWLAN) {
+                if (mPhone.getImsPhone() != null && mPhone.getImsPhone().isWifiCallingEnabled()) {
+                    log("getCombinedRegState: return STATE_IN_SERVICE for IWLAN as "
+                            + "Data is in service and WFC is enabled");
+                    regState = dataRegState;
+                }
+            } else {
+                log("getCombinedRegState: return STATE_IN_SERVICE as Data is in service");
+                regState = dataRegState;
+            }
         }
         return regState;
     }
@@ -5508,6 +5659,7 @@
             if (networkRegistrationInfos.get(i) != null) {
                 updateOperatorNameForCellIdentity(
                         networkRegistrationInfos.get(i).getCellIdentity());
+                servicestate.addNetworkRegistrationInfo(networkRegistrationInfos.get(i));
             }
         }
     }
@@ -5617,14 +5769,43 @@
     public Set<Integer> getNrContextIds() {
         Set<Integer> idSet = new HashSet<>();
 
-        for (PhysicalChannelConfig config : mLastPhysicalChannelConfigList) {
-            if (isNrPhysicalChannelConfig(config)) {
-                for (int id : config.getContextIds()) {
-                    idSet.add(id);
+        if (!ArrayUtils.isEmpty(mLastPhysicalChannelConfigList)) {
+            for (PhysicalChannelConfig config : mLastPhysicalChannelConfigList) {
+                if (isNrPhysicalChannelConfig(config)) {
+                    for (int id : config.getContextIds()) {
+                        idSet.add(id);
+                    }
                 }
             }
         }
 
         return idSet;
     }
+
+    private void setDataNetworkTypeForPhone(int type) {
+        if (mPhone.getUnitTestMode()) {
+            return;
+        }
+        TelephonyManager tm = (TelephonyManager) mPhone.getContext().getSystemService(
+                Context.TELEPHONY_SERVICE);
+        tm.setDataNetworkTypeForPhone(mPhone.getPhoneId(), type);
+    }
+
+    /**
+     * Used to insert a ServiceState into the ServiceStateProvider as a ContentValues instance.
+     *
+     * Copied from packages/services/Telephony/src/com/android/phone/ServiceStateProvider.java
+     *
+     * @param state the ServiceState to convert into ContentValues
+     * @return the convertedContentValues instance
+     */
+    private ContentValues getContentValuesForServiceState(ServiceState state) {
+        ContentValues values = new ContentValues();
+        final Parcel p = Parcel.obtain();
+        state.writeToParcel(p, 0);
+        // Turn the parcel to byte array. Safe to do this because the content values were never
+        // written into a persistent storage. ServiceStateProvider keeps values in the memory.
+        values.put(SERVICE_STATE, p.marshall());
+        return values;
+    }
 }
diff --git a/src/java/com/android/internal/telephony/SettingsObserver.java b/src/java/com/android/internal/telephony/SettingsObserver.java
index 2253c36..e480b05 100644
--- a/src/java/com/android/internal/telephony/SettingsObserver.java
+++ b/src/java/com/android/internal/telephony/SettingsObserver.java
@@ -21,7 +21,8 @@
 import android.database.ContentObserver;
 import android.net.Uri;
 import android.os.Handler;
-import android.telephony.Rlog;
+
+import com.android.telephony.Rlog;
 
 import java.util.HashMap;
 import java.util.Map;
diff --git a/src/java/com/android/internal/telephony/SimActivationTracker.java b/src/java/com/android/internal/telephony/SimActivationTracker.java
index 8fd6eed..2daf858 100644
--- a/src/java/com/android/internal/telephony/SimActivationTracker.java
+++ b/src/java/com/android/internal/telephony/SimActivationTracker.java
@@ -15,25 +15,24 @@
  */
 package com.android.internal.telephony;
 
+import static android.telephony.TelephonyManager.SIM_ACTIVATION_STATE_ACTIVATED;
+import static android.telephony.TelephonyManager.SIM_ACTIVATION_STATE_ACTIVATING;
+import static android.telephony.TelephonyManager.SIM_ACTIVATION_STATE_DEACTIVATED;
+import static android.telephony.TelephonyManager.SIM_ACTIVATION_STATE_RESTRICTED;
+import static android.telephony.TelephonyManager.SIM_ACTIVATION_STATE_UNKNOWN;
+
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
-import android.telephony.Rlog;
 import android.util.LocalLog;
 import android.util.Log;
 
 import com.android.internal.util.IndentingPrintWriter;
+import com.android.telephony.Rlog;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
-import java.security.InvalidParameterException;
-
-import static android.telephony.TelephonyManager.SIM_ACTIVATION_STATE_ACTIVATED;
-import static android.telephony.TelephonyManager.SIM_ACTIVATION_STATE_DEACTIVATED;
-import static android.telephony.TelephonyManager.SIM_ACTIVATION_STATE_UNKNOWN;
-import static android.telephony.TelephonyManager.SIM_ACTIVATION_STATE_RESTRICTED;
-import static android.telephony.TelephonyManager.SIM_ACTIVATION_STATE_ACTIVATING;
 
 public class SimActivationTracker {
     /**
diff --git a/src/java/com/android/internal/telephony/SmsBroadcastUndelivered.java b/src/java/com/android/internal/telephony/SmsBroadcastUndelivered.java
index a1d8faa..5008665 100644
--- a/src/java/com/android/internal/telephony/SmsBroadcastUndelivered.java
+++ b/src/java/com/android/internal/telephony/SmsBroadcastUndelivered.java
@@ -16,7 +16,7 @@
 
 package com.android.internal.telephony;
 
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
 import android.content.BroadcastReceiver;
 import android.content.ContentResolver;
 import android.content.Context;
@@ -27,12 +27,12 @@
 import android.os.PersistableBundle;
 import android.os.UserManager;
 import android.telephony.CarrierConfigManager;
-import android.telephony.Rlog;
 import android.telephony.SubscriptionManager;
 
 import com.android.internal.telephony.cdma.CdmaInboundSmsHandler;
 import com.android.internal.telephony.gsm.GsmInboundSmsHandler;
 import com.android.internal.telephony.metrics.TelephonyMetrics;
+import com.android.telephony.Rlog;
 
 import java.util.HashMap;
 import java.util.HashSet;
@@ -188,8 +188,10 @@
                 InboundSmsTracker tracker;
                 try {
                     tracker = TelephonyComponentFactory.getInstance()
-                            .inject(InboundSmsTracker.class.getName()).makeInboundSmsTracker(cursor,
-                            isCurrentFormat3gpp2);
+                            .inject(InboundSmsTracker.class.getName()).makeInboundSmsTracker(
+                                    context,
+                                    cursor,
+                                    isCurrentFormat3gpp2);
                 } catch (IllegalArgumentException e) {
                     Rlog.e(TAG, "error loading SmsTracker: " + e);
                     continue;
diff --git a/src/java/com/android/internal/telephony/SmsController.java b/src/java/com/android/internal/telephony/SmsController.java
index 45efafd..4924970 100644
--- a/src/java/com/android/internal/telephony/SmsController.java
+++ b/src/java/com/android/internal/telephony/SmsController.java
@@ -18,28 +18,29 @@
 
 package com.android.internal.telephony;
 
-import static com.android.internal.util.DumpUtils.checkDumpPermission;
+import static com.android.internal.telephony.util.TelephonyUtils.checkDumpPermission;
 
 import android.annotation.Nullable;
-import android.annotation.UnsupportedAppUsage;
 import android.app.ActivityManager;
-import android.app.ActivityThread;
 import android.app.AppOpsManager;
 import android.app.PendingIntent;
+import android.compat.annotation.UnsupportedAppUsage;
 import android.content.Context;
 import android.net.Uri;
+import android.os.BaseBundle;
 import android.os.Binder;
 import android.os.Bundle;
-import android.os.ServiceManager;
+import android.os.TelephonyServiceManager.ServiceRegisterer;
 import android.provider.Telephony.Sms.Intents;
-import android.telephony.IFinancialSmsCallback;
-import android.telephony.Rlog;
+import android.telephony.CarrierConfigManager;
 import android.telephony.SmsManager;
 import android.telephony.SubscriptionInfo;
 import android.telephony.SubscriptionManager;
+import android.telephony.TelephonyFrameworkInitializer;
 import android.telephony.TelephonyManager;
 
 import com.android.internal.util.IndentingPrintWriter;
+import com.android.telephony.Rlog;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
@@ -56,8 +57,11 @@
 
     protected SmsController(Context context) {
         mContext = context;
-        if (ServiceManager.getService("isms") == null) {
-            ServiceManager.addService("isms", this);
+        ServiceRegisterer smsServiceRegisterer = TelephonyFrameworkInitializer
+                .getTelephonyServiceManager()
+                .getSmsServiceRegisterer();
+        if (smsServiceRegisterer.get() == null) {
+            smsServiceRegisterer.register(this);
         }
     }
 
@@ -80,6 +84,9 @@
     @Override
     public boolean updateMessageOnIccEfForSubscriber(int subId, String callingPackage, int index,
             int status, byte[] pdu) {
+        if (callingPackage == null) {
+            callingPackage = getCallingPackage();
+        }
         IccSmsInterfaceManager iccSmsIntMgr = getIccSmsInterfaceManager(subId);
         if (iccSmsIntMgr != null) {
             return iccSmsIntMgr.updateMessageOnIccEf(callingPackage, index, status, pdu);
@@ -94,6 +101,9 @@
     @Override
     public boolean copyMessageToIccEfForSubscriber(int subId, String callingPackage, int status,
             byte[] pdu, byte[] smsc) {
+        if (callingPackage == null) {
+            callingPackage = getCallingPackage();
+        }
         IccSmsInterfaceManager iccSmsIntMgr = getIccSmsInterfaceManager(subId);
         if (iccSmsIntMgr != null) {
             return iccSmsIntMgr.copyMessageToIccEf(callingPackage, status, pdu, smsc);
@@ -107,6 +117,9 @@
     @UnsupportedAppUsage
     @Override
     public List<SmsRawData> getAllMessagesFromIccEfForSubscriber(int subId, String callingPackage) {
+        if (callingPackage == null) {
+            callingPackage = getCallingPackage();
+        }
         IccSmsInterfaceManager iccSmsIntMgr = getIccSmsInterfaceManager(subId);
         if (iccSmsIntMgr != null) {
             return iccSmsIntMgr.getAllMessagesFromIccEf(callingPackage);
@@ -117,15 +130,30 @@
         }
     }
 
+    /**
+     * @deprecated Use {@link #sendDataForSubscriber(int, String, String, String, String, int,
+     * byte[], PendingIntent, PendingIntent)} instead
+     */
+    @Deprecated
     @UnsupportedAppUsage
-    @Override
     public void sendDataForSubscriber(int subId, String callingPackage, String destAddr,
             String scAddr, int destPort, byte[] data, PendingIntent sentIntent,
             PendingIntent deliveryIntent) {
+        sendDataForSubscriber(subId, callingPackage, null, destAddr, scAddr, destPort, data,
+                sentIntent, deliveryIntent);
+    }
+
+    @Override
+    public void sendDataForSubscriber(int subId, String callingPackage,
+            String callingAttributionTag, String destAddr, String scAddr, int destPort, byte[] data,
+            PendingIntent sentIntent, PendingIntent deliveryIntent) {
+        if (callingPackage == null) {
+            callingPackage = getCallingPackage();
+        }
         IccSmsInterfaceManager iccSmsIntMgr = getIccSmsInterfaceManager(subId);
         if (iccSmsIntMgr != null) {
-            iccSmsIntMgr.sendData(callingPackage, destAddr, scAddr, destPort, data,
-                    sentIntent, deliveryIntent);
+            iccSmsIntMgr.sendData(callingPackage, callingAttributionTag, destAddr, scAddr, destPort,
+                    data, sentIntent, deliveryIntent);
         } else {
             Rlog.e(LOG_TAG, "sendDataForSubscriber iccSmsIntMgr is null for"
                     + " Subscription: " + subId);
@@ -134,21 +162,13 @@
         }
     }
 
-    @Override
-    public void sendDataForSubscriberWithSelfPermissions(int subId, String callingPackage,
-            String destAddr, String scAddr, int destPort, byte[] data, PendingIntent sentIntent,
-            PendingIntent deliveryIntent) {
-        sendDataForSubscriberWithSelfPermissionsInternal(subId, callingPackage, destAddr, scAddr,
-                destPort, data, sentIntent, deliveryIntent, false /* isForVvm */);
-    }
-
     private void sendDataForSubscriberWithSelfPermissionsInternal(int subId, String callingPackage,
-            String destAddr, String scAddr, int destPort, byte[] data, PendingIntent sentIntent,
-            PendingIntent deliveryIntent, boolean isForVvm) {
+            String callingAttributionTag, String destAddr, String scAddr, int destPort, byte[] data,
+            PendingIntent sentIntent, PendingIntent deliveryIntent, boolean isForVvm) {
         IccSmsInterfaceManager iccSmsIntMgr = getIccSmsInterfaceManager(subId);
         if (iccSmsIntMgr != null) {
-            iccSmsIntMgr.sendDataWithSelfPermissions(callingPackage, destAddr, scAddr, destPort,
-                    data, sentIntent, deliveryIntent, isForVvm);
+            iccSmsIntMgr.sendDataWithSelfPermissions(callingPackage, callingAttributionTag,
+                    destAddr, scAddr, destPort, data, sentIntent, deliveryIntent, isForVvm);
         } else {
             Rlog.e(LOG_TAG, "sendText iccSmsIntMgr is null for"
                     + " Subscription: " + subId);
@@ -156,16 +176,23 @@
         }
     }
 
+    private String getCallingPackage() {
+        return mContext.getPackageManager().getPackagesForUid(Binder.getCallingUid())[0];
+    }
+
     @Override
-    public void sendTextForSubscriber(int subId, String callingPackage, String destAddr,
-            String scAddr, String text, PendingIntent sentIntent, PendingIntent deliveryIntent,
-            boolean persistMessageForNonDefaultSmsApp) {
+    public void sendTextForSubscriber(int subId, String callingPackage,
+            String callingAttributionTag, String destAddr, String scAddr, String text,
+            PendingIntent sentIntent, PendingIntent deliveryIntent,
+            boolean persistMessageForNonDefaultSmsApp, long messageId) {
+        if (callingPackage == null) {
+            callingPackage = getCallingPackage();
+        }
         if (!getSmsPermissions(subId).checkCallingCanSendText(persistMessageForNonDefaultSmsApp,
-                callingPackage, "Sending SMS message")) {
+                callingPackage, callingAttributionTag, "Sending SMS message")) {
             sendErrorInPendingIntent(sentIntent, SmsManager.RESULT_ERROR_GENERIC_FAILURE);
             return;
         }
-
         long token = Binder.clearCallingIdentity();
         SubscriptionInfo info;
         try {
@@ -177,7 +204,7 @@
             sendBluetoothText(info, destAddr, text, sentIntent, deliveryIntent);
         } else {
             sendIccText(subId, callingPackage, destAddr, scAddr, text, sentIntent, deliveryIntent,
-                    persistMessageForNonDefaultSmsApp);
+                    persistMessageForNonDefaultSmsApp, messageId);
         }
     }
 
@@ -189,38 +216,33 @@
     private void sendBluetoothText(SubscriptionInfo info, String destAddr,
             String text, PendingIntent sentIntent, PendingIntent deliveryIntent) {
         BtSmsInterfaceManager btSmsInterfaceManager = new BtSmsInterfaceManager();
-        btSmsInterfaceManager.sendText(destAddr, text, sentIntent, deliveryIntent, info);
+        btSmsInterfaceManager.sendText(mContext, destAddr, text, sentIntent, deliveryIntent, info);
     }
 
     private void sendIccText(int subId, String callingPackage, String destAddr,
             String scAddr, String text, PendingIntent sentIntent, PendingIntent deliveryIntent,
-            boolean persistMessageForNonDefaultSmsApp) {
+            boolean persistMessageForNonDefaultSmsApp, long messageId) {
+        Rlog.d(LOG_TAG, "sendTextForSubscriber iccSmsIntMgr"
+                + " Subscription: " + subId + " id: " + messageId);
         IccSmsInterfaceManager iccSmsIntMgr = getIccSmsInterfaceManager(subId);
         if (iccSmsIntMgr != null) {
             iccSmsIntMgr.sendText(callingPackage, destAddr, scAddr, text, sentIntent,
-                    deliveryIntent, persistMessageForNonDefaultSmsApp);
+                    deliveryIntent, persistMessageForNonDefaultSmsApp, messageId);
         } else {
             Rlog.e(LOG_TAG, "sendTextForSubscriber iccSmsIntMgr is null for"
-                    + " Subscription: " + subId);
+                    + " Subscription: " + subId + " id: " + messageId);
             sendErrorInPendingIntent(sentIntent, SmsManager.RESULT_ERROR_GENERIC_FAILURE);
         }
     }
 
-    @Override
-    public void sendTextForSubscriberWithSelfPermissions(int subId, String callingPackage,
-            String destAddr, String scAddr, String text, PendingIntent sentIntent,
-            PendingIntent deliveryIntent, boolean persistMessage) {
-        sendTextForSubscriberWithSelfPermissionsInternal(subId, callingPackage, destAddr, scAddr,
-                text, sentIntent, deliveryIntent, persistMessage, false /* isForVvm */);
-    }
-
     private void sendTextForSubscriberWithSelfPermissionsInternal(int subId, String callingPackage,
-            String destAddr, String scAddr, String text, PendingIntent sentIntent,
-            PendingIntent deliveryIntent, boolean persistMessage, boolean isForVvm) {
+            String callingAttributeTag, String destAddr, String scAddr, String text,
+            PendingIntent sentIntent, PendingIntent deliveryIntent, boolean persistMessage,
+            boolean isForVvm) {
         IccSmsInterfaceManager iccSmsIntMgr = getIccSmsInterfaceManager(subId);
         if (iccSmsIntMgr != null) {
-            iccSmsIntMgr.sendTextWithSelfPermissions(callingPackage, destAddr, scAddr, text,
-                    sentIntent, deliveryIntent, persistMessage, isForVvm);
+            iccSmsIntMgr.sendTextWithSelfPermissions(callingPackage, callingAttributeTag, destAddr,
+                    scAddr, text, sentIntent, deliveryIntent, persistMessage, isForVvm);
         } else {
             Rlog.e(LOG_TAG, "sendText iccSmsIntMgr is null for"
                     + " Subscription: " + subId);
@@ -230,13 +252,17 @@
 
     @Override
     public void sendTextForSubscriberWithOptions(int subId, String callingPackage,
-            String destAddr, String scAddr, String parts, PendingIntent sentIntent,
-            PendingIntent deliveryIntent, boolean persistMessage, int priority,
-            boolean expectMore, int validityPeriod) {
+            String callingAttributionTag, String destAddr, String scAddr, String parts,
+            PendingIntent sentIntent, PendingIntent deliveryIntent, boolean persistMessage,
+            int priority, boolean expectMore, int validityPeriod) {
+        if (callingPackage == null) {
+            callingPackage = getCallingPackage();
+        }
         IccSmsInterfaceManager iccSmsIntMgr = getIccSmsInterfaceManager(subId);
         if (iccSmsIntMgr != null) {
-            iccSmsIntMgr.sendTextWithOptions(callingPackage, destAddr, scAddr, parts, sentIntent,
-                    deliveryIntent, persistMessage, priority, expectMore, validityPeriod);
+            iccSmsIntMgr.sendTextWithOptions(callingPackage, callingAttributionTag, destAddr,
+                    scAddr, parts, sentIntent, deliveryIntent, persistMessage, priority, expectMore,
+                    validityPeriod);
         } else {
             Rlog.e(LOG_TAG, "sendTextWithOptions iccSmsIntMgr is null for"
                     + " Subscription: " + subId);
@@ -245,30 +271,40 @@
     }
 
     @Override
-    public void sendMultipartTextForSubscriber(int subId, String callingPackage, String destAddr,
-            String scAddr, List<String> parts, List<PendingIntent> sentIntents,
-            List<PendingIntent> deliveryIntents, boolean persistMessageForNonDefaultSmsApp) {
+    public void sendMultipartTextForSubscriber(int subId, String callingPackage,
+            String callingAttributionTag, String destAddr, String scAddr, List<String> parts,
+            List<PendingIntent> sentIntents, List<PendingIntent> deliveryIntents,
+            boolean persistMessageForNonDefaultSmsApp, long messageId) {
+        // This is different from the checking of other method. It prefers the package name
+        // returned by getCallPackage() for backward-compatibility.
+        if (getCallingPackage() != null) {
+            callingPackage = getCallingPackage();
+        }
         IccSmsInterfaceManager iccSmsIntMgr = getIccSmsInterfaceManager(subId);
         if (iccSmsIntMgr != null) {
-            iccSmsIntMgr.sendMultipartText(callingPackage, destAddr, scAddr, parts, sentIntents,
-                    deliveryIntents, persistMessageForNonDefaultSmsApp);
+            iccSmsIntMgr.sendMultipartText(callingPackage, callingAttributionTag, destAddr, scAddr,
+                    parts, sentIntents, deliveryIntents, persistMessageForNonDefaultSmsApp,
+                    messageId);
         } else {
             Rlog.e(LOG_TAG, "sendMultipartTextForSubscriber iccSmsIntMgr is null for"
-                    + " Subscription: " + subId);
+                    + " Subscription: " + subId + " id: " + messageId);
             sendErrorInPendingIntents(sentIntents, SmsManager.RESULT_ERROR_GENERIC_FAILURE);
         }
     }
 
     @Override
     public void sendMultipartTextForSubscriberWithOptions(int subId, String callingPackage,
-            String destAddr, String scAddr, List<String> parts, List<PendingIntent> sentIntents,
-            List<PendingIntent> deliveryIntents, boolean persistMessage, int priority,
-            boolean expectMore, int validityPeriod) {
+            String callingAttributionTag, String destAddr, String scAddr, List<String> parts,
+            List<PendingIntent> sentIntents, List<PendingIntent> deliveryIntents,
+            boolean persistMessage, int priority, boolean expectMore, int validityPeriod) {
+        if (callingPackage == null) {
+            callingPackage = getCallingPackage();
+        }
         IccSmsInterfaceManager iccSmsIntMgr = getIccSmsInterfaceManager(subId);
         if (iccSmsIntMgr != null) {
-            iccSmsIntMgr.sendMultipartTextWithOptions(callingPackage, destAddr, scAddr, parts,
-                    sentIntents, deliveryIntents, persistMessage, priority, expectMore,
-                    validityPeriod);
+            iccSmsIntMgr.sendMultipartTextWithOptions(callingPackage, callingAttributionTag,
+                    destAddr, scAddr, parts, sentIntents, deliveryIntents, persistMessage, priority,
+                    expectMore, validityPeriod, 0L /* messageId */);
         } else {
             Rlog.e(LOG_TAG, "sendMultipartTextWithOptions iccSmsIntMgr is null for"
                     + " Subscription: " + subId);
@@ -367,7 +403,7 @@
 
     @Override
     public boolean isSmsSimPickActivityNeeded(int subId) {
-        final Context context = ActivityThread.currentApplication().getApplicationContext();
+        final Context context = mContext.getApplicationContext();
         ActivityManager am = context.getSystemService(ActivityManager.class);
         // Don't show the SMS SIM Pick activity if it is not foreground.
         boolean isCallingProcessForeground = am != null
@@ -474,12 +510,13 @@
     }
 
     @Override
-    public void sendStoredText(int subId, String callingPkg, Uri messageUri, String scAddress,
-            PendingIntent sentIntent, PendingIntent deliveryIntent) {
+    public void sendStoredText(int subId, String callingPkg, String callingAttributionTag,
+            Uri messageUri, String scAddress, PendingIntent sentIntent,
+            PendingIntent deliveryIntent) {
         IccSmsInterfaceManager iccSmsIntMgr = getIccSmsInterfaceManager(subId);
         if (iccSmsIntMgr != null) {
-            iccSmsIntMgr.sendStoredText(callingPkg, messageUri, scAddress, sentIntent,
-                    deliveryIntent);
+            iccSmsIntMgr.sendStoredText(callingPkg, callingAttributionTag, messageUri, scAddress,
+                    sentIntent, deliveryIntent);
         } else {
             Rlog.e(LOG_TAG, "sendStoredText iccSmsIntMgr is null for subscription: " + subId);
             sendErrorInPendingIntent(sentIntent, SmsManager.RESULT_ERROR_GENERIC_FAILURE);
@@ -487,13 +524,13 @@
     }
 
     @Override
-    public void sendStoredMultipartText(int subId, String callingPkg, Uri messageUri,
-            String scAddress, List<PendingIntent> sentIntents,
+    public void sendStoredMultipartText(int subId, String callingPkg, String callingAttributionTag,
+            Uri messageUri, String scAddress, List<PendingIntent> sentIntents,
             List<PendingIntent> deliveryIntents) {
         IccSmsInterfaceManager iccSmsIntMgr = getIccSmsInterfaceManager(subId);
         if (iccSmsIntMgr != null) {
-            iccSmsIntMgr.sendStoredMultipartText(callingPkg, messageUri, scAddress, sentIntents,
-                    deliveryIntents);
+            iccSmsIntMgr.sendStoredMultipartText(callingPkg, callingAttributionTag, messageUri,
+                    scAddress, sentIntents, deliveryIntents);
         } else {
             Rlog.e(LOG_TAG, "sendStoredMultipartText iccSmsIntMgr is null for subscription: "
                     + subId);
@@ -502,29 +539,155 @@
     }
 
     @Override
+    public Bundle getCarrierConfigValuesForSubscriber(int subId) {
+        final long identity = Binder.clearCallingIdentity();
+        try {
+            final CarrierConfigManager configManager =
+                    (CarrierConfigManager)
+                            mContext.getSystemService(Context.CARRIER_CONFIG_SERVICE);
+            return getMmsConfig(configManager.getConfigForSubId(subId));
+        } finally {
+            Binder.restoreCallingIdentity(identity);
+        }
+    }
+
+    /**
+     * Filters a bundle to only contain MMS config variables.
+     *
+     * This is for use with bundles returned by CarrierConfigManager which contain MMS config and
+     * unrelated config. It is assumed that all MMS_CONFIG_* keys are present in the supplied
+     * bundle.
+     *
+     * @param config a Bundle that contains MMS config variables and possibly more.
+     * @return a new Bundle that only contains the MMS_CONFIG_* keys defined in SmsManager.
+     */
+    private static Bundle getMmsConfig(BaseBundle config) {
+        Bundle filtered = new Bundle();
+        filtered.putBoolean(
+                SmsManager.MMS_CONFIG_APPEND_TRANSACTION_ID,
+                config.getBoolean(SmsManager.MMS_CONFIG_APPEND_TRANSACTION_ID));
+        filtered.putBoolean(
+                SmsManager.MMS_CONFIG_MMS_ENABLED,
+                config.getBoolean(SmsManager.MMS_CONFIG_MMS_ENABLED));
+        filtered.putBoolean(
+                SmsManager.MMS_CONFIG_GROUP_MMS_ENABLED,
+                config.getBoolean(SmsManager.MMS_CONFIG_GROUP_MMS_ENABLED));
+        filtered.putBoolean(
+                SmsManager.MMS_CONFIG_NOTIFY_WAP_MMSC_ENABLED,
+                config.getBoolean(SmsManager.MMS_CONFIG_NOTIFY_WAP_MMSC_ENABLED));
+        filtered.putBoolean(
+                SmsManager.MMS_CONFIG_ALIAS_ENABLED,
+                config.getBoolean(SmsManager.MMS_CONFIG_ALIAS_ENABLED));
+        filtered.putBoolean(
+                SmsManager.MMS_CONFIG_ALLOW_ATTACH_AUDIO,
+                config.getBoolean(SmsManager.MMS_CONFIG_ALLOW_ATTACH_AUDIO));
+        filtered.putBoolean(
+                SmsManager.MMS_CONFIG_MULTIPART_SMS_ENABLED,
+                config.getBoolean(SmsManager.MMS_CONFIG_MULTIPART_SMS_ENABLED));
+        filtered.putBoolean(
+                SmsManager.MMS_CONFIG_SMS_DELIVERY_REPORT_ENABLED,
+                config.getBoolean(SmsManager.MMS_CONFIG_SMS_DELIVERY_REPORT_ENABLED));
+        filtered.putBoolean(
+                SmsManager.MMS_CONFIG_SUPPORT_MMS_CONTENT_DISPOSITION,
+                config.getBoolean(SmsManager.MMS_CONFIG_SUPPORT_MMS_CONTENT_DISPOSITION));
+        filtered.putBoolean(
+                SmsManager.MMS_CONFIG_SEND_MULTIPART_SMS_AS_SEPARATE_MESSAGES,
+                config.getBoolean(SmsManager.MMS_CONFIG_SEND_MULTIPART_SMS_AS_SEPARATE_MESSAGES));
+        filtered.putBoolean(
+                SmsManager.MMS_CONFIG_MMS_READ_REPORT_ENABLED,
+                config.getBoolean(SmsManager.MMS_CONFIG_MMS_READ_REPORT_ENABLED));
+        filtered.putBoolean(
+                SmsManager.MMS_CONFIG_MMS_DELIVERY_REPORT_ENABLED,
+                config.getBoolean(SmsManager.MMS_CONFIG_MMS_DELIVERY_REPORT_ENABLED));
+        filtered.putBoolean(
+                SmsManager.MMS_CONFIG_CLOSE_CONNECTION,
+                config.getBoolean(SmsManager.MMS_CONFIG_CLOSE_CONNECTION));
+        filtered.putInt(
+                SmsManager.MMS_CONFIG_MAX_MESSAGE_SIZE,
+                config.getInt(SmsManager.MMS_CONFIG_MAX_MESSAGE_SIZE));
+        filtered.putInt(
+                SmsManager.MMS_CONFIG_MAX_IMAGE_WIDTH,
+                config.getInt(SmsManager.MMS_CONFIG_MAX_IMAGE_WIDTH));
+        filtered.putInt(
+                SmsManager.MMS_CONFIG_MAX_IMAGE_HEIGHT,
+                config.getInt(SmsManager.MMS_CONFIG_MAX_IMAGE_HEIGHT));
+        filtered.putInt(
+                SmsManager.MMS_CONFIG_RECIPIENT_LIMIT,
+                config.getInt(SmsManager.MMS_CONFIG_RECIPIENT_LIMIT));
+        filtered.putInt(
+                SmsManager.MMS_CONFIG_ALIAS_MIN_CHARS,
+                config.getInt(SmsManager.MMS_CONFIG_ALIAS_MIN_CHARS));
+        filtered.putInt(
+                SmsManager.MMS_CONFIG_ALIAS_MAX_CHARS,
+                config.getInt(SmsManager.MMS_CONFIG_ALIAS_MAX_CHARS));
+        filtered.putInt(
+                SmsManager.MMS_CONFIG_SMS_TO_MMS_TEXT_THRESHOLD,
+                config.getInt(SmsManager.MMS_CONFIG_SMS_TO_MMS_TEXT_THRESHOLD));
+        filtered.putInt(
+                SmsManager.MMS_CONFIG_SMS_TO_MMS_TEXT_LENGTH_THRESHOLD,
+                config.getInt(SmsManager.MMS_CONFIG_SMS_TO_MMS_TEXT_LENGTH_THRESHOLD));
+        filtered.putInt(
+                SmsManager.MMS_CONFIG_MESSAGE_TEXT_MAX_SIZE,
+                config.getInt(SmsManager.MMS_CONFIG_MESSAGE_TEXT_MAX_SIZE));
+        filtered.putInt(
+                SmsManager.MMS_CONFIG_SUBJECT_MAX_LENGTH,
+                config.getInt(SmsManager.MMS_CONFIG_SUBJECT_MAX_LENGTH));
+        filtered.putInt(
+                SmsManager.MMS_CONFIG_HTTP_SOCKET_TIMEOUT,
+                config.getInt(SmsManager.MMS_CONFIG_HTTP_SOCKET_TIMEOUT));
+        filtered.putString(
+                SmsManager.MMS_CONFIG_UA_PROF_TAG_NAME,
+                config.getString(SmsManager.MMS_CONFIG_UA_PROF_TAG_NAME));
+        filtered.putString(
+                SmsManager.MMS_CONFIG_USER_AGENT,
+                config.getString(SmsManager.MMS_CONFIG_USER_AGENT));
+        filtered.putString(
+                SmsManager.MMS_CONFIG_UA_PROF_URL,
+                config.getString(SmsManager.MMS_CONFIG_UA_PROF_URL));
+        filtered.putString(
+                SmsManager.MMS_CONFIG_HTTP_PARAMS,
+                config.getString(SmsManager.MMS_CONFIG_HTTP_PARAMS));
+        filtered.putString(
+                SmsManager.MMS_CONFIG_EMAIL_GATEWAY_NUMBER,
+                config.getString(SmsManager.MMS_CONFIG_EMAIL_GATEWAY_NUMBER));
+        filtered.putString(
+                SmsManager.MMS_CONFIG_NAI_SUFFIX,
+                config.getString(SmsManager.MMS_CONFIG_NAI_SUFFIX));
+        filtered.putBoolean(
+                SmsManager.MMS_CONFIG_SHOW_CELL_BROADCAST_APP_LINKS,
+                config.getBoolean(SmsManager.MMS_CONFIG_SHOW_CELL_BROADCAST_APP_LINKS));
+        filtered.putBoolean(
+                SmsManager.MMS_CONFIG_SUPPORT_HTTP_CHARSET_HEADER,
+                config.getBoolean(SmsManager.MMS_CONFIG_SUPPORT_HTTP_CHARSET_HEADER));
+        return filtered;
+    }
+
+    @Override
     public String createAppSpecificSmsTokenWithPackageInfo(
             int subId, String callingPkg, String prefixes, PendingIntent intent) {
+        if (callingPkg == null) {
+            callingPkg = getCallingPackage();
+        }
         return getPhone(subId).getAppSmsManager().createAppSpecificSmsTokenWithPackageInfo(
                 subId, callingPkg, prefixes, intent);
     }
 
     @Override
     public String createAppSpecificSmsToken(int subId, String callingPkg, PendingIntent intent) {
+        if (callingPkg == null) {
+            callingPkg = getCallingPackage();
+        }
         return getPhone(subId).getAppSmsManager().createAppSpecificSmsToken(callingPkg, intent);
     }
 
     @Override
-    public void getSmsMessagesForFinancialApp(
-            int subId, String callingPkg, Bundle params, IFinancialSmsCallback callback) {
-        getPhone(subId).getAppSmsManager().getSmsMessagesForFinancialApp(
-                callingPkg, params, callback);
-    }
-
-    @Override
-    public int checkSmsShortCodeDestination(
-            int subId, String callingPackage, String destAddress, String countryIso) {
+    public int checkSmsShortCodeDestination(int subId, String callingPackage,
+            String callingFeatureId, String destAddress, String countryIso) {
+        if (callingPackage == null) {
+            callingPackage = getCallingPackage();
+        }
         if (!TelephonyPermissions.checkCallingOrSelfReadPhoneState(getPhone(subId).getContext(),
-                subId, callingPackage, "checkSmsShortCodeDestination")) {
+                subId, callingPackage, callingFeatureId, "checkSmsShortCodeDestination")) {
             return SmsManager.SMS_CATEGORY_NOT_SHORT_CODE;
         }
         final long identity = Binder.clearCallingIdentity();
@@ -539,19 +702,56 @@
      * Internal API to send visual voicemail related SMS. This is not exposed outside the phone
      * process, and should be called only after verifying that the caller is the default VVM app.
      */
-    public void sendVisualVoicemailSmsForSubscriber(String callingPackage, int subId,
-            String number, int port, String text, PendingIntent sentIntent) {
+    public void sendVisualVoicemailSmsForSubscriber(String callingPackage,
+            String callingAttributionTag, int subId, String number, int port, String text,
+            PendingIntent sentIntent) {
         if (port == 0) {
-            sendTextForSubscriberWithSelfPermissionsInternal(subId, callingPackage, number,
-                    null, text, sentIntent, null, false, true /* isForVvm */);
+            sendTextForSubscriberWithSelfPermissionsInternal(subId, callingPackage,
+                    callingAttributionTag, number, null, text, sentIntent, null, false,
+                    true /* isForVvm */);
         } else {
             byte[] data = text.getBytes(StandardCharsets.UTF_8);
-            sendDataForSubscriberWithSelfPermissionsInternal(subId, callingPackage, number,
-                    null, (short) port, data, sentIntent, null, true /* isForVvm */);
+            sendDataForSubscriberWithSelfPermissionsInternal(subId, callingPackage,
+                    callingAttributionTag, number, null, (short) port, data, sentIntent, null,
+                    true /* isForVvm */);
         }
     }
 
     @Override
+    public String getSmscAddressFromIccEfForSubscriber(int subId, String callingPackage) {
+        if (callingPackage == null) {
+            callingPackage = getCallingPackage();
+        }
+        IccSmsInterfaceManager iccSmsIntMgr = getIccSmsInterfaceManager(subId);
+        if (iccSmsIntMgr != null) {
+            return iccSmsIntMgr.getSmscAddressFromIccEf(callingPackage);
+        } else {
+            Rlog.e(LOG_TAG, "getSmscAddressFromIccEfForSubscriber iccSmsIntMgr is null"
+                    + " for Subscription: " + subId);
+            return null;
+        }
+    }
+
+    @Override
+    public boolean setSmscAddressOnIccEfForSubscriber(
+            String smsc, int subId, String callingPackage) {
+        if (callingPackage == null) {
+            callingPackage = getCallingPackage();
+        }
+        IccSmsInterfaceManager iccSmsIntMgr = getIccSmsInterfaceManager(subId);
+        if (iccSmsIntMgr != null) {
+            return iccSmsIntMgr.setSmscAddressOnIccEf(callingPackage, smsc);
+        } else {
+            Rlog.e(LOG_TAG, "setSmscAddressOnIccEfForSubscriber iccSmsIntMgr is null"
+                    + " for Subscription: " + subId);
+            return false;
+        }
+    }
+
+    /**
+     * Triggered by `adb shell dumpsys isms`
+     */
+    @Override
     protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
         if (!checkDumpPermission(mContext, LOG_TAG, pw)) {
             return;
@@ -607,4 +807,37 @@
                 .getSystemService(Context.TELEPHONY_SUBSCRIPTION_SERVICE);
         return manager.getActiveSubscriptionInfo(subId);
     }
+
+    /**
+     * Get the capacity count of sms on Icc card.
+     */
+    @Override
+    public int getSmsCapacityOnIccForSubscriber(int subId) {
+        IccSmsInterfaceManager iccSmsIntMgr = getIccSmsInterfaceManager(subId);
+
+        if (iccSmsIntMgr != null ) {
+            return iccSmsIntMgr.getSmsCapacityOnIcc();
+        } else {
+            Rlog.e(LOG_TAG, "iccSmsIntMgr is null for " + " subId: " + subId);
+            return 0;
+        }
+    }
+
+    /**
+     * Reset all cell broadcast ranges. Previously enabled ranges will become invalid after this.
+     *
+     * @param subId Subscription index
+     * @return {@code true} if succeeded, otherwise {@code false}.
+     */
+    @Override
+    public boolean resetAllCellBroadcastRanges(int subId) {
+        IccSmsInterfaceManager iccSmsIntMgr = getIccSmsInterfaceManager(subId);
+        if (iccSmsIntMgr != null) {
+            iccSmsIntMgr.resetAllCellBroadcastRanges();
+            return true;
+        } else {
+            Rlog.e(LOG_TAG, "iccSmsIntMgr is null for " + " subId: " + subId);
+            return false;
+        }
+    }
 }
diff --git a/src/java/com/android/internal/telephony/SmsDispatchersController.java b/src/java/com/android/internal/telephony/SmsDispatchersController.java
index b2ec134..d657eb0 100644
--- a/src/java/com/android/internal/telephony/SmsDispatchersController.java
+++ b/src/java/com/android/internal/telephony/SmsDispatchersController.java
@@ -18,6 +18,9 @@
 
 import static com.android.internal.telephony.IccSmsInterfaceManager.SMS_MESSAGE_PERIOD_NOT_SPECIFIED;
 import static com.android.internal.telephony.IccSmsInterfaceManager.SMS_MESSAGE_PRIORITY_NOT_SPECIFIED;
+import static com.android.internal.telephony.SmsResponse.NO_ERROR_CODE;
+import static com.android.internal.telephony.cdma.sms.BearerData.ERROR_NONE;
+import static com.android.internal.telephony.cdma.sms.BearerData.ERROR_TEMPORARY;
 
 import android.app.Activity;
 import android.app.PendingIntent;
@@ -33,7 +36,6 @@
 import android.os.UserManager;
 import android.provider.Telephony.Sms;
 import android.provider.Telephony.Sms.Intents;
-import android.telephony.Rlog;
 import android.telephony.ServiceState;
 import android.telephony.SmsManager;
 import android.telephony.SmsMessage;
@@ -44,11 +46,13 @@
 import com.android.internal.telephony.cdma.CdmaSMSDispatcher;
 import com.android.internal.telephony.gsm.GsmInboundSmsHandler;
 import com.android.internal.telephony.gsm.GsmSMSDispatcher;
+import com.android.telephony.Rlog;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.HashMap;
+import java.util.Map.Entry;
 
 /**
  *
@@ -428,91 +432,102 @@
      */
     public void sendRetrySms(SMSDispatcher.SmsTracker tracker) {
         String oldFormat = tracker.mFormat;
+        boolean retryUsingImsService = false;
 
-        // newFormat will be based on voice technology
+        if (!tracker.mUsesImsServiceForIms && mImsSmsDispatcher.isAvailable()) {
+            // If this tracker has not been handled by ImsSmsDispatcher yet and IMS Service is
+            // available now, retry this failed tracker using IMS Service.
+            retryUsingImsService = true;
+        }
+
+        // If retryUsingImsService is true, newFormat will be IMS SMS format. Otherwise, newFormat
+        // will be based on voice technology.
         String newFormat =
-                (PhoneConstants.PHONE_TYPE_CDMA == mPhone.getPhoneType())
-                        ? mCdmaDispatcher.getFormat() : mGsmDispatcher.getFormat();
+                retryUsingImsService
+                        ? mImsSmsDispatcher.getFormat()
+                        : (PhoneConstants.PHONE_TYPE_CDMA == mPhone.getPhoneType())
+                                ? mCdmaDispatcher.getFormat()
+                                : mGsmDispatcher.getFormat();
 
-        // was previously sent sms format match with voice tech?
-        if (oldFormat.equals(newFormat)) {
-            if (isCdmaFormat(newFormat)) {
-                Rlog.d(TAG, "old format matched new format (cdma)");
-                mCdmaDispatcher.sendSms(tracker);
-                return;
-            } else {
-                Rlog.d(TAG, "old format matched new format (gsm)");
-                mGsmDispatcher.sendSms(tracker);
+        Rlog.d(TAG, "old format(" + oldFormat + ") ==> new format (" + newFormat + ")");
+        if (!oldFormat.equals(newFormat)) {
+            // format didn't match, need to re-encode.
+            HashMap map = tracker.getData();
+
+            // to re-encode, fields needed are: scAddr, destAddr and text if originally sent as
+            // sendText or data and destPort if originally sent as sendData.
+            if (!(map.containsKey("scAddr") && map.containsKey("destAddr")
+                    && (map.containsKey("text")
+                    || (map.containsKey("data") && map.containsKey("destPort"))))) {
+                // should never come here...
+                Rlog.e(TAG, "sendRetrySms failed to re-encode per missing fields!");
+                tracker.onFailed(mContext, SmsManager.RESULT_SMS_SEND_RETRY_FAILED, NO_ERROR_CODE);
                 return;
             }
-        }
+            String scAddr = (String) map.get("scAddr");
+            String destAddr = (String) map.get("destAddr");
 
-        // format didn't match, need to re-encode.
-        HashMap map = tracker.getData();
+            SmsMessageBase.SubmitPduBase pdu = null;
+            // figure out from tracker if this was sendText/Data
+            if (map.containsKey("text")) {
+                Rlog.d(TAG, "sms failed was text");
+                String text = (String) map.get("text");
 
-        // to re-encode, fields needed are:  scAddr, destAddr, and
-        //   text if originally sent as sendText or
-        //   data and destPort if originally sent as sendData.
-        if (!(map.containsKey("scAddr") && map.containsKey("destAddr")
-                && (map.containsKey("text")
-                || (map.containsKey("data") && map.containsKey("destPort"))))) {
-            // should never come here...
-            Rlog.e(TAG, "sendRetrySms failed to re-encode per missing fields!");
-            tracker.onFailed(mContext, SmsManager.RESULT_ERROR_GENERIC_FAILURE, 0/*errorCode*/);
-            return;
-        }
-        String scAddr = (String) map.get("scAddr");
-        String destAddr = (String) map.get("destAddr");
+                if (isCdmaFormat(newFormat)) {
+                    pdu = com.android.internal.telephony.cdma.SmsMessage.getSubmitPdu(
+                            scAddr, destAddr, text, (tracker.mDeliveryIntent != null), null);
+                } else {
+                    pdu = com.android.internal.telephony.gsm.SmsMessage.getSubmitPdu(
+                            scAddr, destAddr, text, (tracker.mDeliveryIntent != null), null);
+                }
+            } else if (map.containsKey("data")) {
+                Rlog.d(TAG, "sms failed was data");
+                byte[] data = (byte[]) map.get("data");
+                Integer destPort = (Integer) map.get("destPort");
 
-        SmsMessageBase.SubmitPduBase pdu = null;
-        //    figure out from tracker if this was sendText/Data
-        if (map.containsKey("text")) {
-            Rlog.d(TAG, "sms failed was text");
-            String text = (String) map.get("text");
-
-            if (isCdmaFormat(newFormat)) {
-                Rlog.d(TAG, "old format (gsm) ==> new format (cdma)");
-                pdu = com.android.internal.telephony.cdma.SmsMessage.getSubmitPdu(
-                        scAddr, destAddr, text, (tracker.mDeliveryIntent != null), null);
-            } else {
-                Rlog.d(TAG, "old format (cdma) ==> new format (gsm)");
-                pdu = com.android.internal.telephony.gsm.SmsMessage.getSubmitPdu(
-                        scAddr, destAddr, text, (tracker.mDeliveryIntent != null), null);
-            }
-        } else if (map.containsKey("data")) {
-            Rlog.d(TAG, "sms failed was data");
-            byte[] data = (byte[]) map.get("data");
-            Integer destPort = (Integer) map.get("destPort");
-
-            if (isCdmaFormat(newFormat)) {
-                Rlog.d(TAG, "old format (gsm) ==> new format (cdma)");
-                pdu = com.android.internal.telephony.cdma.SmsMessage.getSubmitPdu(
+                if (isCdmaFormat(newFormat)) {
+                    pdu = com.android.internal.telephony.cdma.SmsMessage.getSubmitPdu(
                             scAddr, destAddr, destPort.intValue(), data,
                             (tracker.mDeliveryIntent != null));
-            } else {
-                Rlog.d(TAG, "old format (cdma) ==> new format (gsm)");
-                pdu = com.android.internal.telephony.gsm.SmsMessage.getSubmitPdu(
+                } else {
+                    pdu = com.android.internal.telephony.gsm.SmsMessage.getSubmitPdu(
                             scAddr, destAddr, destPort.intValue(), data,
                             (tracker.mDeliveryIntent != null));
+                }
             }
+
+            // replace old smsc and pdu with newly encoded ones
+            map.put("smsc", pdu.encodedScAddress);
+            map.put("pdu", pdu.encodedMessage);
+            tracker.mFormat = newFormat;
         }
 
-        // replace old smsc and pdu with newly encoded ones
-        map.put("smsc", pdu.encodedScAddress);
-        map.put("pdu", pdu.encodedMessage);
+        SMSDispatcher dispatcher =
+                retryUsingImsService
+                        ? mImsSmsDispatcher
+                        : (isCdmaFormat(newFormat)) ? mCdmaDispatcher : mGsmDispatcher;
 
-        SMSDispatcher dispatcher = (isCdmaFormat(newFormat)) ? mCdmaDispatcher : mGsmDispatcher;
-
-        tracker.mFormat = dispatcher.getFormat();
         dispatcher.sendSms(tracker);
     }
 
+    /**
+     * SMS over IMS is supported if IMS is registered and SMS is supported on IMS.
+     *
+     * @return true if SMS over IMS is supported via an IMS Service or mIms is true for the older
+     *         implementation. Otherwise, false.
+     */
     public boolean isIms() {
-        return mIms;
+        return mImsSmsDispatcher.isAvailable() ? true : mIms;
     }
 
+    /**
+     * Gets SMS format supported on IMS.
+     *
+     * @return the SMS format from an IMS Service if available. Otherwise, mImsSmsFormat for the
+     *         older implementation.
+     */
     public String getImsSmsFormat() {
-        return mImsSmsFormat;
+        return mImsSmsDispatcher.isAvailable() ? mImsSmsDispatcher.getFormat() : mImsSmsFormat;
     }
 
     /**
@@ -528,7 +543,7 @@
             return (PhoneConstants.PHONE_TYPE_CDMA == mPhone.getPhoneType());
         }
         // IMS is registered with SMS support
-        return isCdmaFormat(mImsSmsFormat);
+        return isCdmaFormat(getImsSmsFormat());
     }
 
     /**
@@ -628,20 +643,22 @@
      */
     public void sendText(String destAddr, String scAddr, String text, PendingIntent sentIntent,
             PendingIntent deliveryIntent, Uri messageUri, String callingPkg, boolean persistMessage,
-            int priority, boolean expectMore, int validityPeriod, boolean isForVvm) {
+            int priority, boolean expectMore, int validityPeriod, boolean isForVvm,
+            long messageId) {
         if (mImsSmsDispatcher.isAvailable() || mImsSmsDispatcher.isEmergencySmsSupport(destAddr)) {
             mImsSmsDispatcher.sendText(destAddr, scAddr, text, sentIntent, deliveryIntent,
                     messageUri, callingPkg, persistMessage, SMS_MESSAGE_PRIORITY_NOT_SPECIFIED,
-                    false /*expectMore*/, SMS_MESSAGE_PERIOD_NOT_SPECIFIED, isForVvm);
+                    false /*expectMore*/, SMS_MESSAGE_PERIOD_NOT_SPECIFIED, isForVvm,
+                    messageId);
         } else {
             if (isCdmaMo()) {
                 mCdmaDispatcher.sendText(destAddr, scAddr, text, sentIntent, deliveryIntent,
                         messageUri, callingPkg, persistMessage, priority, expectMore,
-                        validityPeriod, isForVvm);
+                        validityPeriod, isForVvm, messageId);
             } else {
                 mGsmDispatcher.sendText(destAddr, scAddr, text, sentIntent, deliveryIntent,
                         messageUri, callingPkg, persistMessage, priority, expectMore,
-                        validityPeriod, isForVvm);
+                        validityPeriod, isForVvm, messageId);
             }
         }
     }
@@ -690,33 +707,37 @@
      *  Validity Period(Minimum) -> 5 mins
      *  Validity Period(Maximum) -> 635040 mins(i.e.63 weeks).
      *  Any Other values included Negative considered as Invalid Validity Period of the message.
-
+     * @param messageId An id that uniquely identifies the message requested to be sent.
+     *                 Used for logging and diagnostics purposes. The id may be 0.
+     *
      */
     protected void sendMultipartText(String destAddr, String scAddr,
             ArrayList<String> parts, ArrayList<PendingIntent> sentIntents,
             ArrayList<PendingIntent> deliveryIntents, Uri messageUri, String callingPkg,
-            boolean persistMessage, int priority, boolean expectMore, int validityPeriod) {
+            boolean persistMessage, int priority, boolean expectMore, int validityPeriod,
+            long messageId) {
         if (mImsSmsDispatcher.isAvailable()) {
             mImsSmsDispatcher.sendMultipartText(destAddr, scAddr, parts, sentIntents,
                     deliveryIntents, messageUri, callingPkg, persistMessage,
                     SMS_MESSAGE_PRIORITY_NOT_SPECIFIED,
-                    false /*expectMore*/, SMS_MESSAGE_PERIOD_NOT_SPECIFIED);
+                    false /*expectMore*/, SMS_MESSAGE_PERIOD_NOT_SPECIFIED,
+                    messageId);
         } else {
             if (isCdmaMo()) {
                 mCdmaDispatcher.sendMultipartText(destAddr, scAddr, parts, sentIntents,
                         deliveryIntents, messageUri, callingPkg, persistMessage, priority,
-                        expectMore, validityPeriod);
+                        expectMore, validityPeriod, messageId);
             } else {
                 mGsmDispatcher.sendMultipartText(destAddr, scAddr, parts, sentIntents,
                         deliveryIntents, messageUri, callingPkg, persistMessage, priority,
-                        expectMore, validityPeriod);
+                        expectMore, validityPeriod, messageId);
             }
         }
     }
 
     /**
      * Returns the premium SMS permission for the specified package. If the package has never
-     * been seen before, the default {@link SmsUsageMonitor#PREMIUM_SMS_PERMISSION_ASK_USER}
+     * been seen before, the default {@link SmsUsageMonitor#PREMIUM_SMS_PERMISSION_UNKNOWN}
      * will be returned.
      * @param packageName the name of the package to query permission
      * @return one of {@link SmsUsageMonitor#PREMIUM_SMS_PERMISSION_UNKNOWN},
@@ -745,6 +766,30 @@
     }
 
     /**
+     * Handles the sms status report for the sent sms through ImsSmsDispatcher. Carriers can send
+     * the report over CS even if the previously submitted SMS-SUBMIT was sent over IMS. For this
+     * case, finds a corresponding tracker from the tracker map in ImsSmsDispatcher and handles it.
+     *
+     * @param messageRef the TP-MR of the previously submitted SMS-SUBMIT in the report.
+     * @param format the format.
+     * @param pdu the pdu of the report.
+     */
+    public void handleSentOverImsStatusReport(int messageRef, String format, byte[] pdu) {
+        for (Entry<Integer, SMSDispatcher.SmsTracker> entry :
+                mImsSmsDispatcher.mTrackers.entrySet()) {
+            int token = entry.getKey();
+            SMSDispatcher.SmsTracker tracker = entry.getValue();
+            if (tracker.mMessageRef == messageRef) {
+                Pair<Boolean, Boolean> result = handleSmsStatusReport(tracker, format, pdu);
+                if (result.second) {
+                    mImsSmsDispatcher.mTrackers.remove(token);
+                }
+                return;
+            }
+        }
+    }
+
+    /**
      * Triggers the correct method for handling the sms status report based on the format.
      *
      * @param tracker the sms tracker.
@@ -766,9 +811,23 @@
 
     private Pair<Boolean, Boolean> handleCdmaStatusReport(SMSDispatcher.SmsTracker tracker,
             String format, byte[] pdu) {
-        tracker.updateSentMessageStatus(mContext, Sms.STATUS_COMPLETE);
-        boolean success = triggerDeliveryIntent(tracker, format, pdu);
-        return new Pair(success, true /* complete */);
+        com.android.internal.telephony.cdma.SmsMessage sms =
+                com.android.internal.telephony.cdma.SmsMessage.createFromPdu(pdu);
+        boolean complete = false;
+        boolean success = false;
+        if (sms != null) {
+            // The status is composed of an error class (bits 25-24) and a status code (bits 23-16).
+            int errorClass = (sms.getStatus() >> 24) & 0x03;
+            if (errorClass != ERROR_TEMPORARY) {
+                // Update the message status (COMPLETE or FAILED)
+                tracker.updateSentMessageStatus(
+                        mContext,
+                        (errorClass == ERROR_NONE) ? Sms.STATUS_COMPLETE : Sms.STATUS_FAILED);
+                complete = true;
+            }
+            success = triggerDeliveryIntent(tracker, format, pdu);
+        }
+        return new Pair(success, complete);
     }
 
     private Pair<Boolean, Boolean> handleGsmStatusReport(SMSDispatcher.SmsTracker tracker,
diff --git a/src/java/com/android/internal/telephony/SmsNumberUtils.java b/src/java/com/android/internal/telephony/SmsNumberUtils.java
deleted file mode 100644
index af7d871..0000000
--- a/src/java/com/android/internal/telephony/SmsNumberUtils.java
+++ /dev/null
@@ -1,644 +0,0 @@
-/*
- * Copyright (C) 2014 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;
-
-import android.content.Context;
-import android.database.Cursor;
-import android.database.SQLException;
-import android.os.Binder;
-import android.os.Build;
-import android.os.PersistableBundle;
-import android.telephony.CarrierConfigManager;
-import android.telephony.PhoneNumberUtils;
-import android.telephony.Rlog;
-import android.telephony.TelephonyManager;
-import android.text.TextUtils;
-
-import com.android.internal.telephony.HbpcdLookup.MccIdd;
-import com.android.internal.telephony.HbpcdLookup.MccLookup;
-
-import java.util.ArrayList;
-import java.util.HashMap;
-
-
- /**
- * This class implements handle the MO SMS target address before sending.
- * This is special for VZW requirement. Follow the specifications of assisted dialing
- * of MO SMS while traveling on VZW CDMA, international CDMA or GSM markets.
- * {@hide}
- */
-public class SmsNumberUtils {
-    private static final String TAG = "SmsNumberUtils";
-    private static final boolean DBG = Build.IS_DEBUGGABLE;
-
-    private static final String PLUS_SIGN = "+";
-
-    private static final int NANP_SHORT_LENGTH = 7;
-    private static final int NANP_MEDIUM_LENGTH = 10;
-    private static final int NANP_LONG_LENGTH = 11;
-
-    private static final int NANP_CC = 1;
-    private static final String NANP_NDD = "1";
-    private static final String NANP_IDD = "011";
-
-    private static final int MIN_COUNTRY_AREA_LOCAL_LENGTH = 10;
-
-    private static final int GSM_UMTS_NETWORK = 0;
-    private static final int CDMA_HOME_NETWORK = 1;
-    private static final int CDMA_ROAMING_NETWORK = 2;
-
-    private static final int NP_NONE = 0;
-    private static final int NP_NANP_BEGIN = 1;
-
-    /* <Phone Number>, <NXX>-<XXXX> N[2-9] */
-    private static final int NP_NANP_LOCAL = NP_NANP_BEGIN;
-
-    /* <Area_code>-<Phone Number>, <NXX>-<NXX>-<XXXX> N[2-9] */
-    private static final int NP_NANP_AREA_LOCAL = NP_NANP_BEGIN + 1;
-
-    /* <1>-<Area_code>-<Phone Number>, 1-<NXX>-<NXX>-<XXXX> N[2-9] */
-    private static final int NP_NANP_NDD_AREA_LOCAL = NP_NANP_BEGIN + 2;
-
-    /* <+><U.S.Country_code><Area_code><Phone Number>, +1-<NXX>-<NXX>-<XXXX> N[2-9] */
-    private static final int NP_NANP_NBPCD_CC_AREA_LOCAL = NP_NANP_BEGIN + 3;
-
-    /* <Local_IDD><Country_code><Area_code><Phone Number>, 001-1-<NXX>-<NXX>-<XXXX> N[2-9] */
-    private static final int NP_NANP_LOCALIDD_CC_AREA_LOCAL = NP_NANP_BEGIN + 4;
-
-    /* <+><Home_IDD><Country_code><Area_code><Phone Number>, +011-1-<NXX>-<NXX>-<XXXX> N[2-9] */
-    private static final int NP_NANP_NBPCD_HOMEIDD_CC_AREA_LOCAL = NP_NANP_BEGIN + 5;
-
-    private static final int NP_INTERNATIONAL_BEGIN = 100;
-    /* <+>-<Home_IDD>-<Country_code>-<Area_code>-<Phone Number>, +011-86-25-86281234 */
-    private static final int NP_NBPCD_HOMEIDD_CC_AREA_LOCAL = NP_INTERNATIONAL_BEGIN;
-
-    /* <Home_IDD>-<Country_code>-<Area_code>-<Phone Number>, 011-86-25-86281234 */
-    private static final int NP_HOMEIDD_CC_AREA_LOCAL = NP_INTERNATIONAL_BEGIN + 1;
-
-    /* <NBPCD>-<Country_code>-<Area_code>-<Phone Number>, +1-86-25-86281234 */
-    private static final int NP_NBPCD_CC_AREA_LOCAL = NP_INTERNATIONAL_BEGIN + 2;
-
-    /* <Local_IDD>-<Country_code>-<Area_code>-<Phone Number>, 00-86-25-86281234 */
-    private static final int NP_LOCALIDD_CC_AREA_LOCAL = NP_INTERNATIONAL_BEGIN + 3;
-
-    /* <Country_code>-<Area_code>-<Phone Number>, 86-25-86281234*/
-    private static final int NP_CC_AREA_LOCAL = NP_INTERNATIONAL_BEGIN + 4;
-
-    private static int[] ALL_COUNTRY_CODES = null;
-    private static int MAX_COUNTRY_CODES_LENGTH;
-    private static HashMap<String, ArrayList<String>> IDDS_MAPS =
-            new HashMap<String, ArrayList<String>>();
-
-    private static class NumberEntry {
-        public String number;
-        public String IDD;
-        public int countryCode;
-        public NumberEntry(String number) {
-            this.number = number;
-        }
-    }
-
-    /* Breaks the given number down and formats it according to the rules
-     * for different number plans and different network.
-     *
-     * @param number destination number which need to be format
-     * @param activeMcc current network's mcc
-     * @param networkType current network type
-     *
-     * @return the number after formatting.
-     */
-    private static String formatNumber(Context context, String number,
-                               String activeMcc,
-                               int networkType) {
-        if (number == null ) {
-            throw new IllegalArgumentException("number is null");
-        }
-
-        if (activeMcc == null || activeMcc.trim().length() == 0) {
-            throw new IllegalArgumentException("activeMcc is null or empty!");
-        }
-
-        String networkPortionNumber = PhoneNumberUtils.extractNetworkPortion(number);
-        if (networkPortionNumber == null || networkPortionNumber.length() == 0) {
-            throw new IllegalArgumentException("Number is invalid!");
-        }
-
-        NumberEntry numberEntry = new NumberEntry(networkPortionNumber);
-        ArrayList<String> allIDDs = getAllIDDs(context, activeMcc);
-
-        // First check whether the number is a NANP number.
-        int nanpState = checkNANP(numberEntry, allIDDs);
-        if (DBG) Rlog.d(TAG, "NANP type: " + getNumberPlanType(nanpState));
-
-        if ((nanpState == NP_NANP_LOCAL)
-            || (nanpState == NP_NANP_AREA_LOCAL)
-            || (nanpState == NP_NANP_NDD_AREA_LOCAL)) {
-            return networkPortionNumber;
-        } else if (nanpState == NP_NANP_NBPCD_CC_AREA_LOCAL) {
-            if (networkType == CDMA_HOME_NETWORK
-                    || networkType == CDMA_ROAMING_NETWORK) {
-                // Remove "+"
-                return networkPortionNumber.substring(1);
-            } else {
-                return networkPortionNumber;
-            }
-        } else if (nanpState == NP_NANP_LOCALIDD_CC_AREA_LOCAL) {
-            if (networkType == CDMA_HOME_NETWORK) {
-                return networkPortionNumber;
-            } else if (networkType == GSM_UMTS_NETWORK) {
-                // Remove the local IDD and replace with "+"
-                int iddLength  =  numberEntry.IDD != null ? numberEntry.IDD.length() : 0;
-                return PLUS_SIGN + networkPortionNumber.substring(iddLength);
-            } else if (networkType == CDMA_ROAMING_NETWORK) {
-                // Remove the local IDD
-                int iddLength  =  numberEntry.IDD != null ? numberEntry.IDD.length() : 0;
-                return  networkPortionNumber.substring(iddLength);
-            }
-        }
-
-        int internationalState = checkInternationalNumberPlan(context, numberEntry, allIDDs,
-                NANP_IDD);
-        if (DBG) Rlog.d(TAG, "International type: " + getNumberPlanType(internationalState));
-        String returnNumber = null;
-
-        switch (internationalState) {
-            case NP_NBPCD_HOMEIDD_CC_AREA_LOCAL:
-                if (networkType == GSM_UMTS_NETWORK) {
-                    // Remove "+"
-                    returnNumber = networkPortionNumber.substring(1);
-                }
-                break;
-
-            case NP_NBPCD_CC_AREA_LOCAL:
-                // Replace "+" with "011"
-                returnNumber = NANP_IDD + networkPortionNumber.substring(1);
-                break;
-
-            case NP_LOCALIDD_CC_AREA_LOCAL:
-                if (networkType == GSM_UMTS_NETWORK || networkType == CDMA_ROAMING_NETWORK) {
-                    int iddLength  =  numberEntry.IDD != null ? numberEntry.IDD.length() : 0;
-                    // Replace <Local IDD> to <Home IDD>("011")
-                    returnNumber = NANP_IDD + networkPortionNumber.substring(iddLength);
-                }
-                break;
-
-            case NP_CC_AREA_LOCAL:
-                int countryCode = numberEntry.countryCode;
-
-                if (!inExceptionListForNpCcAreaLocal(numberEntry)
-                    && networkPortionNumber.length() >= 11 && countryCode != NANP_CC) {
-                    // Add "011"
-                    returnNumber = NANP_IDD + networkPortionNumber;
-                }
-                break;
-
-            case NP_HOMEIDD_CC_AREA_LOCAL:
-                returnNumber = networkPortionNumber;
-                break;
-
-            default:
-                // Replace "+" with 011 in CDMA network if the number's country
-                // code is not in the HbpcdLookup database.
-                if (networkPortionNumber.startsWith(PLUS_SIGN)
-                    && (networkType == CDMA_HOME_NETWORK || networkType == CDMA_ROAMING_NETWORK)) {
-                    if (networkPortionNumber.startsWith(PLUS_SIGN + NANP_IDD)) {
-                        // Only remove "+"
-                        returnNumber = networkPortionNumber.substring(1);
-                    } else {
-                        // Replace "+" with "011"
-                        returnNumber = NANP_IDD + networkPortionNumber.substring(1);
-                    }
-                }
-        }
-
-        if (returnNumber == null) {
-            returnNumber = networkPortionNumber;
-        }
-        return returnNumber;
-    }
-
-    /* Query International direct dialing from HbpcdLookup.db
-     * for specified country code
-     *
-     * @param mcc current network's country code
-     *
-     * @return the IDD array list.
-     */
-    private static ArrayList<String> getAllIDDs(Context context, String mcc) {
-        ArrayList<String> allIDDs = IDDS_MAPS.get(mcc);
-        if (allIDDs != null) {
-            return allIDDs;
-        } else {
-            allIDDs = new ArrayList<String>();
-        }
-
-        String projection[] = {MccIdd.IDD, MccIdd.MCC};
-        String where = null;
-
-        // if mcc is null         : return all rows
-        // if mcc is empty-string : return those rows whose mcc is emptry-string
-        String[] selectionArgs = null;
-        if (mcc != null) {
-            where = MccIdd.MCC + "=?";
-            selectionArgs = new String[] {mcc};
-        }
-
-        Cursor cursor = null;
-        try {
-            cursor = context.getContentResolver().query(MccIdd.CONTENT_URI, projection,
-                    where, selectionArgs, null);
-            if (cursor.getCount() > 0) {
-                while (cursor.moveToNext()) {
-                    String idd = cursor.getString(0);
-                    if (!allIDDs.contains(idd)) {
-                        allIDDs.add(idd);
-                    }
-                }
-            }
-        } catch (SQLException e) {
-            Rlog.e(TAG, "Can't access HbpcdLookup database", e);
-        } finally {
-            if (cursor != null) {
-                cursor.close();
-            }
-        }
-
-        IDDS_MAPS.put(mcc, allIDDs);
-
-        if (DBG) Rlog.d(TAG, "MCC = " + mcc + ", all IDDs = " + allIDDs);
-        return allIDDs;
-    }
-
-
-    /* Verify if the the destination number is a NANP number
-     *
-     * @param numberEntry including number and IDD array
-     * @param allIDDs the IDD array list of the current network's country code
-     *
-     * @return the number plan type related NANP
-     */
-    private static int checkNANP(NumberEntry numberEntry, ArrayList<String> allIDDs) {
-        boolean isNANP = false;
-        String number = numberEntry.number;
-
-        if (number.length() == NANP_SHORT_LENGTH) {
-            // 7 digits - Seven digit phone numbers
-            char firstChar = number.charAt(0);
-            if (firstChar >= '2' && firstChar <= '9') {
-                isNANP = true;
-                for (int i=1; i< NANP_SHORT_LENGTH; i++ ) {
-                    char c= number.charAt(i);
-                    if (!PhoneNumberUtils.isISODigit(c)) {
-                        isNANP = false;
-                        break;
-                    }
-                }
-            }
-            if (isNANP) {
-                return NP_NANP_LOCAL;
-            }
-        } else if (number.length() == NANP_MEDIUM_LENGTH) {
-            // 10 digits - Three digit area code followed by seven digit phone numbers/
-            if (isNANP(number)) {
-                return NP_NANP_AREA_LOCAL;
-            }
-        } else if (number.length() == NANP_LONG_LENGTH) {
-            // 11 digits - One digit U.S. NDD(National Direct Dial) prefix '1',
-            // followed by three digit area code and seven digit phone numbers
-            if (isNANP(number)) {
-                return NP_NANP_NDD_AREA_LOCAL;
-            }
-        } else if (number.startsWith(PLUS_SIGN)) {
-            number = number.substring(1);
-            if (number.length() == NANP_LONG_LENGTH) {
-                // '+' and 11 digits -'+', followed by NANP CC prefix '1' followed by
-                // three digit area code and seven digit phone numbers
-                if (isNANP(number)) {
-                    return NP_NANP_NBPCD_CC_AREA_LOCAL;
-                }
-            } else if (number.startsWith(NANP_IDD) && number.length() == NANP_LONG_LENGTH + 3) {
-                // '+' and 14 digits -'+', followed by NANP IDD "011" followed by NANP CC
-                // prefix '1' followed by three digit area code and seven digit phone numbers
-                number = number.substring(3);
-                if (isNANP(number)) {
-                    return NP_NANP_NBPCD_HOMEIDD_CC_AREA_LOCAL;
-                }
-            }
-        } else {
-            // Check whether it's NP_NANP_LOCALIDD_CC_AREA_LOCAL
-            for (String idd : allIDDs) {
-                if (number.startsWith(idd)) {
-                    String number2 = number.substring(idd.length());
-                    if(number2 !=null && number2.startsWith(String.valueOf(NANP_CC))){
-                        if (isNANP(number2)) {
-                            numberEntry.IDD = idd;
-                            return NP_NANP_LOCALIDD_CC_AREA_LOCAL;
-                        }
-                    }
-                }
-            }
-        }
-
-        return NP_NONE;
-    }
-
-    private static boolean isNANP(String number) {
-        if (number.length() == NANP_MEDIUM_LENGTH
-            || (number.length() == NANP_LONG_LENGTH  && number.startsWith(NANP_NDD))) {
-            if (number.length() == NANP_LONG_LENGTH) {
-                number = number.substring(1);
-            }
-            return (PhoneNumberUtils.isNanp(number));
-        }
-        return false;
-    }
-
-    /* Verify if the the destination number is an internal number
-     *
-     * @param numberEntry including number and IDD array
-     * @param allIDDs the IDD array list of the current network's country code
-     *
-     * @return the number plan type related international number
-     */
-    private static int checkInternationalNumberPlan(Context context, NumberEntry numberEntry,
-            ArrayList<String> allIDDs,String homeIDD) {
-        String number = numberEntry.number;
-        int countryCode = -1;
-
-        if (number.startsWith(PLUS_SIGN)) {
-            // +xxxxxxxxxx
-            String numberNoNBPCD = number.substring(1);
-            if (numberNoNBPCD.startsWith(homeIDD)) {
-                // +011xxxxxxxx
-                String numberCountryAreaLocal = numberNoNBPCD.substring(homeIDD.length());
-                if ((countryCode = getCountryCode(context, numberCountryAreaLocal)) > 0) {
-                    numberEntry.countryCode = countryCode;
-                    return NP_NBPCD_HOMEIDD_CC_AREA_LOCAL;
-                }
-            } else if ((countryCode = getCountryCode(context, numberNoNBPCD)) > 0) {
-                numberEntry.countryCode = countryCode;
-                return NP_NBPCD_CC_AREA_LOCAL;
-            }
-
-        } else if (number.startsWith(homeIDD)) {
-            // 011xxxxxxxxx
-            String numberCountryAreaLocal = number.substring(homeIDD.length());
-            if ((countryCode = getCountryCode(context, numberCountryAreaLocal)) > 0) {
-                numberEntry.countryCode = countryCode;
-                return NP_HOMEIDD_CC_AREA_LOCAL;
-            }
-        } else {
-            for (String exitCode : allIDDs) {
-                if (number.startsWith(exitCode)) {
-                    String numberNoIDD = number.substring(exitCode.length());
-                    if ((countryCode = getCountryCode(context, numberNoIDD)) > 0) {
-                        numberEntry.countryCode = countryCode;
-                        numberEntry.IDD = exitCode;
-                        return NP_LOCALIDD_CC_AREA_LOCAL;
-                    }
-                }
-            }
-
-            if (!number.startsWith("0") && (countryCode = getCountryCode(context, number)) > 0) {
-                numberEntry.countryCode = countryCode;
-                return NP_CC_AREA_LOCAL;
-            }
-        }
-        return NP_NONE;
-    }
-
-    /**
-     *  Returns the country code from the given number.
-     */
-    private static int getCountryCode(Context context, String number) {
-        int countryCode = -1;
-        if (number.length() >= MIN_COUNTRY_AREA_LOCAL_LENGTH) {
-            // Check Country code
-            int[] allCCs = getAllCountryCodes(context);
-            if (allCCs == null) {
-                return countryCode;
-            }
-
-            int[] ccArray = new int[MAX_COUNTRY_CODES_LENGTH];
-            for (int i = 0; i < MAX_COUNTRY_CODES_LENGTH; i ++) {
-                ccArray[i] = Integer.parseInt(number.substring(0, i+1));
-            }
-
-            for (int i = 0; i < allCCs.length; i ++) {
-                int tempCC = allCCs[i];
-                for (int j = 0; j < MAX_COUNTRY_CODES_LENGTH; j ++) {
-                    if (tempCC == ccArray[j]) {
-                        if (DBG) Rlog.d(TAG, "Country code = " + tempCC);
-                        return tempCC;
-                    }
-                }
-            }
-        }
-
-        return countryCode;
-    }
-
-    /**
-     *  Gets all country Codes information with given MCC.
-     */
-    private static int[] getAllCountryCodes(Context context) {
-        if (ALL_COUNTRY_CODES != null) {
-            return ALL_COUNTRY_CODES;
-        }
-
-        Cursor cursor = null;
-        try {
-            String projection[] = {MccLookup.COUNTRY_CODE};
-            cursor = context.getContentResolver().query(MccLookup.CONTENT_URI,
-                    projection, null, null, null);
-
-            if (cursor.getCount() > 0) {
-                ALL_COUNTRY_CODES = new int[cursor.getCount()];
-                int i = 0;
-                while (cursor.moveToNext()) {
-                    int countryCode = cursor.getInt(0);
-                    ALL_COUNTRY_CODES[i++] = countryCode;
-                    int length = String.valueOf(countryCode).trim().length();
-                    if (length > MAX_COUNTRY_CODES_LENGTH) {
-                        MAX_COUNTRY_CODES_LENGTH = length;
-                    }
-                }
-            }
-        } catch (SQLException e) {
-            Rlog.e(TAG, "Can't access HbpcdLookup database", e);
-        } finally {
-            if (cursor != null) {
-                cursor.close();
-            }
-        }
-        return ALL_COUNTRY_CODES;
-    }
-
-    private static boolean inExceptionListForNpCcAreaLocal(NumberEntry numberEntry) {
-        int countryCode = numberEntry.countryCode;
-        boolean result = (numberEntry.number.length() == 12
-                          && (countryCode == 7 || countryCode == 20
-                              || countryCode == 65 || countryCode == 90));
-        return result;
-    }
-
-    private static String getNumberPlanType(int state) {
-        String numberPlanType = "Number Plan type (" + state + "): ";
-
-        if (state == NP_NANP_LOCAL) {
-            numberPlanType = "NP_NANP_LOCAL";
-        } else if (state == NP_NANP_AREA_LOCAL) {
-            numberPlanType = "NP_NANP_AREA_LOCAL";
-        } else if (state  == NP_NANP_NDD_AREA_LOCAL) {
-            numberPlanType = "NP_NANP_NDD_AREA_LOCAL";
-        } else if (state == NP_NANP_NBPCD_CC_AREA_LOCAL) {
-            numberPlanType = "NP_NANP_NBPCD_CC_AREA_LOCAL";
-        } else if (state == NP_NANP_LOCALIDD_CC_AREA_LOCAL) {
-            numberPlanType = "NP_NANP_LOCALIDD_CC_AREA_LOCAL";
-        } else if (state == NP_NANP_NBPCD_HOMEIDD_CC_AREA_LOCAL) {
-            numberPlanType = "NP_NANP_NBPCD_HOMEIDD_CC_AREA_LOCAL";
-        } else if (state == NP_NBPCD_HOMEIDD_CC_AREA_LOCAL) {
-            numberPlanType = "NP_NBPCD_HOMEIDD_CC_AREA_LOCAL";
-        } else if (state == NP_HOMEIDD_CC_AREA_LOCAL) {
-            numberPlanType = "NP_HOMEIDD_CC_AREA_LOCAL";
-        } else if (state == NP_NBPCD_CC_AREA_LOCAL) {
-            numberPlanType = "NP_NBPCD_CC_AREA_LOCAL";
-        } else if (state == NP_LOCALIDD_CC_AREA_LOCAL) {
-            numberPlanType = "NP_LOCALIDD_CC_AREA_LOCAL";
-        } else if (state == NP_CC_AREA_LOCAL) {
-            numberPlanType = "NP_CC_AREA_LOCAL";
-        } else {
-            numberPlanType = "Unknown type";
-        }
-        return numberPlanType;
-    }
-
-    /**
-     *  Filter the destination number if using VZW sim card.
-     */
-    public static String filterDestAddr(Phone phone, String destAddr) {
-        if (DBG) Rlog.d(TAG, "enter filterDestAddr. destAddr=\"" + Rlog.pii(TAG, destAddr) + "\"" );
-
-        if (destAddr == null || !PhoneNumberUtils.isGlobalPhoneNumber(destAddr)) {
-            Rlog.w(TAG, "destAddr" + Rlog.pii(TAG, destAddr) +
-                    " is not a global phone number! Nothing changed.");
-            return destAddr;
-        }
-
-        final String networkOperator = TelephonyManager.from(phone.getContext()).
-                getNetworkOperator(phone.getSubId());
-        String result = null;
-
-        if (needToConvert(phone)) {
-            final int networkType = getNetworkType(phone);
-            if (networkType != -1 && !TextUtils.isEmpty(networkOperator)) {
-                String networkMcc = networkOperator.substring(0, 3);
-                if (networkMcc != null && networkMcc.trim().length() > 0) {
-                    result = formatNumber(phone.getContext(), destAddr, networkMcc, networkType);
-                }
-            }
-        }
-
-        if (DBG) {
-            Rlog.d(TAG, "destAddr is " + ((result != null)?"formatted.":"not formatted."));
-            Rlog.d(TAG, "leave filterDestAddr, new destAddr=\"" + (result != null ? Rlog.pii(TAG,
-                    result) : Rlog.pii(TAG, destAddr)) + "\"");
-        }
-        return result != null ? result : destAddr;
-    }
-
-    /**
-     * Returns the current network type
-     */
-    private static int getNetworkType(Phone phone) {
-        int networkType = -1;
-        int phoneType = phone.getPhoneType();
-
-        if (phoneType == PhoneConstants.PHONE_TYPE_GSM) {
-            networkType = GSM_UMTS_NETWORK;
-        } else if (phoneType == PhoneConstants.PHONE_TYPE_CDMA) {
-            if (isInternationalRoaming(phone)) {
-                networkType = CDMA_ROAMING_NETWORK;
-            } else {
-                networkType = CDMA_HOME_NETWORK;
-            }
-        } else {
-            if (DBG) Rlog.w(TAG, "warning! unknown mPhoneType value=" + phoneType);
-        }
-
-        return networkType;
-    }
-
-    private static boolean isInternationalRoaming(Phone phone) {
-        String operatorIsoCountry = TelephonyManager.from(phone.getContext()).
-                getNetworkCountryIsoForPhone(phone.getPhoneId());
-        String simIsoCountry = TelephonyManager.from(phone.getContext()).getSimCountryIsoForPhone(
-                phone.getPhoneId());
-        boolean internationalRoaming = !TextUtils.isEmpty(operatorIsoCountry)
-                && !TextUtils.isEmpty(simIsoCountry)
-                && !simIsoCountry.equals(operatorIsoCountry);
-        if (internationalRoaming) {
-            if ("us".equals(simIsoCountry)) {
-                internationalRoaming = !"vi".equals(operatorIsoCountry);
-            } else if ("vi".equals(simIsoCountry)) {
-                internationalRoaming = !"us".equals(operatorIsoCountry);
-            }
-        }
-        return internationalRoaming;
-    }
-
-    private static boolean needToConvert(Phone phone) {
-        // Calling package may not have READ_PHONE_STATE which is required for getConfig().
-        // Clear the calling identity so that it is called as self.
-        final long identity = Binder.clearCallingIdentity();
-        try {
-            CarrierConfigManager configManager = (CarrierConfigManager)
-                    phone.getContext().getSystemService(Context.CARRIER_CONFIG_SERVICE);
-            if (configManager != null) {
-                PersistableBundle bundle = configManager.getConfigForSubId(phone.getSubId());
-                if (bundle != null) {
-                    return bundle.getBoolean(CarrierConfigManager
-                            .KEY_SMS_REQUIRES_DESTINATION_NUMBER_CONVERSION_BOOL);
-                }
-            }
-        } finally {
-            Binder.restoreCallingIdentity(identity);
-        }
-        // by default this value is false
-        return false;
-    }
-
-    private static boolean compareGid1(Phone phone, String serviceGid1) {
-        String gid1 = phone.getGroupIdLevel1();
-        boolean ret = true;
-
-        if (TextUtils.isEmpty(serviceGid1)) {
-            if (DBG) Rlog.d(TAG, "compareGid1 serviceGid is empty, return " + ret);
-            return ret;
-        }
-
-        int gid_length = serviceGid1.length();
-        // Check if gid1 match service GID1
-        if (!((gid1 != null) && (gid1.length() >= gid_length) &&
-                gid1.substring(0, gid_length).equalsIgnoreCase(serviceGid1))) {
-            if (DBG) Rlog.d(TAG, " gid1 " + gid1 + " serviceGid1 " + serviceGid1);
-            ret = false;
-        }
-        if (DBG) Rlog.d(TAG, "compareGid1 is " + (ret?"Same":"Different"));
-        return ret;
-    }
-}
diff --git a/src/java/com/android/internal/telephony/SmsPermissions.java b/src/java/com/android/internal/telephony/SmsPermissions.java
index 0d509b2..55de517 100644
--- a/src/java/com/android/internal/telephony/SmsPermissions.java
+++ b/src/java/com/android/internal/telephony/SmsPermissions.java
@@ -17,15 +17,15 @@
 package com.android.internal.telephony;
 
 import android.Manifest;
-import android.annotation.UnsupportedAppUsage;
 import android.app.AppOpsManager;
+import android.compat.annotation.UnsupportedAppUsage;
 import android.content.Context;
 import android.content.Intent;
-import android.content.pm.PackageManager;
 import android.os.Binder;
 import android.service.carrier.CarrierMessagingService;
-import android.telephony.Rlog;
-import android.util.Log;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.telephony.Rlog;
 
 /**
  * Permissions checks for SMS functionality
@@ -50,7 +50,6 @@
      * Check that the caller can send text messages.
      *
      * For persisted messages, the caller just needs the SEND_SMS permission. For unpersisted
-     * For persisted messages, the caller just needs the SEND_SMS permission. For unpersisted
      * messages, the caller must either be the IMS app or a carrier-privileged app, or they must
      * have both the MODIFY_PHONE_STATE and SEND_SMS permissions.
      *
@@ -59,7 +58,8 @@
      * @return true unless the caller has all necessary permissions but has a revoked AppOps bit.
      */
     public boolean checkCallingCanSendText(
-            boolean persistMessageForNonDefaultSmsApp, String callingPackage, String message) {
+            boolean persistMessageForNonDefaultSmsApp, String callingPackage,
+            String callingAttributionTag, String message) {
         // TODO(b/75978989): Should we allow IMS/carrier apps for persisted messages as well?
         if (!persistMessageForNonDefaultSmsApp) {
             try {
@@ -71,10 +71,9 @@
                         android.Manifest.permission.MODIFY_PHONE_STATE, message);
             }
         }
-        return checkCallingCanSendSms(callingPackage, message);
+        return checkCallingCanSendSms(callingPackage, callingAttributionTag, message);
     }
 
-
     /**
      * Enforces that the caller is one of the following apps:
      * <ul>
@@ -83,25 +82,15 @@
      * </ul>
      */
     public void enforceCallerIsImsAppOrCarrierApp(String message) {
-        int callingUid = Binder.getCallingUid();
-        String carrierImsPackage = CarrierSmsUtils.getCarrierImsPackageForIntent(mContext, mPhone,
-                new Intent(CarrierMessagingService.SERVICE_INTERFACE));
-        try {
-            if (carrierImsPackage != null
-                    && callingUid == mContext.getPackageManager().getPackageUid(
-                    carrierImsPackage, 0)) {
-                return;
-            }
-        } catch (PackageManager.NameNotFoundException e) {
-            if (Rlog.isLoggable("SMS", Log.DEBUG)) {
-                log("Cannot find configured carrier ims package");
-            }
+        String carrierImsPackage = CarrierSmsUtils.getCarrierImsPackageForIntent(mContext,
+                mPhone, new Intent(CarrierMessagingService.SERVICE_INTERFACE));
+        if (carrierImsPackage != null && packageNameMatchesCallingUid(carrierImsPackage)) {
+            return;
         }
-
-        TelephonyPermissions.enforceCallingOrSelfCarrierPrivilege(mPhone.getSubId(), message);
+        TelephonyPermissions.enforceCallingOrSelfCarrierPrivilege(
+                mContext, mPhone.getSubId(), message);
     }
 
-
     /**
      * Check that the caller has SEND_SMS permissions. Can only be called during an IPC.
      *
@@ -109,10 +98,11 @@
      *                           permission revoked at runtime.
      * @return whether the caller has the OP_SEND_SMS AppOps bit.
      */
-    public boolean checkCallingCanSendSms(String callingPackage, String message) {
+    public boolean checkCallingCanSendSms(String callingPackage, String callingAttributionTag,
+            String message) {
         mContext.enforceCallingPermission(Manifest.permission.SEND_SMS, message);
-        return mAppOps.noteOp(AppOpsManager.OP_SEND_SMS, Binder.getCallingUid(), callingPackage)
-                == AppOpsManager.MODE_ALLOWED;
+        return mAppOps.noteOp(AppOpsManager.OPSTR_SEND_SMS, Binder.getCallingUid(), callingPackage,
+                callingAttributionTag, null) == AppOpsManager.MODE_ALLOWED;
     }
 
     /**
@@ -122,14 +112,88 @@
      *                           permission revoked at runtime.
      * @return whether the caller has the OP_SEND_SMS AppOps bit.
      */
-    public boolean checkCallingOrSelfCanSendSms(String callingPackage, String message) {
+    public boolean checkCallingOrSelfCanSendSms(String callingPackage, String callingAttributionTag,
+            String message) {
         mContext.enforceCallingOrSelfPermission(Manifest.permission.SEND_SMS, message);
-        return mAppOps.noteOp(AppOpsManager.OP_SEND_SMS, Binder.getCallingUid(), callingPackage)
+        return mAppOps.noteOp(AppOpsManager.OPSTR_SEND_SMS, Binder.getCallingUid(), callingPackage,
+                callingAttributionTag, null)
                 == AppOpsManager.MODE_ALLOWED;
     }
 
+    /**
+     * Check that the caller (or self, if this is not an IPC) can get SMSC address from (U)SIM.
+     *
+     * The default SMS application can get SMSC address, otherwise the caller must have
+     * {@link android.Manifest.permission#READ_PRIVILEGED_PHONE_STATE} or carrier privileges.
+     *
+     * @return true if the caller is default SMS app or has the required permission and privileges.
+     *              Otherwise, false;
+     */
+    public boolean checkCallingOrSelfCanGetSmscAddress(String callingPackage, String message) {
+        // Allow it to the default SMS app always.
+        if (!isCallerDefaultSmsPackage(callingPackage)) {
+            TelephonyPermissions
+                        .enforeceCallingOrSelfReadPrivilegedPhoneStatePermissionOrCarrierPrivilege(
+                                mContext, mPhone.getSubId(), message);
+        }
+        return true;
+    }
+
+    /**
+     * Check that the caller (or self, if this is not an IPC) can set SMSC address on (U)SIM.
+     *
+     * The default SMS application can set SMSC address, otherwise the caller must have
+     * {@link android.Manifest.permission#MODIFY_PHONE_STATE} or carrier privileges.
+     *
+     * @return true if the caller is default SMS app or has the required permission and privileges.
+     *              Otherwise, false.
+     */
+    public boolean checkCallingOrSelfCanSetSmscAddress(String callingPackage, String message) {
+        // Allow it to the default SMS app always.
+        if (!isCallerDefaultSmsPackage(callingPackage)) {
+            // Allow it with MODIFY_PHONE_STATE or Carrier Privileges
+            TelephonyPermissions.enforceCallingOrSelfModifyPermissionOrCarrierPrivilege(
+                    mContext, mPhone.getSubId(), message);
+        }
+        return true;
+    }
+
+    /** Check if a package is default SMS app. */
+    @VisibleForTesting
+    public boolean isCallerDefaultSmsPackage(String packageName) {
+        if (packageNameMatchesCallingUid(packageName)) {
+            return SmsApplication.isDefaultSmsApplication(mContext, packageName);
+        }
+        return false;
+    }
+
+    /**
+     * Check if the passed in packageName belongs to the calling uid.
+     * @param packageName name of the package to check
+     * @return true if package belongs to calling uid, false otherwise
+     */
+    @VisibleForTesting
+    public boolean packageNameMatchesCallingUid(String packageName) {
+        try {
+            ((AppOpsManager) mContext.getSystemService(Context.APP_OPS_SERVICE))
+                    .checkPackage(Binder.getCallingUid(), packageName);
+            // If checkPackage doesn't throw an exception then we are the given package
+            return true;
+        } catch (SecurityException e) {
+            return false;
+        }
+    }
+
     @UnsupportedAppUsage
     protected void log(String msg) {
-        Log.d(LOG_TAG, "[IccSmsInterfaceManager] " + msg);
+        Rlog.d(LOG_TAG, msg);
+    }
+
+    protected void loge(String msg) {
+        Rlog.e(LOG_TAG, msg);
+    }
+
+    protected void loge(String msg, Throwable e) {
+        Rlog.e(LOG_TAG, msg, e);
     }
 }
diff --git a/src/java/com/android/internal/telephony/SmsResponse.java b/src/java/com/android/internal/telephony/SmsResponse.java
index 0c9d7ac..e2209be 100644
--- a/src/java/com/android/internal/telephony/SmsResponse.java
+++ b/src/java/com/android/internal/telephony/SmsResponse.java
@@ -16,7 +16,7 @@
 
 package com.android.internal.telephony;
 
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
 
 /**
  * Object returned by the RIL upon successful completion of sendSMS.
@@ -24,6 +24,8 @@
  *
  */
 public class SmsResponse {
+    public static final int NO_ERROR_CODE = -1;
+
     /** Message reference of the just-sent SMS. */
     @UnsupportedAppUsage
     int mMessageRef;
diff --git a/src/java/com/android/internal/telephony/SmsStorageMonitor.java b/src/java/com/android/internal/telephony/SmsStorageMonitor.java
index 1dcbdd6..9eb43da 100755
--- a/src/java/com/android/internal/telephony/SmsStorageMonitor.java
+++ b/src/java/com/android/internal/telephony/SmsStorageMonitor.java
@@ -16,7 +16,7 @@
 
 package com.android.internal.telephony;
 
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
@@ -26,9 +26,10 @@
 import android.os.Message;
 import android.os.PowerManager;
 import android.provider.Telephony.Sms.Intents;
-import android.telephony.Rlog;
 import android.telephony.SubscriptionManager;
 
+import com.android.telephony.Rlog;
+
 /**
  * Monitors the device and ICC storage, and sends the appropriate events.
  *
diff --git a/src/java/com/android/internal/telephony/SmsUsageMonitor.java b/src/java/com/android/internal/telephony/SmsUsageMonitor.java
index f3a0550..8f583d9 100644
--- a/src/java/com/android/internal/telephony/SmsUsageMonitor.java
+++ b/src/java/com/android/internal/telephony/SmsUsageMonitor.java
@@ -16,27 +16,26 @@
 
 package com.android.internal.telephony;
 
-import android.annotation.UnsupportedAppUsage;
-import android.app.AppGlobals;
+import android.compat.annotation.UnsupportedAppUsage;
 import android.content.ContentResolver;
 import android.content.Context;
 import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager.NameNotFoundException;
 import android.content.res.XmlResourceParser;
 import android.database.ContentObserver;
 import android.os.Binder;
 import android.os.Handler;
 import android.os.Process;
-import android.os.RemoteException;
 import android.os.UserHandle;
 import android.provider.Settings;
 import android.telephony.PhoneNumberUtils;
-import android.telephony.Rlog;
 import android.telephony.SmsManager;
 import android.util.AtomicFile;
 import android.util.Xml;
 
+import com.android.internal.telephony.util.XmlUtils;
 import com.android.internal.util.FastXmlSerializer;
-import com.android.internal.util.XmlUtils;
+import com.android.telephony.Rlog;
 
 import org.xmlpull.v1.XmlPullParser;
 import org.xmlpull.v1.XmlPullParserException;
@@ -84,16 +83,20 @@
     }
 
     /** Premium SMS permission for a new package (ask user when first premium SMS sent). */
-    public static final int PREMIUM_SMS_PERMISSION_UNKNOWN = 0;
+    public static final int PREMIUM_SMS_PERMISSION_UNKNOWN =
+            SmsManager.PREMIUM_SMS_CONSENT_UNKNOWN;
 
     /** Default premium SMS permission (ask user for each premium SMS sent). */
-    public static final int PREMIUM_SMS_PERMISSION_ASK_USER = 1;
+    public static final int PREMIUM_SMS_PERMISSION_ASK_USER =
+            SmsManager.PREMIUM_SMS_CONSENT_ASK_USER;
 
     /** Premium SMS permission when the owner has denied the app from sending premium SMS. */
-    public static final int PREMIUM_SMS_PERMISSION_NEVER_ALLOW = 2;
+    public static final int PREMIUM_SMS_PERMISSION_NEVER_ALLOW =
+            SmsManager.PREMIUM_SMS_CONSENT_NEVER_ALLOW;
 
     /** Premium SMS permission when the owner has allowed the app to send premium SMS. */
-    public static final int PREMIUM_SMS_PERMISSION_ALWAYS_ALLOW = 3;
+    public static final int PREMIUM_SMS_PERMISSION_ALWAYS_ALLOW =
+            SmsManager.PREMIUM_SMS_CONSENT_ALWAYS_ALLOW;
 
     private final int mCheckPeriod;
     private final int mMaxAllowed;
@@ -523,7 +526,7 @@
 
     /**
      * Returns the premium SMS permission for the specified package. If the package has never
-     * been seen before, the default {@link #PREMIUM_SMS_PERMISSION_ASK_USER}
+     * been seen before, the default {@link #PREMIUM_SMS_PERMISSION_UNKNOWN}
      * will be returned.
      * @param packageName the name of the package to query permission
      * @return one of {@link #PREMIUM_SMS_PERMISSION_UNKNOWN},
@@ -571,21 +574,22 @@
         }).start();
     }
 
-    private static void checkCallerIsSystemOrPhoneOrSameApp(String pkg) {
+    private void checkCallerIsSystemOrPhoneOrSameApp(String pkg) {
         int uid = Binder.getCallingUid();
         int appId = UserHandle.getAppId(uid);
         if (appId == Process.SYSTEM_UID || appId == Process.PHONE_UID || uid == 0) {
             return;
         }
         try {
-            ApplicationInfo ai = AppGlobals.getPackageManager().getApplicationInfo(
-                    pkg, 0, UserHandle.getCallingUserId());
-            if (!UserHandle.isSameApp(ai.uid, uid)) {
+            ApplicationInfo ai = mContext.getPackageManager().getApplicationInfoAsUser(
+                    pkg, 0, UserHandle.getUserHandleForUid(uid));
+
+          if (UserHandle.getAppId(ai.uid) != UserHandle.getAppId(uid)) {
                 throw new SecurityException("Calling uid " + uid + " gave package"
                         + pkg + " which is owned by uid " + ai.uid);
             }
-        } catch (RemoteException re) {
-            throw new SecurityException("Unknown package " + pkg + "\n" + re);
+        } catch (NameNotFoundException ex) {
+            throw new SecurityException("Unknown package " + pkg + "\n" + ex);
         }
     }
 
diff --git a/src/java/com/android/internal/telephony/SubscriptionController.java b/src/java/com/android/internal/telephony/SubscriptionController.java
index d71f958..557e525 100644
--- a/src/java/com/android/internal/telephony/SubscriptionController.java
+++ b/src/java/com/android/internal/telephony/SubscriptionController.java
@@ -17,14 +17,16 @@
 package com.android.internal.telephony;
 
 import static android.content.pm.PackageManager.PERMISSION_GRANTED;
+import static android.telephony.TelephonyManager.MULTISIM_ALLOWED;
+import static android.telephony.TelephonyManager.SET_OPPORTUNISTIC_SUB_REMOTE_SERVICE_EXCEPTION;
 import static android.telephony.UiccSlotInfo.CARD_STATE_INFO_PRESENT;
 
 import android.Manifest;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
-import android.annotation.UnsupportedAppUsage;
 import android.app.AppOpsManager;
 import android.app.PendingIntent;
+import android.compat.annotation.UnsupportedAppUsage;
 import android.content.ContentResolver;
 import android.content.ContentValues;
 import android.content.Context;
@@ -34,20 +36,24 @@
 import android.graphics.BitmapFactory;
 import android.net.Uri;
 import android.os.Binder;
+import android.os.Handler;
 import android.os.ParcelUuid;
+import android.os.RegistrantList;
 import android.os.RemoteException;
-import android.os.ServiceManager;
+import android.os.TelephonyServiceManager.ServiceRegisterer;
 import android.os.UserHandle;
 import android.provider.Settings;
 import android.telecom.PhoneAccountHandle;
 import android.telecom.TelecomManager;
+import android.telephony.AnomalyReporter;
 import android.telephony.CarrierConfigManager;
 import android.telephony.RadioAccessFamily;
-import android.telephony.Rlog;
 import android.telephony.SubscriptionInfo;
 import android.telephony.SubscriptionManager;
 import android.telephony.SubscriptionManager.SimDisplayNameSource;
+import android.telephony.TelephonyFrameworkInitializer;
 import android.telephony.TelephonyManager;
+import android.telephony.TelephonyRegistryManager;
 import android.telephony.UiccAccessRule;
 import android.telephony.UiccSlotInfo;
 import android.telephony.euicc.EuiccManager;
@@ -62,7 +68,10 @@
 import com.android.internal.telephony.uicc.IccUtils;
 import com.android.internal.telephony.uicc.UiccCard;
 import com.android.internal.telephony.uicc.UiccController;
-import com.android.internal.util.ArrayUtils;
+import com.android.internal.telephony.uicc.UiccSlot;
+import com.android.internal.telephony.util.ArrayUtils;
+import com.android.internal.telephony.util.TelephonyUtils;
+import com.android.telephony.Rlog;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
@@ -82,8 +91,7 @@
 import java.util.stream.Collectors;
 
 /**
- * SubscriptionController to provide an inter-process communication to
- * access Sms in Icc.
+ * Implementation of the ISub interface.
  *
  * Any setters which take subId, slotIndex or phoneId as a parameter will throw an exception if the
  * parameter equals the corresponding INVALID_XXX_ID or DEFAULT_XXX_ID.
@@ -107,6 +115,9 @@
     private static final ParcelUuid INVALID_GROUP_UUID =
             ParcelUuid.fromString(CarrierConfigManager.REMOVE_GROUP_UUID_STRING);
     private final LocalLog mLocalLog = new LocalLog(200);
+    private static final int SUB_ID_FOUND = 1;
+    private static final int NO_ENTRY_FOR_SLOT_INDEX = -1;
+    private static final int SUB_ID_NOT_IN_SLOT = -2;
 
     // Lock that both mCacheActiveSubInfoList and mCacheOpportunisticSubInfoList use.
     private Object mSubInfoListLock = new Object();
@@ -133,8 +144,7 @@
     protected final Object mLock = new Object();
 
     /** The singleton instance. */
-    private static SubscriptionController sInstance = null;
-    protected static Phone[] sPhones;
+    protected static SubscriptionController sInstance = null;
     @UnsupportedAppUsage
     protected Context mContext;
     protected TelephonyManager mTelephonyManager;
@@ -142,15 +152,134 @@
 
     private AppOpsManager mAppOps;
 
+    // Allows test mocks to avoid SELinux failures on invalidate calls.
+    private static boolean sCachingEnabled = true;
+
     // Each slot can have multiple subs.
-    private static Map<Integer, ArrayList<Integer>> sSlotIndexToSubIds = new ConcurrentHashMap<>();
-    private static int mDefaultFallbackSubId = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
+    private static class WatchedSlotIndexToSubIds {
+        private Map<Integer, ArrayList<Integer>> mSlotIndexToSubIds = new ConcurrentHashMap<>();
+
+        WatchedSlotIndexToSubIds() {
+        }
+
+        public void clear() {
+            mSlotIndexToSubIds.clear();
+            invalidateDefaultSubIdCaches();
+            invalidateSlotIndexCaches();
+        }
+
+        public Set<Entry<Integer, ArrayList<Integer>>> entrySet() {
+            return mSlotIndexToSubIds.entrySet();
+        }
+
+        // Force all updates to data structure through wrapper.
+        public ArrayList<Integer> getCopy(int slotIndex) {
+            ArrayList<Integer> subIdList = mSlotIndexToSubIds.get(slotIndex);
+            if (subIdList == null) {
+                return null;
+            }
+
+            return new ArrayList<Integer>(subIdList);
+        }
+
+        public void put(int slotIndex, ArrayList<Integer> value) {
+            mSlotIndexToSubIds.put(slotIndex, value);
+            invalidateDefaultSubIdCaches();
+            invalidateSlotIndexCaches();
+        }
+
+        public void remove(int slotIndex) {
+            mSlotIndexToSubIds.remove(slotIndex);
+            invalidateDefaultSubIdCaches();
+            invalidateSlotIndexCaches();
+        }
+
+        public int size() {
+            return mSlotIndexToSubIds.size();
+        }
+
+        @VisibleForTesting
+        public Map<Integer, ArrayList<Integer>> getMap() {
+            return mSlotIndexToSubIds;
+        }
+
+        public int removeFromSubIdList(int slotIndex, int subId) {
+            ArrayList<Integer> subIdList = mSlotIndexToSubIds.get(slotIndex);
+            if (subIdList == null) {
+                return NO_ENTRY_FOR_SLOT_INDEX;
+            } else {
+                if (subIdList.contains(subId)) {
+                    subIdList.remove(new Integer(subId));
+                    if (subIdList.isEmpty()) {
+                        mSlotIndexToSubIds.remove(slotIndex);
+                    }
+                    invalidateDefaultSubIdCaches();
+                    invalidateSlotIndexCaches();
+                    return SUB_ID_FOUND;
+                } else {
+                    return SUB_ID_NOT_IN_SLOT;
+                }
+            }
+        }
+
+        public void addToSubIdList(int slotIndex, Integer value) {
+            ArrayList<Integer> subIdList = mSlotIndexToSubIds.get(slotIndex);
+            if (subIdList == null) {
+                subIdList = new ArrayList<Integer>();
+                subIdList.add(value);
+                mSlotIndexToSubIds.put(slotIndex, subIdList);
+            } else {
+                subIdList.add(value);
+            }
+            invalidateDefaultSubIdCaches();
+            invalidateSlotIndexCaches();
+        }
+
+        public void clearSubIdList(int slotIndex) {
+            ArrayList<Integer> subIdList = mSlotIndexToSubIds.get(slotIndex);
+            if (subIdList != null) {
+                subIdList.clear();
+                invalidateDefaultSubIdCaches();
+                invalidateSlotIndexCaches();
+            }
+        }
+    }
+
+    public static class WatchedInt {
+        private int mValue;
+
+        public WatchedInt(int initialValue) {
+            mValue = initialValue;
+        }
+
+        public int get() {
+            return mValue;
+        }
+
+        public void set(int newValue) {
+            mValue = newValue;
+        }
+    }
+
+    private static WatchedSlotIndexToSubIds sSlotIndexToSubIds = new WatchedSlotIndexToSubIds();
+
+    protected static WatchedInt sDefaultFallbackSubId =
+            new WatchedInt(SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
+        @Override
+        public void set(int newValue) {
+            super.set(newValue);
+            invalidateDefaultSubIdCaches();
+            invalidateSlotIndexCaches();
+        }
+    };
+
     @UnsupportedAppUsage
     private static int mDefaultPhoneId = SubscriptionManager.DEFAULT_PHONE_INDEX;
 
     @UnsupportedAppUsage
     private int[] colorArr;
     private long mLastISubServiceRegTime;
+    private RegistrantList mUiccAppsEnableChangeRegList = new RegistrantList();
 
     // The properties that should be shared and synced across grouped subscriptions.
     private static final Set<String> GROUP_SHARING_PROPERTIES = new HashSet<>(Arrays.asList(
@@ -162,20 +291,11 @@
             SubscriptionManager.WFC_IMS_ROAMING_ENABLED,
             SubscriptionManager.DATA_ROAMING,
             SubscriptionManager.DISPLAY_NAME,
-            SubscriptionManager.DATA_ENABLED_OVERRIDE_RULES));
+            SubscriptionManager.DATA_ENABLED_OVERRIDE_RULES,
+            SubscriptionManager.UICC_APPLICATIONS_ENABLED,
+            SubscriptionManager.IMS_RCS_UCE_ENABLED));
 
-    public static SubscriptionController init(Phone phone) {
-        synchronized (SubscriptionController.class) {
-            if (sInstance == null) {
-                sInstance = new SubscriptionController(phone);
-            } else {
-                Log.wtf(LOG_TAG, "init() called multiple times!  sInstance = " + sInstance);
-            }
-            return sInstance;
-        }
-    }
-
-    public static SubscriptionController init(Context c, CommandsInterface[] ci) {
+    public static SubscriptionController init(Context c) {
         synchronized (SubscriptionController.class) {
             if (sInstance == null) {
                 sInstance = new SubscriptionController(c);
@@ -188,8 +308,7 @@
 
     @UnsupportedAppUsage
     public static SubscriptionController getInstance() {
-        if (sInstance == null)
-        {
+        if (sInstance == null) {
            Log.wtf(LOG_TAG, "getInstance null");
         }
 
@@ -197,11 +316,11 @@
     }
 
     protected SubscriptionController(Context c) {
-        init(c);
+        internalInit(c);
         migrateImsSettings();
     }
 
-    protected void init(Context c) {
+    protected void internalInit(Context c) {
         mContext = c;
         mTelephonyManager = TelephonyManager.from(mContext);
 
@@ -214,14 +333,27 @@
 
         mAppOps = (AppOpsManager)mContext.getSystemService(Context.APP_OPS_SERVICE);
 
-        if(ServiceManager.getService("isub") == null) {
-            ServiceManager.addService("isub", this);
+        ServiceRegisterer subscriptionServiceRegisterer = TelephonyFrameworkInitializer
+                .getTelephonyServiceManager()
+                .getSubscriptionServiceRegisterer();
+        if (subscriptionServiceRegisterer.get() == null) {
+            subscriptionServiceRegisterer.register(this);
             mLastISubServiceRegTime = System.currentTimeMillis();
         }
 
         // clear SLOT_INDEX for all subs
         clearSlotIndexForSubInfoRecords();
 
+        // Cache Setting values
+        cacheSettingValues();
+
+        // Initial invalidate activates caching.
+        invalidateDefaultSubIdCaches();
+        invalidateDefaultDataSubIdCaches();
+        invalidateDefaultSmsSubIdCaches();
+        invalidateActiveDataSubIdCaches();
+        invalidateSlotIndexCaches();
+
         if (DBG) logdl("[SubscriptionController] init by Context");
     }
 
@@ -257,24 +389,28 @@
         mContext.getContentResolver().update(SubscriptionManager.CONTENT_URI, value, null, null);
     }
 
-    private SubscriptionController(Phone phone) {
-        mContext = phone.getContext();
-        mAppOps = mContext.getSystemService(AppOpsManager.class);
+    /**
+     * Cache the Settings values by reading these values from Setting from disk to prevent disk I/O
+     * access during the API calling. This is based on an assumption that the Settings system will
+     * itself cache this value after the first read and thus only the first read after boot will
+     * access the disk.
+     */
+    private void cacheSettingValues() {
+        Settings.Global.getInt(mContext.getContentResolver(),
+                Settings.Global.MULTI_SIM_SMS_SUBSCRIPTION,
+                        SubscriptionManager.INVALID_SUBSCRIPTION_ID);
 
-        if(ServiceManager.getService("isub") == null) {
-                ServiceManager.addService("isub", this);
-        }
+        Settings.Global.getInt(mContext.getContentResolver(),
+                Settings.Global.MULTI_SIM_VOICE_CALL_SUBSCRIPTION,
+                        SubscriptionManager.INVALID_SUBSCRIPTION_ID);
 
-        migrateImsSettings();
-
-        // clear SLOT_INDEX for all subs
-        clearSlotIndexForSubInfoRecords();
-
-        if (DBG) logdl("[SubscriptionController] init by Phone");
+        Settings.Global.getInt(mContext.getContentResolver(),
+                Settings.Global.MULTI_SIM_DATA_CALL_SUBSCRIPTION,
+                        SubscriptionManager.INVALID_SUBSCRIPTION_ID);
     }
 
     @UnsupportedAppUsage
-    private void enforceModifyPhoneState(String message) {
+    protected void enforceModifyPhoneState(String message) {
         mContext.enforceCallingOrSelfPermission(
                 android.Manifest.permission.MODIFY_PHONE_STATE, message);
     }
@@ -290,10 +426,10 @@
      * SecurityException.
      */
     private boolean hasSubscriberIdentifierAccess(int subId, String callingPackage,
-            String message) {
+            String callingFeatureId, String message) {
         try {
             return TelephonyPermissions.checkCallingOrSelfReadSubscriberIdentifiers(mContext, subId,
-                    callingPackage, message);
+                    callingPackage, callingFeatureId, message);
         } catch (SecurityException e) {
             // A SecurityException indicates that the calling package is targeting at least the
             // minimum level that enforces identifier access restrictions and the new access
@@ -303,6 +439,20 @@
     }
 
     /**
+     * Returns whether the {@code callingPackage} has access to the phone number on the specified
+     * {@code subId} using the provided {@code message} in any resulting SecurityException.
+     */
+    private boolean hasPhoneNumberAccess(int subId, String callingPackage, String callingFeatureId,
+            String message) {
+        try {
+            return TelephonyPermissions.checkCallingOrSelfReadPhoneNumber(mContext, subId,
+                    callingPackage, callingFeatureId, message);
+        } catch (SecurityException e) {
+            return false;
+        }
+    }
+
+    /**
      * Broadcast when SubscriptionInfo has changed
      * FIXME: Hopefully removed if the API council accepts SubscriptionInfoListener
      */
@@ -318,14 +468,11 @@
      */
     @UnsupportedAppUsage
     public void notifySubscriptionInfoChanged() {
-        ITelephonyRegistry tr = ITelephonyRegistry.Stub.asInterface(ServiceManager.getService(
-                "telephony.registry"));
-        try {
-            if (DBG) logd("notifySubscriptionInfoChanged:");
-            tr.notifySubscriptionInfoChanged();
-        } catch (RemoteException ex) {
-            // Should never happen because its always available.
-        }
+        TelephonyRegistryManager trm =
+                (TelephonyRegistryManager)
+                        mContext.getSystemService(Context.TELEPHONY_REGISTRY_SERVICE);
+        if (DBG) logd("notifySubscriptionInfoChanged:");
+        trm.notifySubscriptionInfoChanged();
 
         // FIXME: Remove if listener technique accepted.
         broadcastSimInfoContentChanged();
@@ -341,6 +488,9 @@
             notifyOpportunisticSubscriptionInfoChanged();
         }
         metrics.updateActiveSubscriptionInfoList(subInfos);
+        for (Phone phone : PhoneFactory.getPhones()) {
+            phone.getVoiceCallSessionStats().onActiveSubscriptionInfoChanged(subInfos);
+        }
     }
 
     /**
@@ -363,7 +513,7 @@
         int nameSource = cursor.getInt(cursor.getColumnIndexOrThrow(
                 SubscriptionManager.NAME_SOURCE));
         int iconTint = cursor.getInt(cursor.getColumnIndexOrThrow(
-                SubscriptionManager.COLOR));
+                SubscriptionManager.HUE));
         String number = cursor.getString(cursor.getColumnIndexOrThrow(
                 SubscriptionManager.NUMBER));
         int dataRoaming = cursor.getInt(cursor.getColumnIndexOrThrow(
@@ -412,6 +562,8 @@
                 SubscriptionManager.SUBSCRIPTION_TYPE));
         String groupOwner = getOptionalStringFromCursor(cursor, SubscriptionManager.GROUP_OWNER,
                 /*defaultVal*/ null);
+        boolean areUiccApplicationsEnabled = cursor.getInt(cursor.getColumnIndexOrThrow(
+                SubscriptionManager.UICC_APPLICATIONS_ENABLED)) == 1;
 
         if (VDBG) {
             String iccIdToPrint = SubscriptionInfo.givePrintableIccid(iccId);
@@ -426,7 +578,8 @@
                     + " cardId:" + cardIdToPrint + " publicCardId:" + publicCardId
                     + " isOpportunistic:" + isOpportunistic + " groupUUID:" + groupUUID
                     + " profileClass:" + profileClass + " subscriptionType: " + subType
-                    + " carrierConfigAccessRules:" + carrierConfigAccessRules);
+                    + " carrierConfigAccessRules:" + carrierConfigAccessRules
+                    + " areUiccApplicationsEnabled: " + areUiccApplicationsEnabled);
         }
 
         // If line1number has been set to a different number, use it instead.
@@ -438,7 +591,7 @@
                 carrierName, nameSource, iconTint, number, dataRoaming, iconBitmap, mcc, mnc,
                 countryIso, isEmbedded, accessRules, cardId, publicCardId, isOpportunistic,
                 groupUUID, false /* isGroupDisabled */, carrierId, profileClass, subType,
-                groupOwner, carrierConfigAccessRules);
+                groupOwner, carrierConfigAccessRules, areUiccApplicationsEnabled);
         info.setAssociatedPlmns(ehplmns, hplmns);
         return info;
     }
@@ -450,6 +603,18 @@
     }
 
     /**
+     * Get a subscription that matches IccId.
+     * @return null if there isn't a match, or subscription info if there is one.
+     */
+    public SubscriptionInfo getSubInfoForIccId(String iccId) {
+        List<SubscriptionInfo> info = getSubInfo(
+                SubscriptionManager.ICC_ID + "=\'" + iccId + "\'", null);
+        if (info == null || info.size() == 0) return null;
+        // Should be at most one subscription with the iccid.
+        return info.get(0);
+    }
+
+    /**
      * Query SubInfoRecord(s) from subinfo database
      * @param selection A filter declaring which rows to return
      * @param queryKey query key content
@@ -469,10 +634,8 @@
             if (cursor != null) {
                 while (cursor.moveToNext()) {
                     SubscriptionInfo subInfo = getSubInfoRecord(cursor);
-                    if (subInfo != null)
-                    {
-                        if (subList == null)
-                        {
+                    if (subInfo != null) {
+                        if (subList == null) {
                             subList = new ArrayList<SubscriptionInfo>();
                         }
                         subList.add(subInfo);
@@ -493,10 +656,12 @@
     /**
      * Find unused color to be set for new SubInfoRecord
      * @param callingPackage The package making the IPC.
+     * @param callingFeatureId The feature in the package
      * @return RGB integer value of color
      */
-    private int getUnusedColor(String callingPackage) {
-        List<SubscriptionInfo> availableSubInfos = getActiveSubscriptionInfoList(callingPackage);
+    private int getUnusedColor(String callingPackage, String callingFeatureId) {
+        List<SubscriptionInfo> availableSubInfos = getActiveSubscriptionInfoList(callingPackage,
+                callingFeatureId);
         colorArr = mContext.getResources().getIntArray(com.android.internal.R.array.sim_colors);
         int colorIdx = 0;
 
@@ -517,17 +682,24 @@
         return colorArr[colorIdx];
     }
 
+    @Deprecated
+    @UnsupportedAppUsage
+    public SubscriptionInfo getActiveSubscriptionInfo(int subId, String callingPackage) {
+        return getActiveSubscriptionInfo(subId, callingPackage, null);
+    }
+
     /**
      * Get the active SubscriptionInfo with the subId key
      * @param subId The unique SubscriptionInfo key in database
      * @param callingPackage The package making the IPC.
+     * @param callingFeatureId The feature in the package
      * @return SubscriptionInfo, maybe null if its not active
      */
-    @UnsupportedAppUsage
     @Override
-    public SubscriptionInfo getActiveSubscriptionInfo(int subId, String callingPackage) {
-        if (!TelephonyPermissions.checkCallingOrSelfReadPhoneState(
-                mContext, subId, callingPackage, "getActiveSubscriptionInfo")) {
+    public SubscriptionInfo getActiveSubscriptionInfo(int subId, String callingPackage,
+            String callingFeatureId) {
+        if (!TelephonyPermissions.checkCallingOrSelfReadPhoneState(mContext, subId, callingPackage,
+                callingFeatureId, "getActiveSubscriptionInfo")) {
             return null;
         }
 
@@ -535,7 +707,8 @@
         final long identity = Binder.clearCallingIdentity();
         List<SubscriptionInfo> subList;
         try {
-            subList = getActiveSubscriptionInfoList(mContext.getOpPackageName());
+            subList = getActiveSubscriptionInfoList(
+                    mContext.getOpPackageName(), mContext.getAttributionTag());
         } finally {
             Binder.restoreCallingIdentity(identity);
         }
@@ -545,7 +718,7 @@
                     if (VDBG) {
                         logd("[getActiveSubscriptionInfo]+ subId=" + subId + " subInfo=" + si);
                     }
-                    return conditionallyRemoveIdentifiers(si, callingPackage,
+                    return conditionallyRemoveIdentifiers(si, callingPackage, callingFeatureId,
                             "getActiveSubscriptionInfo");
                 }
             }
@@ -566,6 +739,19 @@
      * @hide
      */
     public SubscriptionInfo getSubscriptionInfo(int subId) {
+        // check cache for active subscriptions first, before querying db
+        for (SubscriptionInfo subInfo : mCacheActiveSubInfoList) {
+            if (subInfo.getSubscriptionId() == subId) {
+                return subInfo;
+            }
+        }
+        // check cache for opportunistic subscriptions too, before querying db
+        for (SubscriptionInfo subInfo : mCacheOpportunisticSubInfoList) {
+            if (subInfo.getSubscriptionId() == subId) {
+                return subInfo;
+            }
+        }
+
         List<SubscriptionInfo> subInfoList = getSubInfo(
                 SubscriptionManager.UNIQUE_KEY_SUBSCRIPTION_ID + "=" + subId, null);
         if (subInfoList == null || subInfoList.isEmpty()) return null;
@@ -576,22 +762,14 @@
      * Get the active SubscriptionInfo associated with the iccId
      * @param iccId the IccId of SIM card
      * @param callingPackage The package making the IPC.
+     * @param callingFeatureId The feature in the package
      * @return SubscriptionInfo, maybe null if its not active
      */
     @Override
-    public SubscriptionInfo getActiveSubscriptionInfoForIccId(String iccId, String callingPackage) {
-        // Query the subscriptions unconditionally, and then check whether the caller has access to
-        // the given subscription.
-        final SubscriptionInfo si = getActiveSubscriptionInfoForIccIdInternal(iccId);
-
-        final int subId = si != null
-                ? si.getSubscriptionId() : SubscriptionManager.INVALID_SUBSCRIPTION_ID;
-        if (!TelephonyPermissions.checkCallingOrSelfReadPhoneState(
-                mContext, subId, callingPackage, "getActiveSubscriptionInfoForIccId")) {
-            return null;
-        }
-
-        return si;
+    public SubscriptionInfo getActiveSubscriptionInfoForIccId(String iccId, String callingPackage,
+            String callingFeatureId) {
+        enforceReadPrivilegedPhoneState("getActiveSubscriptionInfoForIccId");
+        return getActiveSubscriptionInfoForIccIdInternal(iccId);
     }
 
     /**
@@ -606,7 +784,7 @@
         final long identity = Binder.clearCallingIdentity();
         try {
             List<SubscriptionInfo> subList = getActiveSubscriptionInfoList(
-                    mContext.getOpPackageName());
+                    mContext.getOpPackageName(), mContext.getAttributionTag());
             if (subList != null) {
                 for (SubscriptionInfo si : subList) {
                     if (iccId.equals(si.getIccId())) {
@@ -632,11 +810,12 @@
      * This API does not return details on Remote-SIM subscriptions.
      * @param slotIndex the slot which the subscription is inserted
      * @param callingPackage The package making the IPC.
+     * @param callingFeatureId The feature in the package
      * @return SubscriptionInfo, null for Remote-SIMs or non-active slotIndex.
      */
     @Override
     public SubscriptionInfo getActiveSubscriptionInfoForSimSlotIndex(int slotIndex,
-            String callingPackage) {
+            String callingPackage, String callingFeatureId) {
         Phone phone = PhoneFactory.getPhone(slotIndex);
         if (phone == null) {
             if (DBG) {
@@ -645,7 +824,7 @@
             return null;
         }
         if (!TelephonyPermissions.checkCallingOrSelfReadPhoneState(
-                mContext, phone.getSubId(), callingPackage,
+                mContext, phone.getSubId(), callingPackage, callingFeatureId,
                 "getActiveSubscriptionInfoForSimSlotIndex")) {
             return null;
         }
@@ -654,7 +833,8 @@
         final long identity = Binder.clearCallingIdentity();
         List<SubscriptionInfo> subList;
         try {
-            subList = getActiveSubscriptionInfoList(mContext.getOpPackageName());
+            subList = getActiveSubscriptionInfoList(
+                    mContext.getOpPackageName(), mContext.getAttributionTag());
         } finally {
             Binder.restoreCallingIdentity(identity);
         }
@@ -665,7 +845,7 @@
                         logd("[getActiveSubscriptionInfoForSimSlotIndex]+ slotIndex="
                                 + slotIndex + " subId=" + si);
                     }
-                    return conditionallyRemoveIdentifiers(si, callingPackage,
+                    return conditionallyRemoveIdentifiers(si, callingPackage, callingFeatureId,
                             "getActiveSubscriptionInfoForSimSlotIndex");
                 }
             }
@@ -685,19 +865,21 @@
 
     /**
      * @param callingPackage The package making the IPC.
+     * @param callingFeatureId The feature in the package
      * @return List of all SubscriptionInfo records in database,
      * include those that were inserted before, maybe empty but not null.
      * @hide
      */
     @Override
-    public List<SubscriptionInfo> getAllSubInfoList(String callingPackage) {
+    public List<SubscriptionInfo> getAllSubInfoList(String callingPackage,
+            String callingFeatureId) {
         if (VDBG) logd("[getAllSubInfoList]+");
 
         // This API isn't public, so no need to provide a valid subscription ID - we're not worried
         // about carrier-privileged callers not having access.
         if (!TelephonyPermissions.checkCallingOrSelfReadPhoneState(
                 mContext, SubscriptionManager.INVALID_SUBSCRIPTION_ID, callingPackage,
-                "getAllSubInfoList")) {
+                callingFeatureId, "getAllSubInfoList")) {
             return null;
         }
 
@@ -717,16 +899,25 @@
         }
     }
 
+    @Deprecated
+    @UnsupportedAppUsage
+    public List<SubscriptionInfo> getActiveSubscriptionInfoList(String callingPackage) {
+        return getSubscriptionInfoListFromCacheHelper(callingPackage, null,
+                mCacheActiveSubInfoList);
+    }
+
     /**
      * Get the SubInfoRecord(s) of the currently active SIM(s) - which include both local
      * and remote SIMs.
      * @param callingPackage The package making the IPC.
+     * @param callingFeatureId The feature in the package
      * @return Array list of currently inserted SubInfoRecord(s)
      */
-    @UnsupportedAppUsage
     @Override
-    public List<SubscriptionInfo> getActiveSubscriptionInfoList(String callingPackage) {
-        return getSubscriptionInfoListFromCacheHelper(callingPackage, mCacheActiveSubInfoList);
+    public List<SubscriptionInfo> getActiveSubscriptionInfoList(String callingPackage,
+            String callingFeatureId) {
+        return getSubscriptionInfoListFromCacheHelper(callingPackage, callingFeatureId,
+                mCacheActiveSubInfoList);
     }
 
     /**
@@ -775,16 +966,23 @@
         }
     }
 
+    @Deprecated
+    @UnsupportedAppUsage
+    public int getActiveSubInfoCount(String callingPackage) {
+        return getActiveSubInfoCount(callingPackage, null);
+    }
+
     /**
      * Get the SUB count of active SUB(s)
      * @param callingPackage The package making the IPC.
+     * @param callingFeatureId The feature in the package.
      * @return active SIM count
      */
-    @UnsupportedAppUsage
     @Override
-    public int getActiveSubInfoCount(String callingPackage) {
+    public int getActiveSubInfoCount(String callingPackage, String callingFeatureId) {
         // Let getActiveSubscriptionInfoList perform permission checks / filtering.
-        List<SubscriptionInfo> records = getActiveSubscriptionInfoList(callingPackage);
+        List<SubscriptionInfo> records = getActiveSubscriptionInfoList(callingPackage,
+                callingFeatureId);
         if (records == null) {
             if (VDBG) logd("[getActiveSubInfoCount] records null");
             return 0;
@@ -796,17 +994,18 @@
     /**
      * Get the SUB count of all SUB(s) in SubscriptoinInfo database
      * @param callingPackage The package making the IPC.
+     * @param callingFeatureId The feature in the package
      * @return all SIM count in database, include what was inserted before
      */
     @Override
-    public int getAllSubInfoCount(String callingPackage) {
+    public int getAllSubInfoCount(String callingPackage, String callingFeatureId) {
         if (DBG) logd("[getAllSubInfoCount]+");
 
         // This API isn't public, so no need to provide a valid subscription ID - we're not worried
         // about carrier-privileged callers not having access.
         if (!TelephonyPermissions.checkCallingOrSelfReadPhoneState(
                 mContext, SubscriptionManager.INVALID_SUBSCRIPTION_ID, callingPackage,
-                "getAllSubInfoCount")) {
+                callingFeatureId, "getAllSubInfoCount")) {
             return 0;
         }
 
@@ -844,12 +1043,13 @@
     }
 
     @Override
-    public List<SubscriptionInfo> getAvailableSubscriptionInfoList(String callingPackage) {
+    public List<SubscriptionInfo> getAvailableSubscriptionInfoList(String callingPackage,
+            String callingFeatureId) {
         // This API isn't public, so no need to provide a valid subscription ID - we're not worried
         // about carrier-privileged callers not having access.
         if (!TelephonyPermissions.checkCallingOrSelfReadPhoneState(
                 mContext, SubscriptionManager.INVALID_SUBSCRIPTION_ID, callingPackage,
-                "getAvailableSubscriptionInfoList")) {
+                callingFeatureId, "getAvailableSubscriptionInfoList")) {
             throw new SecurityException("Need READ_PHONE_STATE to call "
                     + " getAvailableSubscriptionInfoList");
         }
@@ -867,6 +1067,17 @@
                 selection += " OR " + SubscriptionManager.IS_EMBEDDED + "=1";
             }
 
+            // Available eSIM profiles are reported by EuiccManager. However for physical SIMs if
+            // they are in inactive slot or programmatically disabled, they are still considered
+            // available. In this case we get their iccid from slot info and include their
+            // subscriptionInfos.
+            List<String> iccIds = getIccIdsOfInsertedPhysicalSims();
+
+            if (!iccIds.isEmpty()) {
+                selection += " OR ("  + getSelectionForIccIdList(iccIds.toArray(new String[0]))
+                        + ")";
+            }
+
             List<SubscriptionInfo> subList = getSubInfo(selection, null /* queryKey */);
 
             if (subList != null) {
@@ -883,6 +1094,23 @@
         }
     }
 
+    private List<String> getIccIdsOfInsertedPhysicalSims() {
+        List<String> ret = new ArrayList<>();
+        UiccSlot[] uiccSlots = UiccController.getInstance().getUiccSlots();
+        if (uiccSlots == null) return ret;
+
+        for (UiccSlot uiccSlot : uiccSlots) {
+            if (uiccSlot != null && uiccSlot.getCardState() != null
+                    && uiccSlot.getCardState().isCardPresent()
+                    && !uiccSlot.isEuicc()
+                    && !TextUtils.isEmpty(uiccSlot.getIccId())) {
+                ret.add(IccUtils.stripTrailingFs(uiccSlot.getIccId()));
+            }
+        }
+
+        return ret;
+    }
+
     @Override
     public List<SubscriptionInfo> getAccessibleSubscriptionInfoList(String callingPackage) {
         EuiccManager euiccManager = (EuiccManager) mContext.getSystemService(Context.EUICC_SERVICE);
@@ -1235,9 +1463,6 @@
                     if (DBG) logdl("[addSubInfoRecord] sim name = " + nameToSet);
                 }
 
-                // Once the records are loaded, notify DcTracker
-                sPhones[slotIndex].updateDataConnectionTracker();
-
                 if (DBG) logdl("[addSubInfoRecord]- info size=" + sSlotIndexToSubIds.size());
             }
 
@@ -1256,7 +1481,7 @@
         if (!isActiveSubscriptionId(getDefaultSubId())) {
             // current default is not valid anylonger. set a new default
             if (DBG) {
-                logdl("[updateDefaultSubIdsIfNeeded] set mDefaultFallbackSubId=" + newDefault);
+                logdl("[updateDefaultSubIdsIfNeeded] set sDefaultFallbackSubId=" + newDefault);
             }
             setDefaultFallbackSubId(newDefault, subscriptionType);
         }
@@ -1340,28 +1565,19 @@
                 return -1;
             }
             refreshCachedActiveSubscriptionInfoList();
-
-            // update sSlotIndexToSubIds struct
-            ArrayList<Integer> subIdsList = sSlotIndexToSubIds.get(slotIndex);
-            if (subIdsList == null) {
+            result = sSlotIndexToSubIds.removeFromSubIdList(slotIndex, subId);
+            if (result == NO_ENTRY_FOR_SLOT_INDEX) {
                 loge("sSlotIndexToSubIds has no entry for slotIndex = " + slotIndex);
-            } else {
-                if (subIdsList.contains(subId)) {
-                    subIdsList.remove(new Integer(subId));
-                    if (subIdsList.isEmpty()) {
-                        sSlotIndexToSubIds.remove(slotIndex);
-                    }
-                } else {
-                    loge("sSlotIndexToSubIds has no subid: " + subId
-                            + ", in index: " + slotIndex);
-                }
+            } else if (result == SUB_ID_NOT_IN_SLOT) {
+                loge("sSlotIndexToSubIds has no subid: " + subId + ", in index: " + slotIndex);
             }
+
             // Since a subscription is removed, if this one is set as default for any setting,
             // set some other subid as the default.
             int newDefault = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
             SubscriptionInfo info = null;
             final List<SubscriptionInfo> records = getActiveSubscriptionInfoList(
-                    mContext.getOpPackageName());
+                    mContext.getOpPackageName(), mContext.getAttributionTag());
             if (!records.isEmpty()) {
                 // yes, we have more subscriptions. pick the first one.
                 // FIXME do we need a policy to figure out which one is to be next default
@@ -1384,17 +1600,12 @@
         if (DBG) logdl("[clearSubInfoRecord]+ iccId:" + " slotIndex:" + slotIndex);
 
         // update simInfo db with invalid slot index
-        List<SubscriptionInfo> oldSubInfo = getSubInfoUsingSlotIndexPrivileged(slotIndex);
         ContentResolver resolver = mContext.getContentResolver();
         ContentValues value = new ContentValues(1);
-        value.put(SubscriptionManager.SIM_SLOT_INDEX,
-                SubscriptionManager.INVALID_SIM_SLOT_INDEX);
-        if (oldSubInfo != null) {
-            for (int i = 0; i < oldSubInfo.size(); i++) {
-                resolver.update(SubscriptionManager.getUriForSubscriptionId(
-                        oldSubInfo.get(i).getSubscriptionId()), value, null, null);
-            }
-        }
+        value.put(SubscriptionManager.SIM_SLOT_INDEX, SubscriptionManager.INVALID_SIM_SLOT_INDEX);
+        String where = "(" + SubscriptionManager.SIM_SLOT_INDEX + "=" + slotIndex + ")";
+        resolver.update(SubscriptionManager.CONTENT_URI, value, where, null);
+
         // Refresh the Cache of Active Subscription Info List
         refreshCachedActiveSubscriptionInfoList();
 
@@ -1420,16 +1631,17 @@
         ContentResolver resolver = mContext.getContentResolver();
         ContentValues value = new ContentValues();
         value.put(SubscriptionManager.ICC_ID, uniqueId);
-        int color = getUnusedColor(mContext.getOpPackageName());
+        int color = getUnusedColor(mContext.getOpPackageName(), mContext.getAttributionTag());
         // default SIM color differs between slots
-        value.put(SubscriptionManager.COLOR, color);
+        value.put(SubscriptionManager.HUE, color);
         value.put(SubscriptionManager.SIM_SLOT_INDEX, slotIndex);
         value.put(SubscriptionManager.CARRIER_NAME, "");
         value.put(SubscriptionManager.CARD_ID, uniqueId);
         value.put(SubscriptionManager.SUBSCRIPTION_TYPE, subscriptionType);
-        if (isSubscriptionForRemoteSim(subscriptionType)) {
+        if (!TextUtils.isEmpty(displayName)) {
             value.put(SubscriptionManager.DISPLAY_NAME, displayName);
-        } else {
+        }
+        if (!isSubscriptionForRemoteSim(subscriptionType)) {
             UiccCard card = mUiccController.getUiccCardForPhone(slotIndex);
             if (card != null) {
                 String cardId = card.getCardId();
@@ -1539,7 +1751,7 @@
         try {
             validateSubId(subId);
             ContentValues value = new ContentValues(1);
-            value.put(SubscriptionManager.COLOR, tint);
+            value.put(SubscriptionManager.HUE, tint);
             if (DBG) logd("[setIconTint]- tint:" + tint + " set");
 
             int result = mContext.getContentResolver().update(
@@ -1564,7 +1776,7 @@
      */
     public static int getNameSourcePriority(@SimDisplayNameSource int nameSource) {
         int index = Arrays.asList(
-                SubscriptionManager.NAME_SOURCE_DEFAULT_SOURCE,
+                SubscriptionManager.NAME_SOURCE_CARRIER_ID,
                 SubscriptionManager.NAME_SOURCE_SIM_PNN,
                 SubscriptionManager.NAME_SOURCE_SIM_SPN,
                 SubscriptionManager.NAME_SOURCE_CARRIER,
@@ -1610,14 +1822,22 @@
                 }
             }
             String nameToSet;
-            if (displayName == null) {
-                nameToSet = mContext.getString(SubscriptionManager.DEFAULT_NAME_RES);
+            if (TextUtils.isEmpty(displayName) || displayName.trim().length() == 0) {
+                nameToSet = mTelephonyManager.getSimOperatorName(subId);
+                if (TextUtils.isEmpty(nameToSet)) {
+                    if (nameSource == SubscriptionManager.NAME_SOURCE_USER_INPUT
+                            && SubscriptionManager.isValidSlotIndex(getSlotIndex(subId))) {
+                        nameToSet = "CARD " + (getSlotIndex(subId) + 1);
+                    } else {
+                        nameToSet = mContext.getString(SubscriptionManager.DEFAULT_NAME_RES);
+                    }
+                }
             } else {
                 nameToSet = displayName;
             }
             ContentValues value = new ContentValues(1);
             value.put(SubscriptionManager.DISPLAY_NAME, nameToSet);
-            if (nameSource >= SubscriptionManager.NAME_SOURCE_DEFAULT_SOURCE) {
+            if (nameSource >= SubscriptionManager.NAME_SOURCE_CARRIER_ID) {
                 if (DBG) logd("Set nameSource=" + nameSource);
                 value.put(SubscriptionManager.NAME_SOURCE, nameSource);
             }
@@ -1632,6 +1852,9 @@
                 EuiccManager euiccManager = ((EuiccManager)
                         mContext.getSystemService(Context.EUICC_SERVICE)).createForCardId(cardId);
                 euiccManager.updateSubscriptionNickname(subId, displayName,
+                        // This PendingIntent simply fulfills the requirement to pass in a callback;
+                        // we don't care about the result (hence 0 requestCode and no action
+                        // specified on the intent).
                         PendingIntent.getService(
                             mContext, 0 /* requestCode */, new Intent(), 0 /* flags */));
             }
@@ -1798,6 +2021,7 @@
             case SubscriptionManager.WFC_IMS_ROAMING_MODE:
             case SubscriptionManager.WFC_IMS_ROAMING_ENABLED:
             case SubscriptionManager.DATA_ROAMING:
+            case SubscriptionManager.IMS_RCS_UCE_ENABLED:
                 values.put(propKey, cursor.getInt(columnIndex));
                 break;
             case SubscriptionManager.DISPLAY_NAME:
@@ -1812,7 +2036,7 @@
     // TODO: replace all updates with this helper method.
     private int updateDatabase(ContentValues value, int subId, boolean updateEntireGroup) {
         List<SubscriptionInfo> infoList = getSubscriptionsInGroup(getGroupUuid(subId),
-                mContext.getOpPackageName());
+                mContext.getOpPackageName(), mContext.getAttributionTag());
         if (!updateEntireGroup || infoList == null || infoList.size() == 0) {
             // Only update specified subscriptions.
             return mContext.getContentResolver().update(
@@ -1918,6 +2142,59 @@
     }
 
     /**
+     * Set uicc applications being enabled or disabled.
+     * @param enabled whether uicc applications are enabled or disabled.
+     * @return the number of records updated
+     */
+    public int setUiccApplicationsEnabled(boolean enabled, int subId) {
+        if (DBG) logd("[setUiccApplicationsEnabled]+ enabled:" + enabled + " subId:" + subId);
+
+        enforceModifyPhoneState("setUiccApplicationsEnabled");
+
+        long identity = Binder.clearCallingIdentity();
+        try {
+            ContentValues value = new ContentValues(1);
+            value.put(SubscriptionManager.UICC_APPLICATIONS_ENABLED, enabled);
+
+            int result = mContext.getContentResolver().update(
+                    SubscriptionManager.getUriForSubscriptionId(subId), value, null, null);
+
+            // Refresh the Cache of Active Subscription Info List
+            refreshCachedActiveSubscriptionInfoList();
+
+            notifyUiccAppsEnableChanged();
+            notifySubscriptionInfoChanged();
+
+            return result;
+        } finally {
+            Binder.restoreCallingIdentity(identity);
+        }
+    }
+
+    /**
+     * Register to change of uicc applications enablement changes.
+     * @param notifyNow whether to notify target upon registration.
+     */
+    public void registerForUiccAppsEnabled(Handler handler, int what, Object object,
+            boolean notifyNow) {
+        mUiccAppsEnableChangeRegList.addUnique(handler, what, object);
+        if (notifyNow) {
+            handler.obtainMessage(what, object).sendToTarget();
+        }
+    }
+
+    /**
+     * Unregister to change of uicc applications enablement changes.
+     */
+    public void unregisterForUiccAppsEnabled(Handler handler) {
+        mUiccAppsEnableChangeRegList.remove(handler);
+    }
+
+    private void notifyUiccAppsEnableChanged() {
+        mUiccAppsEnableChangeRegList.notifyRegistrants();
+    }
+
+    /**
      * Get IMSI by subscription ID
      * For active subIds, this will always return the corresponding imsi
      * For inactive subIds, once they are activated once, even if they are deactivated at the time
@@ -2039,7 +2316,7 @@
         }
 
         // Convert ArrayList to array
-        ArrayList<Integer> subIds = sSlotIndexToSubIds.get(slotIndex);
+        ArrayList<Integer> subIds = sSlotIndexToSubIds.getCopy(slotIndex);
         if (subIds != null && subIds.size() > 0) {
             int[] subIdArr = new int[subIds.size()];
             for (int i = 0; i < subIds.size(); i++) {
@@ -2133,15 +2410,11 @@
     }
 
     @UnsupportedAppUsage
-    private void logdl(String msg) {
+    protected void logdl(String msg) {
         logd(msg);
         mLocalLog.log(msg);
     }
 
-    private static void slogd(String msg) {
-        Rlog.d(LOG_TAG, msg);
-    }
-
     @UnsupportedAppUsage
     private void logd(String msg) {
         Rlog.d(LOG_TAG, msg);
@@ -2161,8 +2434,7 @@
     @Override
     public int getDefaultSubId() {
         int subId;
-        boolean isVoiceCapable = mContext.getResources().getBoolean(
-                com.android.internal.R.bool.config_voice_capable);
+        boolean isVoiceCapable = mTelephonyManager.isVoiceCapable();
         if (isVoiceCapable) {
             subId = getDefaultVoiceSubId();
             if (VDBG) logdl("[getDefaultSubId] isVoiceCapable subId=" + subId);
@@ -2171,7 +2443,7 @@
             if (VDBG) logdl("[getDefaultSubId] NOT VoiceCapable subId=" + subId);
         }
         if (!isActiveSubId(subId)) {
-            subId = mDefaultFallbackSubId;
+            subId = sDefaultFallbackSubId.get();
             if (VDBG) logdl("[getDefaultSubId] NOT active use fall back subId=" + subId);
         }
         if (VDBG) logv("[getDefaultSubId]- value = " + subId);
@@ -2187,8 +2459,7 @@
             throw new RuntimeException("setDefaultSmsSubId called with DEFAULT_SUB_ID");
         }
         if (DBG) logdl("[setDefaultSmsSubId] subId=" + subId);
-        Settings.Global.putInt(mContext.getContentResolver(),
-                Settings.Global.MULTI_SIM_SMS_SUBSCRIPTION, subId);
+        setGlobalSetting(Settings.Global.MULTI_SIM_SMS_SUBSCRIPTION, subId);
         broadcastDefaultSmsSubIdChanged(subId);
     }
 
@@ -2196,10 +2467,8 @@
         // Broadcast an Intent for default sms sub change
         if (DBG) logdl("[broadcastDefaultSmsSubIdChanged] subId=" + subId);
         Intent intent = new Intent(SubscriptionManager.ACTION_DEFAULT_SMS_SUBSCRIPTION_CHANGED);
-        intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING
-                | Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
-        intent.putExtra(PhoneConstants.SUBSCRIPTION_KEY, subId);
-        intent.putExtra(SubscriptionManager.EXTRA_SUBSCRIPTION_INDEX, subId);
+        intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING);
+        SubscriptionManager.putSubscriptionIdExtra(intent, subId);
         mContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL);
     }
 
@@ -2225,8 +2494,7 @@
 
         int previousDefaultSub = getDefaultSubId();
 
-        Settings.Global.putInt(mContext.getContentResolver(),
-                Settings.Global.MULTI_SIM_VOICE_CALL_SUBSCRIPTION, subId);
+        setGlobalSetting(Settings.Global.MULTI_SIM_VOICE_CALL_SUBSCRIPTION, subId);
         broadcastDefaultVoiceSubIdChanged(subId);
 
         PhoneAccountHandle newHandle =
@@ -2258,10 +2526,8 @@
         // Broadcast an Intent for default voice sub change
         if (DBG) logdl("[broadcastDefaultVoiceSubIdChanged] subId=" + subId);
         Intent intent = new Intent(TelephonyIntents.ACTION_DEFAULT_VOICE_SUBSCRIPTION_CHANGED);
-        intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING
-                | Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
-        intent.putExtra(PhoneConstants.SUBSCRIPTION_KEY, subId);
-        intent.putExtra(SubscriptionManager.EXTRA_SUBSCRIPTION_INDEX, subId);
+        intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING);
+        SubscriptionManager.putSubscriptionIdExtra(intent, subId);
         mContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL);
     }
 
@@ -2271,7 +2537,7 @@
         int subId = Settings.Global.getInt(mContext.getContentResolver(),
                 Settings.Global.MULTI_SIM_VOICE_CALL_SUBSCRIPTION,
                 SubscriptionManager.INVALID_SUBSCRIPTION_ID);
-        if (VDBG) slogd("[getDefaultVoiceSubId] subId=" + subId);
+        if (VDBG) logd("[getDefaultVoiceSubId] subId=" + subId);
         return subId;
     }
 
@@ -2281,7 +2547,7 @@
         int subId = Settings.Global.getInt(mContext.getContentResolver(),
                 Settings.Global.MULTI_SIM_DATA_CALL_SUBSCRIPTION,
                 SubscriptionManager.INVALID_SUBSCRIPTION_ID);
-        if (VDBG) logd("[getDefaultDataSubId] subId= " + subId);
+        if (VDBG) logd("[getDefaultDataSubId] subId=" + subId);
         return subId;
     }
 
@@ -2297,7 +2563,7 @@
             }
 
             ProxyController proxyController = ProxyController.getInstance();
-            int len = sPhones.length;
+            int len = TelephonyManager.from(mContext).getActiveModemCount();
             logdl("[setDefaultDataSubId] num phones=" + len + ", subId=" + subId);
 
             if (SubscriptionManager.isValidSubscriptionId(subId)) {
@@ -2305,7 +2571,7 @@
                 RadioAccessFamily[] rafs = new RadioAccessFamily[len];
                 boolean atLeastOneMatch = false;
                 for (int phoneId = 0; phoneId < len; phoneId++) {
-                    Phone phone = sPhones[phoneId];
+                    Phone phone = PhoneFactory.getPhone(phoneId);
                     int raf;
                     int id = phone.getSubId();
                     if (id == subId) {
@@ -2327,12 +2593,8 @@
                 }
             }
 
-            // FIXME is this still needed?
-            updateAllDataConnectionTrackers();
-
             int previousDefaultSub = getDefaultSubId();
-            Settings.Global.putInt(mContext.getContentResolver(),
-                    Settings.Global.MULTI_SIM_DATA_CALL_SUBSCRIPTION, subId);
+            setGlobalSetting(Settings.Global.MULTI_SIM_DATA_CALL_SUBSCRIPTION, subId);
             MultiSimSettingController.getInstance().notifyDefaultDataSubChanged();
             broadcastDefaultDataSubIdChanged(subId);
             if (previousDefaultSub != getDefaultSubId()) {
@@ -2344,25 +2606,12 @@
     }
 
     @UnsupportedAppUsage
-    private void updateAllDataConnectionTrackers() {
-        // Tell Phone Proxies to update data connection tracker
-        int len = sPhones.length;
-        if (DBG) logd("[updateAllDataConnectionTrackers] sPhones.length=" + len);
-        for (int phoneId = 0; phoneId < len; phoneId++) {
-            if (DBG) logd("[updateAllDataConnectionTrackers] phoneId=" + phoneId);
-            sPhones[phoneId].updateDataConnectionTracker();
-        }
-    }
-
-    @UnsupportedAppUsage
     private void broadcastDefaultDataSubIdChanged(int subId) {
         // Broadcast an Intent for default data sub change
         if (DBG) logdl("[broadcastDefaultDataSubIdChanged] subId=" + subId);
         Intent intent = new Intent(TelephonyIntents.ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED);
-        intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING
-                | Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
-        intent.putExtra(PhoneConstants.SUBSCRIPTION_KEY, subId);
-        intent.putExtra(SubscriptionManager.EXTRA_SUBSCRIPTION_INDEX, subId);
+        intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING);
+        SubscriptionManager.putSubscriptionIdExtra(intent, subId);
         mContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL);
     }
 
@@ -2371,7 +2620,7 @@
      * the first sub is set as default subscription
      */
     @UnsupportedAppUsage
-    private void setDefaultFallbackSubId(int subId, int subscriptionType) {
+    protected void setDefaultFallbackSubId(int subId, int subscriptionType) {
         if (subId == SubscriptionManager.DEFAULT_SUBSCRIPTION_ID) {
             throw new RuntimeException("setDefaultSubId called with DEFAULT_SUB_ID");
         }
@@ -2381,15 +2630,15 @@
         }
         int previousDefaultSub = getDefaultSubId();
         if (isSubscriptionForRemoteSim(subscriptionType)) {
-            mDefaultFallbackSubId = subId;
+            sDefaultFallbackSubId.set(subId);
             return;
         }
         if (SubscriptionManager.isValidSubscriptionId(subId)) {
             int phoneId = getPhoneId(subId);
             if (phoneId >= 0 && (phoneId < mTelephonyManager.getPhoneCount()
                     || mTelephonyManager.getSimCount() == 1)) {
-                if (DBG) logdl("[setDefaultFallbackSubId] set mDefaultFallbackSubId=" + subId);
-                mDefaultFallbackSubId = subId;
+                if (DBG) logdl("[setDefaultFallbackSubId] set sDefaultFallbackSubId=" + subId);
+                sDefaultFallbackSubId.set(subId);
                 // Update MCC MNC device configuration information
                 String defaultMccMnc = mTelephonyManager.getSimOperatorNumericForPhone(phoneId);
                 MccTable.updateMccMncConfiguration(mContext, defaultMccMnc);
@@ -2408,9 +2657,8 @@
     public void sendDefaultChangedBroadcast(int subId) {
         // Broadcast an Intent for default sub change
         int phoneId = SubscriptionManager.getPhoneId(subId);
-        Intent intent = new Intent(TelephonyIntents.ACTION_DEFAULT_SUBSCRIPTION_CHANGED);
-        intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING
-                | Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
+        Intent intent = new Intent(SubscriptionManager.ACTION_DEFAULT_SUBSCRIPTION_CHANGED);
+        intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING);
         SubscriptionManager.putPhoneIdAndSubIdExtra(intent, phoneId, subId);
         if (DBG) {
             logdl("[sendDefaultChangedBroadcast] broadcast default subId changed phoneId="
@@ -2423,7 +2671,8 @@
      * Whether a subscription is opportunistic or not.
      */
     public boolean isOpportunistic(int subId) {
-        SubscriptionInfo info = getActiveSubscriptionInfo(subId, mContext.getOpPackageName());
+        SubscriptionInfo info = getActiveSubscriptionInfo(subId, mContext.getOpPackageName(),
+                mContext.getAttributionTag());
         return (info != null) && info.isOpportunistic();
     }
 
@@ -2486,10 +2735,6 @@
         }
     }
 
-    public void updatePhonesAvailability(Phone[] phones) {
-        sPhones = phones;
-    }
-
     private synchronized ArrayList<Integer> getActiveSubIdArrayList() {
         // Clone the sub id list so it can't change out from under us while iterating
         List<Entry<Integer, ArrayList<Integer>>> simInfoList =
@@ -2546,9 +2791,9 @@
     }
 
     @Override
-    public boolean isActiveSubId(int subId, String callingPackage) {
+    public boolean isActiveSubId(int subId, String callingPackage, String callingFeatureId) {
         if (!TelephonyPermissions.checkCallingOrSelfReadPhoneState(mContext, subId, callingPackage,
-              "isActiveSubId")) {
+                callingFeatureId, "isActiveSubId")) {
             throw new SecurityException("Requires READ_PHONE_STATE permission.");
         }
         final long identity = Binder.clearCallingIdentity();
@@ -2661,10 +2906,14 @@
             case SubscriptionManager.WFC_IMS_MODE:
             case SubscriptionManager.WFC_IMS_ROAMING_MODE:
             case SubscriptionManager.WFC_IMS_ROAMING_ENABLED:
+            case SubscriptionManager.IMS_RCS_UCE_ENABLED:
                 value.put(propKey, Integer.parseInt(propValue));
                 break;
+            case SubscriptionManager.ALLOWED_NETWORK_TYPES:
+                value.put(propKey, Long.parseLong(propValue));
+                break;
             default:
-                if (DBG) slogd("Invalid column name");
+                if (DBG) logd("Invalid column name");
                 break;
         }
 
@@ -2679,9 +2928,10 @@
      * @return Value associated with subId and propKey column in database
      */
     @Override
-    public String getSubscriptionProperty(int subId, String propKey, String callingPackage) {
-        if (!TelephonyPermissions.checkCallingOrSelfReadPhoneState(
-                mContext, subId, callingPackage, "getSubscriptionProperty")) {
+    public String getSubscriptionProperty(int subId, String propKey, String callingPackage,
+            String callingFeatureId) {
+        if (!TelephonyPermissions.checkCallingOrSelfReadPhoneState(mContext, subId, callingPackage,
+                callingFeatureId, "getSubscriptionProperty")) {
             return null;
         }
 
@@ -2728,12 +2978,11 @@
                         case SubscriptionManager.WFC_IMS_MODE:
                         case SubscriptionManager.WFC_IMS_ROAMING_MODE:
                         case SubscriptionManager.WFC_IMS_ROAMING_ENABLED:
+                        case SubscriptionManager.IMS_RCS_UCE_ENABLED:
                         case SubscriptionManager.IS_OPPORTUNISTIC:
                         case SubscriptionManager.GROUP_UUID:
-                        case SubscriptionManager.WHITE_LISTED_APN_DATA:
-                            resultValue = cursor.getInt(0) + "";
-                            break;
                         case SubscriptionManager.DATA_ENABLED_OVERRIDE_RULES:
+                        case SubscriptionManager.ALLOWED_NETWORK_TYPES:
                             resultValue = cursor.getString(0);
                             break;
                         default:
@@ -2752,16 +3001,16 @@
         return resultValue;
     }
 
-    private static void printStackTrace(String msg) {
+    private void printStackTrace(String msg) {
         RuntimeException re = new RuntimeException();
-        slogd("StackTrace - " + msg);
+        logd("StackTrace - " + msg);
         StackTraceElement[] st = re.getStackTrace();
         boolean first = true;
         for (StackTraceElement ste : st) {
             if (first) {
                 first = false;
             } else {
-                slogd(ste.toString());
+                logd(ste.toString());
             }
         }
     }
@@ -2793,7 +3042,7 @@
             pw.println("++++++++++++++++++++++++++++++++");
 
             List<SubscriptionInfo> sirl = getActiveSubscriptionInfoList(
-                    mContext.getOpPackageName());
+                    mContext.getOpPackageName(), mContext.getAttributionTag());
             if (sirl != null) {
                 pw.println(" ActiveSubInfoList:");
                 for (SubscriptionInfo entry : sirl) {
@@ -2805,7 +3054,7 @@
             pw.flush();
             pw.println("++++++++++++++++++++++++++++++++");
 
-            sirl = getAllSubInfoList(mContext.getOpPackageName());
+            sirl = getAllSubInfoList(mContext.getOpPackageName(), mContext.getAttributionTag());
             if (sirl != null) {
                 pw.println(" AllSubInfoList:");
                 for (SubscriptionInfo entry : sirl) {
@@ -2941,8 +3190,23 @@
         final long token = Binder.clearCallingIdentity();
 
         try {
-            PhoneSwitcher.getInstance().trySetOpportunisticDataSubscription(
-                    subId, needValidation, callback);
+            PhoneSwitcher phoneSwitcher = PhoneSwitcher.getInstance();
+            if (phoneSwitcher == null) {
+                logd("Set preferred data sub: phoneSwitcher is null.");
+                AnomalyReporter.reportAnomaly(
+                        UUID.fromString("a3ab0b9d-f2aa-4baf-911d-7096c0d4645a"),
+                        "Set preferred data sub: phoneSwitcher is null.");
+                if (callback != null) {
+                    try {
+                        callback.onComplete(SET_OPPORTUNISTIC_SUB_REMOTE_SERVICE_EXCEPTION);
+                    } catch (RemoteException exception) {
+                        logd("RemoteException " + exception);
+                    }
+                }
+                return;
+            }
+
+            phoneSwitcher.trySetOpportunisticDataSubscription(subId, needValidation, callback);
         } finally {
             Binder.restoreCallingIdentity(token);
         }
@@ -2954,16 +3218,25 @@
         final long token = Binder.clearCallingIdentity();
 
         try {
-            return PhoneSwitcher.getInstance().getOpportunisticDataSubscriptionId();
+            PhoneSwitcher phoneSwitcher = PhoneSwitcher.getInstance();
+            if (phoneSwitcher == null) {
+                AnomalyReporter.reportAnomaly(
+                        UUID.fromString("a3ab0b9d-f2aa-4baf-911d-7096c0d4645a"),
+                        "Get preferred data sub: phoneSwitcher is null.");
+                return SubscriptionManager.DEFAULT_SUBSCRIPTION_ID;
+            }
+
+            return phoneSwitcher.getOpportunisticDataSubscriptionId();
         } finally {
             Binder.restoreCallingIdentity(token);
         }
     }
 
     @Override
-    public List<SubscriptionInfo> getOpportunisticSubscriptions(String callingPackage) {
+    public List<SubscriptionInfo> getOpportunisticSubscriptions(String callingPackage,
+            String callingFeatureId) {
         return getSubscriptionInfoListFromCacheHelper(
-                callingPackage, mCacheOpportunisticSubInfoList);
+                callingPackage, callingFeatureId, mCacheOpportunisticSubInfoList);
     }
 
     /**
@@ -3111,12 +3384,6 @@
             throw new IllegalArgumentException("Invalid groupUuid");
         }
 
-        // TODO: Revisit whether we need this restriction in R. There's no technical need for it,
-        // but we don't want to change the API behavior at this time.
-        if (getSubscriptionsInGroup(groupUuid, callingPackage).isEmpty()) {
-            throw new IllegalArgumentException("Cannot add subscriptions to a non-existent group!");
-        }
-
         // Makes sure calling package matches caller UID.
         mAppOps.checkPackage(Binder.getCallingUid(), callingPackage);
         // If it doesn't have modify phone state permission, or carrier privilege permission,
@@ -3281,7 +3548,7 @@
      * Helper function to create selection argument of a list of subId.
      * The result should be: "in (subId1, subId2, ...)".
      */
-    private String getSelectionForSubIdList(int[] subId) {
+    public static String getSelectionForSubIdList(int[] subId) {
         StringBuilder selection = new StringBuilder();
         selection.append(SubscriptionManager.UNIQUE_KEY_SUBSCRIPTION_ID);
         selection.append(" IN (");
@@ -3295,6 +3562,23 @@
     }
 
     /**
+     * Helper function to create selection argument of a list of subId.
+     * The result should be: "in (iccId1, iccId2, ...)".
+     */
+    private String getSelectionForIccIdList(String[] iccIds) {
+        StringBuilder selection = new StringBuilder();
+        selection.append(SubscriptionManager.ICC_ID);
+        selection.append(" IN (");
+        for (int i = 0; i < iccIds.length - 1; i++) {
+            selection.append("\"" + iccIds[i] + "\", ");
+        }
+        selection.append("\"" + iccIds[iccIds.length - 1] + "\"");
+        selection.append(")");
+
+        return selection.toString();
+    }
+
+    /**
      * Get subscriptionInfo list of subscriptions that are in the same group of given subId.
      * See {@link #createSubscriptionGroup(int[], String)} for more details.
      *
@@ -3312,12 +3596,13 @@
      */
     @Override
     public List<SubscriptionInfo> getSubscriptionsInGroup(ParcelUuid groupUuid,
-            String callingPackage) {
+            String callingPackage, String callingFeatureId) {
         long identity = Binder.clearCallingIdentity();
         List<SubscriptionInfo> subInfoList;
 
         try {
-            subInfoList = getAllSubInfoList(mContext.getOpPackageName());
+            subInfoList = getAllSubInfoList(mContext.getOpPackageName(),
+                    mContext.getAttributionTag());
             if (groupUuid == null || subInfoList == null || subInfoList.isEmpty()) {
                 return new ArrayList<>();
             }
@@ -3329,11 +3614,12 @@
             if (!groupUuid.equals(info.getGroupUuid())) return false;
             int subId = info.getSubscriptionId();
             return TelephonyPermissions.checkCallingOrSelfReadPhoneState(mContext, subId,
-                    callingPackage, "getSubscriptionsInGroup")
+                    callingPackage, callingFeatureId, "getSubscriptionsInGroup")
                     || info.canManageSubscription(mContext, callingPackage);
         }).map(subscriptionInfo -> conditionallyRemoveIdentifiers(subscriptionInfo,
-                callingPackage, "getSubscriptionsInGroup"))
+                callingPackage, callingFeatureId, "getSubscriptionsInGroup"))
         .collect(Collectors.toList());
+
     }
 
     public ParcelUuid getGroupUuid(int subId) {
@@ -3373,8 +3659,11 @@
                         "setSubscriptionEnabled not usable subId " + subId);
             }
 
+            // Nothing to do if it's already active or inactive.
+            if (enable == isActiveSubscriptionId(subId)) return true;
+
             SubscriptionInfo info = SubscriptionController.getInstance()
-                    .getAllSubInfoList(mContext.getOpPackageName())
+                    .getAllSubInfoList(mContext.getOpPackageName(), mContext.getAttributionTag())
                     .stream()
                     .filter(subInfo -> subInfo.getSubscriptionId() == subId)
                     .findFirst()
@@ -3385,6 +3674,8 @@
                 return false;
             }
 
+            // TODO: make sure after slot mapping, we enable the uicc applications for the
+            // subscription we are enabling.
             if (info.isEmbedded()) {
                 return enableEmbeddedSubscription(info, enable);
             } else {
@@ -3416,46 +3707,58 @@
         // updateEnabledSubscriptionGlobalSetting(subId, physicalSlotIndex);
     }
 
-    private static boolean isInactiveInsertedPSim(UiccSlotInfo slotInfo, String cardId) {
-        return !slotInfo.getIsEuicc() && !slotInfo.getIsActive()
-                && slotInfo.getCardStateInfo() == CARD_STATE_INFO_PRESENT
-                && TextUtils.equals(slotInfo.getCardId(), cardId);
-    }
-
     private boolean enablePhysicalSubscription(SubscriptionInfo info, boolean enable) {
-        if (enable && info.getSimSlotIndex() == SubscriptionManager.INVALID_SIM_SLOT_INDEX) {
-            UiccSlotInfo[] slotsInfo = mTelephonyManager.getUiccSlotsInfo();
-            if (slotsInfo == null) return false;
-            boolean foundMatch = false;
-            for (int i = 0; i < slotsInfo.length; i++) {
-                UiccSlotInfo slotInfo = slotsInfo[i];
-                if (isInactiveInsertedPSim(slotInfo, info.getCardString())) {
-                    // We need to send intents to Euicc if we are turning on an inactive pSIM.
-                    // Euicc will decide whether to ask user to switch to DSDS, or change SIM
-                    // slot mapping.
-                    enableSubscriptionOverEuiccManager(info.getSubscriptionId(), enable, i);
-                    foundMatch = true;
+        if (info == null || !SubscriptionManager.isValidSubscriptionId(info.getSubscriptionId())) {
+            return false;
+        }
+
+        int subId = info.getSubscriptionId();
+
+        UiccSlotInfo slotInfo = null;
+        int physicalSlotIndex = SubscriptionManager.INVALID_SIM_SLOT_INDEX;
+        UiccSlotInfo[] slotsInfo = mTelephonyManager.getUiccSlotsInfo();
+        if (slotsInfo == null) return false;
+        for (int i = 0; i < slotsInfo.length; i++) {
+            UiccSlotInfo curSlotInfo = slotsInfo[i];
+            if (curSlotInfo.getCardStateInfo() == CARD_STATE_INFO_PRESENT) {
+                if (TextUtils.equals(IccUtils.stripTrailingFs(curSlotInfo.getCardId()),
+                        IccUtils.stripTrailingFs(info.getCardString()))) {
+                    slotInfo = curSlotInfo;
+                    physicalSlotIndex = i;
                     break;
                 }
             }
-
-            if (!foundMatch) {
-                logdl("enablePhysicalSubscription subId " + info.getSubscriptionId()
-                        + " is not inserted.");
-            }
-            // returning false to indicate state is not changed yet. If intent is sent to LPA and
-            // user consents switching, caller needs to listen to subscription info change.
-            return false;
-        } else {
-            return mTelephonyManager.enableModemForSlot(info.getSimSlotIndex(), enable);
         }
 
-        // TODO: uncomment or clean up if we decide whether to support standalone CBRS for Q.
-        // updateEnabledSubscriptionGlobalSetting(
-        //        enable ? subId : SubscriptionManager.INVALID_SUBSCRIPTION_ID,
-        //        physicalSlotIndex);
-        // updateModemStackEnabledGlobalSetting(enable, physicalSlotIndex);
-        // refreshCachedActiveSubscriptionInfoList();
+        // Can't find the existing SIM.
+        if (slotInfo == null) return false;
+
+        if (enable && !slotInfo.getIsActive()) {
+            // We need to send intents to Euicc if we are turning on an inactive slot.
+            // Euicc will decide whether to ask user to switch to DSDS, or change SIM
+            // slot mapping.
+            EuiccManager euiccManager =
+                    (EuiccManager) mContext.getSystemService(Context.EUICC_SERVICE);
+            if (euiccManager != null && euiccManager.isEnabled()) {
+                enableSubscriptionOverEuiccManager(subId, enable, physicalSlotIndex);
+            } else {
+                // Enable / disable uicc applications.
+                if (!info.areUiccApplicationsEnabled()) setUiccApplicationsEnabled(enable, subId);
+                // If euiccManager is not enabled, we try to switch to DSDS if possible,
+                // or switch slot if not.
+                if (mTelephonyManager.isMultiSimSupported() == MULTISIM_ALLOWED) {
+                    PhoneConfigurationManager.getInstance().switchMultiSimConfig(
+                            mTelephonyManager.getSupportedModemCount());
+                } else {
+                    UiccController.getInstance().switchSlots(new int[]{physicalSlotIndex}, null);
+                }
+            }
+            return true;
+        } else {
+            // Enable / disable uicc applications.
+            setUiccApplicationsEnabled(enable, subId);
+            return true;
+        }
     }
 
     private void enableSubscriptionOverEuiccManager(int subId, boolean enable,
@@ -3599,20 +3902,25 @@
     // Helper function of getOpportunisticSubscriptions and getActiveSubscriptionInfoList.
     // They are doing similar things except operating on different cache.
     private List<SubscriptionInfo> getSubscriptionInfoListFromCacheHelper(
-            String callingPackage, List<SubscriptionInfo> cacheSubList) {
+            String callingPackage, String callingFeatureId, List<SubscriptionInfo> cacheSubList) {
         boolean canReadPhoneState = false;
         boolean canReadIdentifiers = false;
+        boolean canReadPhoneNumber = false;
         try {
             canReadPhoneState = TelephonyPermissions.checkReadPhoneState(mContext,
                     SubscriptionManager.INVALID_SUBSCRIPTION_ID, Binder.getCallingPid(),
-                    Binder.getCallingUid(), callingPackage, "getSubscriptionInfoList");
+                    Binder.getCallingUid(), callingPackage, callingFeatureId,
+                    "getSubscriptionInfoList");
             // If the calling package has the READ_PHONE_STATE permission then check if the caller
-            // also has access to subscriber identifiers to ensure that the ICC
+            // also has access to subscriber identifiers and the phone number to ensure that the ICC
             // ID and any other unique identifiers are removed if the caller should not have access.
             if (canReadPhoneState) {
                 canReadIdentifiers = hasSubscriberIdentifierAccess(
                         SubscriptionManager.INVALID_SUBSCRIPTION_ID, callingPackage,
-                        "getSubscriptionInfoList");
+                        callingFeatureId, "getSubscriptionInfoList");
+                canReadPhoneNumber = hasPhoneNumberAccess(
+                        SubscriptionManager.INVALID_SUBSCRIPTION_ID, callingPackage,
+                        callingFeatureId, "getSubscriptionInfoList");
             }
         } catch (SecurityException e) {
             // If a SecurityException is thrown during the READ_PHONE_STATE check then the only way
@@ -3623,7 +3931,7 @@
 
         synchronized (mSubInfoListLock) {
             // If the caller can read all phone state, just return the full list.
-            if (canReadIdentifiers) {
+            if (canReadIdentifiers && canReadPhoneNumber) {
                 return new ArrayList<>(cacheSubList);
             }
             // Filter the list to only include subscriptions which the caller can manage.
@@ -3631,7 +3939,7 @@
             for (SubscriptionInfo subscriptionInfo : cacheSubList) {
                 int subId = subscriptionInfo.getSubscriptionId();
                 boolean hasCarrierPrivileges = TelephonyPermissions.checkCarrierPrivilegeForSubId(
-                        subId);
+                        mContext, subId);
                 // If the caller does not have the READ_PHONE_STATE permission nor carrier
                 // privileges then they cannot access the current subscription.
                 if (!canReadPhoneState && !hasCarrierPrivileges) {
@@ -3646,7 +3954,8 @@
                     // identifiers in the subscription based on the results of the initial
                     // permission checks.
                     subscriptions.add(
-                            conditionallyRemoveIdentifiers(subscriptionInfo, canReadIdentifiers));
+                            conditionallyRemoveIdentifiers(subscriptionInfo, canReadIdentifiers,
+                                    canReadPhoneNumber));
                 }
             }
             return subscriptions;
@@ -3663,24 +3972,28 @@
      * in a cache.
      */
     private SubscriptionInfo conditionallyRemoveIdentifiers(SubscriptionInfo subInfo,
-            String callingPackage,  String message) {
+            String callingPackage, String callingFeatureId, String message) {
         SubscriptionInfo result = subInfo;
         int subId = subInfo.getSubscriptionId();
-        boolean hasIdentifierAccess = hasSubscriberIdentifierAccess(subId, callingPackage, message);
-        return conditionallyRemoveIdentifiers(subInfo, hasIdentifierAccess);
+        boolean hasIdentifierAccess = hasSubscriberIdentifierAccess(subId, callingPackage,
+                callingFeatureId, message);
+        boolean hasPhoneNumberAccess = hasPhoneNumberAccess(subId, callingPackage, callingFeatureId,
+                message);
+        return conditionallyRemoveIdentifiers(subInfo, hasIdentifierAccess, hasPhoneNumberAccess);
     }
 
     /**
      * Conditionally removes identifiers from the provided {@code subInfo} based on if the calling
-     * package {@code hasIdentifierAccess} and returns the potentially modified object.
+     * package {@code hasIdentifierAccess} and {@code hasPhoneNumberAccess} and returns the
+     * potentially modified object.
      *
-     * <p>If the caller specifies the package does not have identifier access
+     * <p>If the caller specifies the package does not have identifier or phone number access
      * a clone of the provided SubscriptionInfo is created and modified to avoid altering
      * SubscriptionInfo objects in a cache.
      */
     private SubscriptionInfo conditionallyRemoveIdentifiers(SubscriptionInfo subInfo,
-            boolean hasIdentifierAccess) {
-        if (hasIdentifierAccess) {
+            boolean hasIdentifierAccess, boolean hasPhoneNumberAccess) {
+        if (hasIdentifierAccess && hasPhoneNumberAccess) {
             return subInfo;
         }
         SubscriptionInfo result = new SubscriptionInfo(subInfo);
@@ -3688,11 +4001,14 @@
             result.clearIccId();
             result.clearCardString();
         }
+        if (!hasPhoneNumberAccess) {
+            result.clearNumber();
+        }
         return result;
     }
 
     private synchronized boolean addToSubIdList(int slotIndex, int subId, int subscriptionType) {
-        ArrayList<Integer> subIdsList = sSlotIndexToSubIds.get(slotIndex);
+        ArrayList<Integer> subIdsList = sSlotIndexToSubIds.getCopy(slotIndex);
         if (subIdsList == null) {
             subIdsList = new ArrayList<>();
             sSlotIndexToSubIds.put(slotIndex, subIdsList);
@@ -3705,12 +4021,23 @@
         }
         if (isSubscriptionForRemoteSim(subscriptionType)) {
             // For Remote SIM subscriptions, a slot can have multiple subscriptions.
-            subIdsList.add(subId);
+            sSlotIndexToSubIds.addToSubIdList(slotIndex, subId);
         } else {
             // for all other types of subscriptions, a slot can have only one subscription at a time
-            subIdsList.clear();
-            subIdsList.add(subId);
+            sSlotIndexToSubIds.clearSubIdList(slotIndex);
+            sSlotIndexToSubIds.addToSubIdList(slotIndex, subId);
         }
+
+
+        // Remove the slot from sSlotIndexToSubIds if it has the same sub id with the added slot
+        for (Entry<Integer, ArrayList<Integer>> entry : sSlotIndexToSubIds.entrySet()) {
+            if (entry.getKey() != slotIndex && entry.getValue() != null
+                    && entry.getValue().contains(subId)) {
+                logdl("addToSubIdList - remove " + entry.getKey());
+                sSlotIndexToSubIds.remove(entry.getKey());
+            }
+        }
+
         if (DBG) logdl("slotIndex, subId combo is added to the map.");
         return true;
     }
@@ -3725,7 +4052,7 @@
      */
     @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
     public Map<Integer, ArrayList<Integer>> getSlotIndexToSubIdsMap() {
-        return sSlotIndexToSubIds;
+        return sSlotIndexToSubIds.getMap();
     }
 
     /**
@@ -3734,19 +4061,16 @@
      */
     @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
     public void resetStaticMembers() {
-        mDefaultFallbackSubId = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
+        sDefaultFallbackSubId.set(SubscriptionManager.INVALID_SUBSCRIPTION_ID);
         mDefaultPhoneId = SubscriptionManager.DEFAULT_PHONE_INDEX;
     }
 
     private void notifyOpportunisticSubscriptionInfoChanged() {
-        ITelephonyRegistry tr = ITelephonyRegistry.Stub.asInterface(ServiceManager.getService(
-                "telephony.registry"));
-        try {
-            if (DBG) logd("notifyOpptSubscriptionInfoChanged:");
-            tr.notifyOpportunisticSubscriptionInfoChanged();
-        } catch (RemoteException ex) {
-            // Should never happen because its always available.
-        }
+        TelephonyRegistryManager trm =
+                (TelephonyRegistryManager)
+                        mContext.getSystemService(Context.TELEPHONY_REGISTRY_SERVICE);
+        if (DBG) logd("notifyOpptSubscriptionInfoChanged:");
+        trm.notifyOpportunisticSubscriptionInfoChanged();
     }
 
     private void refreshCachedOpportunisticSubscriptionInfoList() {
@@ -3801,26 +4125,6 @@
         return true;
     }
 
-    // TODO: This method should belong to Telephony manager like other data enabled settings and
-    // override APIs. Remove this once TelephonyManager API is added.
-    @Override
-    public boolean setAlwaysAllowMmsData(int subId, boolean alwaysAllow) {
-        if (DBG) logd("[setAlwaysAllowMmsData]+ alwaysAllow:" + alwaysAllow + " subId:" + subId);
-
-        enforceModifyPhoneState("setAlwaysAllowMmsData");
-
-        // Now that all security checks passes, perform the operation as ourselves.
-        final long identity = Binder.clearCallingIdentity();
-        try {
-            validateSubId(subId);
-            Phone phone = PhoneFactory.getPhone(getPhoneId(subId));
-            if (phone == null) return false;
-            return phone.getDataEnabledSettings().setAlwaysAllowMmsData(alwaysAllow);
-        } finally {
-            Binder.restoreCallingIdentity(identity);
-        }
-    }
-
     /**
      * Set allowing mobile data during voice call.
      *
@@ -3855,7 +4159,7 @@
      */
     @NonNull
     public String getDataEnabledOverrideRules(int subId) {
-        return TextUtils.emptyIfNull(getSubscriptionProperty(subId,
+        return TelephonyUtils.emptyIfNull(getSubscriptionProperty(subId,
                 SubscriptionManager.DATA_ENABLED_OVERRIDE_RULES));
     }
 
@@ -3885,4 +4189,99 @@
             Binder.restoreCallingIdentity(token);
         }
     }
+
+    /**
+     * Whether it's supported to disable / re-enable a subscription on a physical (non-euicc) SIM.
+     */
+    @Override
+    public boolean canDisablePhysicalSubscription() {
+        enforceReadPrivilegedPhoneState("canToggleUiccApplicationsEnablement");
+
+        final long identity = Binder.clearCallingIdentity();
+        try {
+            Phone phone = PhoneFactory.getDefaultPhone();
+            return phone != null && phone.canDisablePhysicalSubscription();
+        } finally {
+            Binder.restoreCallingIdentity(identity);
+        }
+    }
+
+    /**
+     * @hide
+     */
+    private void setGlobalSetting(String name, int value) {
+        Settings.Global.putInt(mContext.getContentResolver(), name, value);
+        if (name == Settings.Global.MULTI_SIM_DATA_CALL_SUBSCRIPTION) {
+            invalidateDefaultDataSubIdCaches();
+            invalidateActiveDataSubIdCaches();
+            invalidateDefaultSubIdCaches();
+            invalidateSlotIndexCaches();
+        } else if (name == Settings.Global.MULTI_SIM_VOICE_CALL_SUBSCRIPTION) {
+            invalidateDefaultSubIdCaches();
+            invalidateSlotIndexCaches();
+        } else if (name == Settings.Global.MULTI_SIM_SMS_SUBSCRIPTION) {
+            invalidateDefaultSmsSubIdCaches();
+        }
+    }
+
+    /**
+     * @hide
+     */
+    private static void invalidateDefaultSubIdCaches() {
+        if (sCachingEnabled) {
+            SubscriptionManager.invalidateDefaultSubIdCaches();
+        }
+    }
+
+    /**
+     * @hide
+     */
+    private static void invalidateDefaultDataSubIdCaches() {
+        if (sCachingEnabled) {
+            SubscriptionManager.invalidateDefaultDataSubIdCaches();
+        }
+    }
+
+    /**
+     * @hide
+     */
+    private static void invalidateDefaultSmsSubIdCaches() {
+        if (sCachingEnabled) {
+            SubscriptionManager.invalidateDefaultSmsSubIdCaches();
+        }
+    }
+
+    /**
+     * @hide
+     */
+    protected static void invalidateActiveDataSubIdCaches() {
+        if (sCachingEnabled) {
+            SubscriptionManager.invalidateActiveDataSubIdCaches();
+        }
+    }
+
+    /**
+     * @hide
+     */
+    protected static void invalidateSlotIndexCaches() {
+        if (sCachingEnabled) {
+            SubscriptionManager.invalidateSlotIndexCaches();
+        }
+    }
+
+    /**
+     * @hide
+     */
+    @VisibleForTesting
+    public static void disableCaching() {
+        sCachingEnabled = false;
+    }
+
+    /**
+     * @hide
+     */
+    @VisibleForTesting
+    public static void enableCaching() {
+        sCachingEnabled = true;
+    }
 }
diff --git a/src/java/com/android/internal/telephony/SubscriptionInfoUpdater.java b/src/java/com/android/internal/telephony/SubscriptionInfoUpdater.java
index 5e336ea..dc842d2 100644
--- a/src/java/com/android/internal/telephony/SubscriptionInfoUpdater.java
+++ b/src/java/com/android/internal/telephony/SubscriptionInfoUpdater.java
@@ -18,24 +18,22 @@
 
 import android.Manifest;
 import android.annotation.Nullable;
-import android.annotation.UnsupportedAppUsage;
 import android.app.ActivityManager;
-import android.app.UserSwitchObserver;
+import android.compat.annotation.UnsupportedAppUsage;
+import android.content.BroadcastReceiver;
 import android.content.ContentResolver;
 import android.content.ContentValues;
 import android.content.Context;
 import android.content.Intent;
+import android.content.IntentFilter;
 import android.content.SharedPreferences;
-import android.content.pm.IPackageManager;
 import android.os.AsyncResult;
 import android.os.Handler;
-import android.os.IRemoteCallback;
 import android.os.Looper;
 import android.os.Message;
 import android.os.ParcelUuid;
 import android.os.PersistableBundle;
-import android.os.RemoteException;
-import android.os.ServiceManager;
+import android.os.UserHandle;
 import android.preference.PreferenceManager;
 import android.provider.Settings;
 import android.provider.Settings.Global;
@@ -46,10 +44,10 @@
 import android.service.euicc.EuiccService;
 import android.service.euicc.GetEuiccProfileInfoListResult;
 import android.telephony.CarrierConfigManager;
-import android.telephony.Rlog;
 import android.telephony.SubscriptionInfo;
 import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyManager;
+import android.telephony.TelephonyManager.SimState;
 import android.telephony.UiccAccessRule;
 import android.telephony.euicc.EuiccManager;
 import android.text.TextUtils;
@@ -63,6 +61,7 @@
 import com.android.internal.telephony.uicc.UiccCard;
 import com.android.internal.telephony.uicc.UiccController;
 import com.android.internal.telephony.uicc.UiccSlot;
+import com.android.telephony.Rlog;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
@@ -75,7 +74,8 @@
 public class SubscriptionInfoUpdater extends Handler {
     private static final String LOG_TAG = "SubscriptionInfoUpdater";
     @UnsupportedAppUsage
-    private static final int PROJECT_SIM_NUM = TelephonyManager.getDefault().getPhoneCount();
+    private static final int SUPPORTED_MODEM_COUNT = TelephonyManager.getDefault()
+            .getSupportedModemCount();
 
     private static final boolean DBG = true;
 
@@ -91,6 +91,8 @@
     private static final int EVENT_SIM_READY = 10;
     private static final int EVENT_SIM_IMSI = 11;
     private static final int EVENT_REFRESH_EMBEDDED_SUBSCRIPTIONS = 12;
+    private static final int EVENT_MULTI_SIM_CONFIG_CHANGED = 13;
+    private static final int EVENT_INACTIVE_SLOT_ICC_STATE_CHANGED = 14;
 
     private static final String ICCID_STRING_FOR_NO_SIM = "";
 
@@ -101,18 +103,16 @@
     public static final String CURR_SUBID = "curr_subid";
 
     @UnsupportedAppUsage
-    private static Phone[] mPhone;
+    private static Context sContext = null;
     @UnsupportedAppUsage
-    private static Context mContext = null;
-    @UnsupportedAppUsage
-    private static String mIccId[] = new String[PROJECT_SIM_NUM];
-    private static int[] sSimCardState = new int[PROJECT_SIM_NUM];
-    private static int[] sSimApplicationState = new int[PROJECT_SIM_NUM];
+
+    protected static String[] sIccId = new String[SUPPORTED_MODEM_COUNT];
+    private static String[] sInactiveIccIds = new String[SUPPORTED_MODEM_COUNT];
+    private static int[] sSimCardState = new int[SUPPORTED_MODEM_COUNT];
+    private static int[] sSimApplicationState = new int[SUPPORTED_MODEM_COUNT];
     private static boolean sIsSubInfoInitialized = false;
     private SubscriptionManager mSubscriptionManager = null;
     private EuiccManager mEuiccManager;
-    @UnsupportedAppUsage
-    private IPackageManager mPackageManager;
     private Handler mBackgroundHandler;
 
     // The current foreground user ID.
@@ -124,7 +124,7 @@
      * Runnable with a boolean parameter. This is used in
      * updateEmbeddedSubscriptions(List<Integer> cardIds, @Nullable UpdateEmbeddedSubsCallback).
      */
-    private interface UpdateEmbeddedSubsCallback {
+    protected interface UpdateEmbeddedSubsCallback {
         /**
          * Callback of the Runnable.
          * @param hasChanges Whether there is any subscription info change. If yes, we need to
@@ -135,26 +135,20 @@
 
     // TODO: The SubscriptionController instance should be passed in here from PhoneFactory
     // rather than invoking the static getter all over the place.
-    public SubscriptionInfoUpdater(
-            Looper looper, Context context, Phone[] phone, CommandsInterface[] ci) {
-        this(looper, context, phone, ci,
-                IPackageManager.Stub.asInterface(ServiceManager.getService("package")));
-    }
-
-    @VisibleForTesting public SubscriptionInfoUpdater(
-            Looper looper, Context context, Phone[] phone,
-            CommandsInterface[] ci, IPackageManager packageMgr) {
+    @VisibleForTesting public SubscriptionInfoUpdater(Looper looper, Context context,
+            CommandsInterface[] ci) {
         logd("Constructor invoked");
         mBackgroundHandler = new Handler(looper);
 
-        mContext = context;
-        mPhone = phone;
-        mSubscriptionManager = SubscriptionManager.from(mContext);
-        mEuiccManager = (EuiccManager) mContext.getSystemService(Context.EUICC_SERVICE);
-        mPackageManager = packageMgr;
+        sContext = context;
+        mSubscriptionManager = SubscriptionManager.from(sContext);
+        mEuiccManager = (EuiccManager) sContext.getSystemService(Context.EUICC_SERVICE);
 
-        mCarrierServiceBindHelper = new CarrierServiceBindHelper(mContext);
+        mCarrierServiceBindHelper = new CarrierServiceBindHelper(sContext);
         initializeCarrierApps();
+
+        PhoneConfigurationManager.registerForMultiSimConfigChange(
+                this, EVENT_MULTI_SIM_CONFIG_CHANGED, null);
     }
 
     private void initializeCarrierApps() {
@@ -163,46 +157,48 @@
         // -Whenever new carrier privilege rules might change (new SIM is loaded)
         // -Whenever we switch to a new user
         mCurrentlyActiveUserId = 0;
-        try {
-            ActivityManager.getService().registerUserSwitchObserver(new UserSwitchObserver() {
-                @Override
-                public void onUserSwitching(int newUserId, IRemoteCallback reply)
-                        throws RemoteException {
-                    mCurrentlyActiveUserId = newUserId;
-                    CarrierAppUtils.disableCarrierAppsUntilPrivileged(mContext.getOpPackageName(),
-                            mPackageManager, TelephonyManager.getDefault(),
-                            mContext.getContentResolver(), mCurrentlyActiveUserId);
-
-                    if (reply != null) {
-                        try {
-                            reply.sendResult(null);
-                        } catch (RemoteException e) {
-                        }
-                    }
+        sContext.registerReceiverForAllUsers(new BroadcastReceiver() {
+            @Override
+            public void onReceive(Context context, Intent intent) {
+                // Remove this line after testing
+                if (Intent.ACTION_USER_FOREGROUND.equals(intent.getAction())) {
+                    UserHandle userHandle = intent.getParcelableExtra(Intent.EXTRA_USER);
+                    // If couldn't get current user ID, guess it's 0.
+                    mCurrentlyActiveUserId = userHandle != null ? userHandle.getIdentifier() : 0;
+                    CarrierAppUtils.disableCarrierAppsUntilPrivileged(sContext.getOpPackageName(),
+                            TelephonyManager.getDefault(), mCurrentlyActiveUserId, sContext);
                 }
-            }, LOG_TAG);
-            mCurrentlyActiveUserId = ActivityManager.getService().getCurrentUser().id;
-        } catch (RemoteException e) {
-            logd("Couldn't get current user ID; guessing it's 0: " + e.getMessage());
-        }
-        CarrierAppUtils.disableCarrierAppsUntilPrivileged(mContext.getOpPackageName(),
-                mPackageManager, TelephonyManager.getDefault(), mContext.getContentResolver(),
-                mCurrentlyActiveUserId);
+            }
+        }, new IntentFilter(Intent.ACTION_USER_FOREGROUND), null, null);
+        ActivityManager am = (ActivityManager) sContext.getSystemService(Context.ACTIVITY_SERVICE);
+        mCurrentlyActiveUserId = am.getCurrentUser();
+        CarrierAppUtils.disableCarrierAppsUntilPrivileged(sContext.getOpPackageName(),
+                TelephonyManager.getDefault(), mCurrentlyActiveUserId, sContext);
     }
 
     /**
      * Update subscriptions when given a new ICC state.
      */
-    public void updateInternalIccState(String simStatus, String reason, int slotId,
-            boolean absentAndInactive) {
+    public void updateInternalIccState(String simStatus, String reason, int phoneId) {
         logd("updateInternalIccState to simStatus " + simStatus + " reason " + reason
-                + " slotId " + slotId);
+                + " phoneId " + phoneId);
         int message = internalIccStateToMessage(simStatus);
         if (message != EVENT_INVALID) {
-            sendMessage(obtainMessage(message, slotId, absentAndInactive ? 1 : 0, reason));
+            sendMessage(obtainMessage(message, phoneId, 0, reason));
         }
     }
 
+    /**
+     * Update subscriptions if needed when there's a change in inactive slot.
+     * @param prevActivePhoneId is the corresponding phoneId of the slot if slot was previously
+     *                          active. It could be INVALID if it was already inactive.
+     * @param iccId iccId in that slot, if any.
+     */
+    public void updateInternalIccStateForInactiveSlot(int prevActivePhoneId, String iccId) {
+        sendMessage(obtainMessage(EVENT_INACTIVE_SLOT_ICC_STATE_CHANGED, prevActivePhoneId,
+                0, iccId));
+    }
+
     private int internalIccStateToMessage(String simStatus) {
         switch(simStatus) {
             case IccCardConstants.INTENT_VALUE_ICC_ABSENT: return EVENT_SIM_ABSENT;
@@ -221,12 +217,12 @@
     }
 
     @UnsupportedAppUsage
-    private boolean isAllIccIdQueryDone() {
-        for (int i = 0; i < PROJECT_SIM_NUM; i++) {
+    protected boolean isAllIccIdQueryDone() {
+        for (int i = 0; i < TelephonyManager.getDefault().getActiveModemCount(); i++) {
             UiccSlot slot = UiccController.getInstance().getUiccSlotForPhone(i);
             int slotId = UiccController.getInstance().getSlotIdFromPhoneId(i);
-            if  (mIccId[i] == null || slot == null || !slot.isActive()) {
-                if (mIccId[i] == null) {
+            if  (sIccId[i] == null || slot == null || !slot.isActive()) {
+                if (sIccId[i] == null) {
                     logd("Wait for SIM " + i + " Iccid");
                 } else {
                     logd(String.format("Wait for slot corresponding to phone %d to be active, "
@@ -250,7 +246,7 @@
                 if (ar.exception == null && ar.result != null) {
                     int[] modes = (int[])ar.result;
                     if (modes[0] == 1) {  // Manual mode.
-                        mPhone[slotId].setNetworkSelectionModeAutomatic(null);
+                        PhoneFactory.getPhone(slotId).setNetworkSelectionModeAutomatic(null);
                     }
                 } else {
                     logd("EVENT_GET_NETWORK_SELECTION_MODE_DONE: error getting network mode.");
@@ -263,7 +259,11 @@
                 break;
 
             case EVENT_SIM_ABSENT:
-                handleSimAbsent(msg.arg1, msg.arg2);
+                handleSimAbsent(msg.arg1);
+                break;
+
+            case EVENT_INACTIVE_SLOT_ICC_STATE_CHANGED:
+                handleInactiveSlotIccStateChange(msg.arg1, (String) msg.obj);
                 break;
 
             case EVENT_SIM_LOCKED:
@@ -294,15 +294,7 @@
                 break;
 
             case EVENT_SIM_READY:
-                cardIds.add(getCardIdFromPhoneId(msg.arg1));
-                updateEmbeddedSubscriptions(cardIds, (hasChanges) -> {
-                    if (hasChanges) {
-                        SubscriptionController.getInstance().notifySubscriptionInfoChanged();
-                    }
-                });
-                broadcastSimStateChanged(msg.arg1, IccCardConstants.INTENT_VALUE_ICC_READY, null);
-                broadcastSimCardStateChanged(msg.arg1, TelephonyManager.SIM_STATE_PRESENT);
-                broadcastSimApplicationStateChanged(msg.arg1, TelephonyManager.SIM_STATE_NOT_READY);
+                handleSimReady(msg.arg1);
                 break;
 
             case EVENT_SIM_IMSI:
@@ -334,12 +326,28 @@
                 });
                 break;
 
+            case EVENT_MULTI_SIM_CONFIG_CHANGED:
+                onMultiSimConfigChanged();
+                break;
+
             default:
                 logd("Unknown msg:" + msg.what);
         }
     }
 
-    private int getCardIdFromPhoneId(int phoneId) {
+    private void onMultiSimConfigChanged() {
+        int activeModemCount = ((TelephonyManager) sContext.getSystemService(
+                Context.TELEPHONY_SERVICE)).getActiveModemCount();
+        // For inactive modems, reset its states.
+        for (int phoneId = activeModemCount; phoneId < SUPPORTED_MODEM_COUNT; phoneId++) {
+            SubscriptionController.getInstance().clearSubInfoRecord(phoneId);
+            sIccId[phoneId] = null;
+            sSimCardState[phoneId] = TelephonyManager.SIM_STATE_UNKNOWN;
+            sSimApplicationState[phoneId] = TelephonyManager.SIM_STATE_UNKNOWN;
+        }
+    }
+
+    protected int getCardIdFromPhoneId(int phoneId) {
         UiccController uiccController = UiccController.getInstance();
         UiccCard card = uiccController.getUiccCardForPhone(phoneId);
         if (card != null) {
@@ -353,13 +361,13 @@
                 EVENT_REFRESH_EMBEDDED_SUBSCRIPTIONS, cardId, 0 /* arg2 */, callback));
     }
 
-    private void handleSimLocked(int slotId, String reason) {
-        if (mIccId[slotId] != null && mIccId[slotId].equals(ICCID_STRING_FOR_NO_SIM)) {
-            logd("SIM" + (slotId + 1) + " hot plug in");
-            mIccId[slotId] = null;
+    protected void handleSimLocked(int phoneId, String reason) {
+        if (sIccId[phoneId] != null && sIccId[phoneId].equals(ICCID_STRING_FOR_NO_SIM)) {
+            logd("SIM" + (phoneId + 1) + " hot plug in");
+            sIccId[phoneId] = null;
         }
 
-        IccCard iccCard = mPhone[slotId].getIccCard();
+        IccCard iccCard = PhoneFactory.getPhone(phoneId).getIccCard();
         if (iccCard == null) {
             logd("handleSimLocked: IccCard null");
             return;
@@ -373,15 +381,15 @@
             logd("handleSimLocked: IccID null");
             return;
         }
-        mIccId[slotId] = IccUtils.stripTrailingFs(records.getFullIccId());
+        sIccId[phoneId] = IccUtils.stripTrailingFs(records.getFullIccId());
 
-        updateSubscriptionInfoByIccId(slotId, true /* updateEmbeddedSubs */);
+        updateSubscriptionInfoByIccId(phoneId, true /* updateEmbeddedSubs */);
 
-        broadcastSimStateChanged(slotId, IccCardConstants.INTENT_VALUE_ICC_LOCKED, reason);
-        broadcastSimCardStateChanged(slotId, TelephonyManager.SIM_STATE_PRESENT);
-        broadcastSimApplicationStateChanged(slotId, getSimStateFromLockedReason(reason));
-        updateSubscriptionCarrierId(slotId, IccCardConstants.INTENT_VALUE_ICC_LOCKED);
-        updateCarrierServices(slotId, IccCardConstants.INTENT_VALUE_ICC_LOCKED);
+        broadcastSimStateChanged(phoneId, IccCardConstants.INTENT_VALUE_ICC_LOCKED, reason);
+        broadcastSimCardStateChanged(phoneId, TelephonyManager.SIM_STATE_PRESENT);
+        broadcastSimApplicationStateChanged(phoneId, getSimStateFromLockedReason(reason));
+        updateSubscriptionCarrierId(phoneId, IccCardConstants.INTENT_VALUE_ICC_LOCKED);
+        updateCarrierServices(phoneId, IccCardConstants.INTENT_VALUE_ICC_LOCKED);
     }
 
     private static int getSimStateFromLockedReason(String lockedReason) {
@@ -400,32 +408,69 @@
         }
     }
 
-    private void handleSimNotReady(int slotId) {
-        logd("handleSimNotReady: slotId: " + slotId);
+    protected void handleSimReady(int phoneId) {
+        List<Integer> cardIds = new ArrayList<>();
 
-        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;
-            updateSubscriptionInfoByIccId(slotId, false /* updateEmbeddedSubs */);
+        cardIds.add(getCardIdFromPhoneId(phoneId));
+        updateEmbeddedSubscriptions(cardIds, (hasChanges) -> {
+        if (hasChanges) {
+            SubscriptionController.getInstance().notifySubscriptionInfoChanged();
         }
-
-        broadcastSimStateChanged(slotId, IccCardConstants.INTENT_VALUE_ICC_NOT_READY,
-                null);
-        broadcastSimCardStateChanged(slotId, TelephonyManager.SIM_STATE_PRESENT);
-        broadcastSimApplicationStateChanged(slotId, TelephonyManager.SIM_STATE_NOT_READY);
+        });
+        broadcastSimStateChanged(phoneId, IccCardConstants.INTENT_VALUE_ICC_READY, null);
+        broadcastSimCardStateChanged(phoneId, TelephonyManager.SIM_STATE_PRESENT);
+        broadcastSimApplicationStateChanged(phoneId, TelephonyManager.SIM_STATE_NOT_READY);
     }
 
-    private void handleSimLoaded(int slotId) {
-        logd("handleSimLoaded: slotId: " + slotId);
+
+    protected void handleSimNotReady(int phoneId) {
+        logd("handleSimNotReady: phoneId: " + phoneId);
+        boolean isFinalState = false;
+
+        IccCard iccCard = PhoneFactory.getPhone(phoneId).getIccCard();
+        boolean uiccAppsDisabled = areUiccAppsDisabledOnCard(phoneId);
+        if (iccCard.isEmptyProfile() || uiccAppsDisabled) {
+            if (uiccAppsDisabled) {
+                UiccSlot slot = UiccController.getInstance().getUiccSlotForPhone(phoneId);
+                sInactiveIccIds[phoneId] = IccUtils.stripTrailingFs(slot.getIccId());
+            }
+            isFinalState = true;
+            // ICC_NOT_READY is a terminal state for
+            // 1) It's an empty profile as there's no uicc applications. Or
+            // 2) Its uicc applications are set to be disabled.
+            // At this phase, the subscription list is accessible. Treating NOT_READY
+            // as equivalent to ABSENT, once the rest of the system can handle it.
+            sIccId[phoneId] = ICCID_STRING_FOR_NO_SIM;
+            updateSubscriptionInfoByIccId(phoneId, false /* updateEmbeddedSubs */);
+        }
+
+        broadcastSimStateChanged(phoneId, IccCardConstants.INTENT_VALUE_ICC_NOT_READY,
+                null);
+        broadcastSimCardStateChanged(phoneId, TelephonyManager.SIM_STATE_PRESENT);
+        broadcastSimApplicationStateChanged(phoneId, TelephonyManager.SIM_STATE_NOT_READY);
+        if (isFinalState) {
+            updateCarrierServices(phoneId, IccCardConstants.INTENT_VALUE_ICC_NOT_READY);
+        }
+    }
+
+    private boolean areUiccAppsDisabledOnCard(int phoneId) {
+        // When uicc apps are disabled(supported in IRadio 1.5), we will still get IccId from
+        // cardStatus (since IRadio 1.2). Amd upon cardStatus change we'll receive another
+        // handleSimNotReady so this will be evaluated again.
+        UiccSlot slot = UiccController.getInstance().getUiccSlotForPhone(phoneId);
+        if (slot == null || slot.getIccId() == null) return false;
+        SubscriptionInfo info = SubscriptionController.getInstance()
+                .getSubInfoForIccId(IccUtils.stripTrailingFs(slot.getIccId()));
+        return info != null && !info.areUiccApplicationsEnabled();
+    }
+
+    protected void handleSimLoaded(int phoneId) {
+        logd("handleSimLoaded: phoneId: " + phoneId);
 
         // The SIM should be loaded at this state, but it is possible in cases such as SIM being
         // removed or a refresh RESET that the IccRecords could be null. The right behavior is to
         // not broadcast the SIM loaded.
-        int loadedSlotId = slotId;
-        IccCard iccCard = mPhone[slotId].getIccCard();
+        IccCard iccCard = PhoneFactory.getPhone(phoneId).getIccCard();
         if (iccCard == null) {  // Possibly a race condition.
             logd("handleSimLoaded: IccCard null");
             return;
@@ -439,30 +484,30 @@
             logd("handleSimLoaded: IccID null");
             return;
         }
-        mIccId[slotId] = IccUtils.stripTrailingFs(records.getFullIccId());
+        sIccId[phoneId] = IccUtils.stripTrailingFs(records.getFullIccId());
 
-        updateSubscriptionInfoByIccId(slotId, true /* updateEmbeddedSubs */);
+        updateSubscriptionInfoByIccId(phoneId, true /* updateEmbeddedSubs */);
         List<SubscriptionInfo> subscriptionInfos = SubscriptionController.getInstance()
-                .getSubInfoUsingSlotIndexPrivileged(slotId);
+                .getSubInfoUsingSlotIndexPrivileged(phoneId);
         if (subscriptionInfos == null || subscriptionInfos.isEmpty()) {
-            loge("empty subinfo for slotId: " + slotId + "could not update ContentResolver");
+            loge("empty subinfo for phoneId: " + phoneId + "could not update ContentResolver");
         } else {
             for (SubscriptionInfo sub : subscriptionInfos) {
                 int subId = sub.getSubscriptionId();
                 TelephonyManager tm = (TelephonyManager)
-                        mContext.getSystemService(Context.TELEPHONY_SERVICE);
+                        sContext.getSystemService(Context.TELEPHONY_SERVICE);
                 String operator = tm.getSimOperatorNumeric(subId);
 
                 if (!TextUtils.isEmpty(operator)) {
                     if (subId == SubscriptionController.getInstance().getDefaultSubId()) {
-                        MccTable.updateMccMncConfiguration(mContext, operator);
+                        MccTable.updateMccMncConfiguration(sContext, operator);
                     }
                     SubscriptionController.getInstance().setMccMnc(operator, subId);
                 } else {
                     logd("EVENT_RECORDS_LOADED Operator name is null");
                 }
 
-                String iso = tm.getSimCountryIsoForPhone(slotId);
+                String iso = tm.getSimCountryIsoForPhone(phoneId);
 
                 if (!TextUtils.isEmpty(iso)) {
                     SubscriptionController.getInstance().setCountryIso(iso, subId);
@@ -490,12 +535,12 @@
                  * Storing last subId in SharedPreference for now to detect SIM change.
                  */
                 SharedPreferences sp =
-                        PreferenceManager.getDefaultSharedPreferences(mContext);
-                int storedSubId = sp.getInt(CURR_SUBID + slotId, -1);
+                        PreferenceManager.getDefaultSharedPreferences(sContext);
+                int storedSubId = sp.getInt(CURR_SUBID + phoneId, -1);
 
                 if (storedSubId != subId) {
                     int networkType = Settings.Global.getInt(
-                            mPhone[slotId].getContext().getContentResolver(),
+                            PhoneFactory.getPhone(phoneId).getContext().getContentResolver(),
                             Settings.Global.PREFERRED_NETWORK_MODE + subId,
                             -1 /* invalid network mode */);
 
@@ -503,38 +548,37 @@
                         networkType = RILConstants.PREFERRED_NETWORK_MODE;
                         try {
                             networkType = TelephonyManager.getIntAtIndex(
-                                    mContext.getContentResolver(),
-                                    Settings.Global.PREFERRED_NETWORK_MODE, slotId);
+                                    sContext.getContentResolver(),
+                                    Settings.Global.PREFERRED_NETWORK_MODE, phoneId);
                         } catch (SettingNotFoundException retrySnfe) {
                             Rlog.e(LOG_TAG, "Settings Exception Reading Value At Index for "
                                     + "Settings.Global.PREFERRED_NETWORK_MODE");
                         }
                         Settings.Global.putInt(
-                                mPhone[slotId].getContext().getContentResolver(),
+                                PhoneFactory.getPhone(phoneId).getContext().getContentResolver(),
                                 Global.PREFERRED_NETWORK_MODE + subId,
                                 networkType);
                     }
 
                     // Set the modem network mode
-                    mPhone[slotId].setPreferredNetworkType(networkType, null);
+                    PhoneFactory.getPhone(phoneId).setPreferredNetworkType(networkType, null);
 
                     // Only support automatic selection mode on SIM change.
-                    mPhone[slotId].getNetworkSelectionMode(
+                    PhoneFactory.getPhone(phoneId).getNetworkSelectionMode(
                             obtainMessage(EVENT_GET_NETWORK_SELECTION_MODE_DONE,
-                                    new Integer(slotId)));
+                                    new Integer(phoneId)));
 
                     // Update stored subId
                     SharedPreferences.Editor editor = sp.edit();
-                    editor.putInt(CURR_SUBID + slotId, subId);
+                    editor.putInt(CURR_SUBID + phoneId, subId);
                     editor.apply();
                 }
             }
         }
 
         // Update set of enabled carrier apps now that the privilege rules may have changed.
-        CarrierAppUtils.disableCarrierAppsUntilPrivileged(mContext.getOpPackageName(),
-                mPackageManager, TelephonyManager.getDefault(),
-                mContext.getContentResolver(), mCurrentlyActiveUserId);
+        CarrierAppUtils.disableCarrierAppsUntilPrivileged(sContext.getOpPackageName(),
+                TelephonyManager.getDefault(), mCurrentlyActiveUserId, sContext);
 
         /**
          * The sim loading sequence will be
@@ -544,80 +588,120 @@
          *  3. ACTION_SUBSCRIPTION_CARRIER_IDENTITY_CHANGED
          *  4. ACTION_CARRIER_CONFIG_CHANGED
          */
-        broadcastSimStateChanged(loadedSlotId, IccCardConstants.INTENT_VALUE_ICC_LOADED, null);
-        broadcastSimCardStateChanged(loadedSlotId, TelephonyManager.SIM_STATE_PRESENT);
-        broadcastSimApplicationStateChanged(loadedSlotId, TelephonyManager.SIM_STATE_LOADED);
-        updateSubscriptionCarrierId(loadedSlotId, IccCardConstants.INTENT_VALUE_ICC_LOADED);
-        updateCarrierServices(loadedSlotId, IccCardConstants.INTENT_VALUE_ICC_LOADED);
+        broadcastSimStateChanged(phoneId, IccCardConstants.INTENT_VALUE_ICC_LOADED, null);
+        broadcastSimCardStateChanged(phoneId, TelephonyManager.SIM_STATE_PRESENT);
+        broadcastSimApplicationStateChanged(phoneId, TelephonyManager.SIM_STATE_LOADED);
+        updateSubscriptionCarrierId(phoneId, IccCardConstants.INTENT_VALUE_ICC_LOADED);
+        updateCarrierServices(phoneId, IccCardConstants.INTENT_VALUE_ICC_LOADED);
     }
 
-    private void updateCarrierServices(int slotId, String simState) {
+    private void updateCarrierServices(int phoneId, String simState) {
         CarrierConfigManager configManager =
-                (CarrierConfigManager) mContext.getSystemService(Context.CARRIER_CONFIG_SERVICE);
-        configManager.updateConfigForPhoneId(slotId, simState);
-        mCarrierServiceBindHelper.updateForPhoneId(slotId, simState);
+                (CarrierConfigManager) sContext.getSystemService(Context.CARRIER_CONFIG_SERVICE);
+        configManager.updateConfigForPhoneId(phoneId, simState);
+        mCarrierServiceBindHelper.updateForPhoneId(phoneId, simState);
     }
 
-    private void updateSubscriptionCarrierId(int slotId, String simState) {
-        if (mPhone != null && mPhone[slotId] != null) {
-            mPhone[slotId].resolveSubscriptionCarrierId(simState);
+    private void updateSubscriptionCarrierId(int phoneId, String simState) {
+        if (PhoneFactory.getPhone(phoneId) != null) {
+            PhoneFactory.getPhone(phoneId).resolveSubscriptionCarrierId(simState);
         }
     }
 
-    private void handleSimAbsent(int slotId, int absentAndInactive) {
-        if (mIccId[slotId] != null && !mIccId[slotId].equals(ICCID_STRING_FOR_NO_SIM)) {
-            logd("SIM" + (slotId + 1) + " hot plug out, absentAndInactive=" + absentAndInactive);
+    /**
+     * PhoneId is the corresponding phoneId of the slot if slot was previously active.
+     * It could be INVALID if it was already inactive.
+     */
+    private void handleInactiveSlotIccStateChange(int phoneId, String iccId) {
+        if (SubscriptionManager.isValidPhoneId(phoneId)) {
+            // If phoneId is valid, it means the physical slot was previously active in that
+            // phoneId. In this case, found the subId and set its phoneId to invalid.
+            if (sIccId[phoneId] != null && !sIccId[phoneId].equals(ICCID_STRING_FOR_NO_SIM)) {
+                logd("Slot of SIM" + (phoneId + 1) + " becomes inactive");
+            }
+            cleanSubscriptionInPhone(phoneId);
         }
-        mIccId[slotId] = ICCID_STRING_FOR_NO_SIM;
-        updateSubscriptionInfoByIccId(slotId, true /* updateEmbeddedSubs */);
-        // Do not broadcast if the SIM is absent and inactive, because the logical slotId here is
-        // no longer correct
-        if (absentAndInactive == 0) {
-            broadcastSimStateChanged(slotId, IccCardConstants.INTENT_VALUE_ICC_ABSENT, null);
-            broadcastSimCardStateChanged(slotId, TelephonyManager.SIM_STATE_ABSENT);
-            broadcastSimApplicationStateChanged(slotId, TelephonyManager.SIM_STATE_UNKNOWN);
-            updateSubscriptionCarrierId(slotId, IccCardConstants.INTENT_VALUE_ICC_ABSENT);
-            updateCarrierServices(slotId, IccCardConstants.INTENT_VALUE_ICC_ABSENT);
+        if (!TextUtils.isEmpty(iccId)) {
+            // If iccId is new, add a subscription record in the db.
+            String strippedIccId = IccUtils.stripTrailingFs(iccId);
+            if (SubscriptionController.getInstance().getSubInfoForIccId(strippedIccId) == null) {
+                SubscriptionController.getInstance().insertEmptySubInfoRecord(
+                        strippedIccId, "CARD", SubscriptionManager.INVALID_PHONE_INDEX,
+                        SubscriptionManager.SUBSCRIPTION_TYPE_LOCAL_SIM);
+            }
         }
     }
 
-    private void handleSimError(int slotId) {
-        if (mIccId[slotId] != null && !mIccId[slotId].equals(ICCID_STRING_FOR_NO_SIM)) {
-            logd("SIM" + (slotId + 1) + " Error ");
+    private void cleanSubscriptionInPhone(int phoneId) {
+        sIccId[phoneId] = ICCID_STRING_FOR_NO_SIM;
+        if (sInactiveIccIds[phoneId] != null) {
+            // When a SIM is unplugged, mark uicc applications enabled. This is to make sure when
+            // user unplugs and re-inserts the SIM card, we re-enable it.
+            logd("cleanSubscriptionInPhone " + phoneId + " inactive iccid "
+                    + sInactiveIccIds[phoneId]);
+            ContentValues value = new ContentValues(1);
+            value.put(SubscriptionManager.UICC_APPLICATIONS_ENABLED, true);
+            sContext.getContentResolver().update(SubscriptionManager.CONTENT_URI, value,
+                    SubscriptionManager.ICC_ID + "=\'" + sInactiveIccIds[phoneId] + "\'", null);
+            sInactiveIccIds[phoneId] = null;
         }
-        mIccId[slotId] = ICCID_STRING_FOR_NO_SIM;
-        updateSubscriptionInfoByIccId(slotId, true /* updateEmbeddedSubs */);
-        broadcastSimStateChanged(slotId, IccCardConstants.INTENT_VALUE_ICC_CARD_IO_ERROR,
-                IccCardConstants.INTENT_VALUE_ICC_CARD_IO_ERROR);
-        broadcastSimCardStateChanged(slotId, TelephonyManager.SIM_STATE_CARD_IO_ERROR);
-        broadcastSimApplicationStateChanged(slotId, TelephonyManager.SIM_STATE_NOT_READY);
-        updateSubscriptionCarrierId(slotId, IccCardConstants.INTENT_VALUE_ICC_CARD_IO_ERROR);
-        updateCarrierServices(slotId, IccCardConstants.INTENT_VALUE_ICC_CARD_IO_ERROR);
+        updateSubscriptionInfoByIccId(phoneId, true /* updateEmbeddedSubs */);
     }
 
-    private synchronized void updateSubscriptionInfoByIccId(int slotIndex,
-            boolean updateEmbeddedSubs) {
-        logd("updateSubscriptionInfoByIccId:+ Start");
-        if (!SubscriptionManager.isValidSlotIndex(slotIndex)) {
-            loge("[updateSubscriptionInfoByIccId]- invalid slotIndex=" + slotIndex);
+    protected void handleSimAbsent(int phoneId) {
+        if (!SubscriptionManager.isValidPhoneId(phoneId)) {
+            logd("handleSimAbsent on invalid phoneId");
             return;
         }
-        logd("updateSubscriptionInfoByIccId: removing subscription info record: slotIndex "
-                + slotIndex);
-        // Clear slotIndex only when sim absent is not enough. It's possible to switch SIM profile
+        if (sIccId[phoneId] != null && !sIccId[phoneId].equals(ICCID_STRING_FOR_NO_SIM)) {
+            logd("SIM" + (phoneId + 1) + " hot plug out");
+        }
+        cleanSubscriptionInPhone(phoneId);
+
+        broadcastSimStateChanged(phoneId, IccCardConstants.INTENT_VALUE_ICC_ABSENT, null);
+        broadcastSimCardStateChanged(phoneId, TelephonyManager.SIM_STATE_ABSENT);
+        broadcastSimApplicationStateChanged(phoneId, TelephonyManager.SIM_STATE_UNKNOWN);
+        updateSubscriptionCarrierId(phoneId, IccCardConstants.INTENT_VALUE_ICC_ABSENT);
+        updateCarrierServices(phoneId, IccCardConstants.INTENT_VALUE_ICC_ABSENT);
+    }
+
+    protected void handleSimError(int phoneId) {
+        if (sIccId[phoneId] != null && !sIccId[phoneId].equals(ICCID_STRING_FOR_NO_SIM)) {
+            logd("SIM" + (phoneId + 1) + " Error ");
+        }
+        sIccId[phoneId] = ICCID_STRING_FOR_NO_SIM;
+        updateSubscriptionInfoByIccId(phoneId, true /* updateEmbeddedSubs */);
+        broadcastSimStateChanged(phoneId, IccCardConstants.INTENT_VALUE_ICC_CARD_IO_ERROR,
+                IccCardConstants.INTENT_VALUE_ICC_CARD_IO_ERROR);
+        broadcastSimCardStateChanged(phoneId, TelephonyManager.SIM_STATE_CARD_IO_ERROR);
+        broadcastSimApplicationStateChanged(phoneId, TelephonyManager.SIM_STATE_NOT_READY);
+        updateSubscriptionCarrierId(phoneId, IccCardConstants.INTENT_VALUE_ICC_CARD_IO_ERROR);
+        updateCarrierServices(phoneId, IccCardConstants.INTENT_VALUE_ICC_CARD_IO_ERROR);
+    }
+
+    protected synchronized void updateSubscriptionInfoByIccId(int phoneId,
+            boolean updateEmbeddedSubs) {
+        logd("updateSubscriptionInfoByIccId:+ Start - phoneId: " + phoneId);
+        if (!SubscriptionManager.isValidPhoneId(phoneId)) {
+            loge("[updateSubscriptionInfoByIccId]- invalid phoneId=" + phoneId);
+            return;
+        }
+        logd("updateSubscriptionInfoByIccId: removing subscription info record: phoneId "
+                + phoneId);
+        // Clear phoneId only when sim absent is not enough. It's possible to switch SIM profile
         // within the same slot. Need to clear the slot index of the previous sub. Thus always clear
         // for the changing slot first.
-        SubscriptionController.getInstance().clearSubInfoRecord(slotIndex);
+        SubscriptionController.getInstance().clearSubInfoRecord(phoneId);
 
         // If SIM is not absent, insert new record or update existing record.
-        if (!ICCID_STRING_FOR_NO_SIM.equals(mIccId[slotIndex])) {
-           logd("updateSubscriptionInfoByIccId: adding subscription info record: iccid: "
-                    + mIccId[slotIndex] + "slot: " + slotIndex);
-           mSubscriptionManager.addSubscriptionInfoRecord(mIccId[slotIndex], slotIndex);
+        if (!ICCID_STRING_FOR_NO_SIM.equals(sIccId[phoneId])) {
+            logd("updateSubscriptionInfoByIccId: adding subscription info record: iccid: "
+                    + sIccId[phoneId] + ", phoneId:" + phoneId);
+            mSubscriptionManager.addSubscriptionInfoRecord(sIccId[phoneId], phoneId);
         }
 
         List<SubscriptionInfo> subInfos = SubscriptionController.getInstance()
-                .getSubInfoUsingSlotIndexPrivileged(slotIndex);
+                .getSubInfoUsingSlotIndexPrivileged(phoneId);
         if (subInfos != null) {
             boolean changed = false;
             for (int i = 0; i < subInfos.size(); i++) {
@@ -629,8 +713,8 @@
 
                 if (!TextUtils.equals(msisdn, temp.getNumber())) {
                     value.put(SubscriptionManager.NUMBER, msisdn);
-                    mContext.getContentResolver().update(SubscriptionManager.getUriForSubscriptionId(
-                            temp.getSubscriptionId()), value, null, null);
+                    sContext.getContentResolver().update(SubscriptionManager
+                            .getUriForSubscriptionId(temp.getSubscriptionId()), value, null, null);
                     changed = true;
                 }
             }
@@ -793,12 +877,12 @@
         // returned by the eUICC controller).
         List<SubscriptionInfo> existingSubscriptions = SubscriptionController.getInstance()
                 .getSubscriptionInfoListForEmbeddedSubscriptionUpdate(embeddedIccids, isRemovable);
-        ContentResolver contentResolver = mContext.getContentResolver();
+        ContentResolver contentResolver = sContext.getContentResolver();
         for (EuiccProfileInfo embeddedProfile : embeddedProfiles) {
             int index =
                     findSubscriptionInfoForIccid(existingSubscriptions, embeddedProfile.getIccid());
             int prevCarrierId = TelephonyManager.UNKNOWN_CARRIER_ID;
-            int nameSource = SubscriptionManager.NAME_SOURCE_DEFAULT_SOURCE;
+            int nameSource = SubscriptionManager.NAME_SOURCE_CARRIER_ID;
             if (index < 0) {
                 // No existing entry for this ICCID; create an empty one.
                 SubscriptionController.getInstance().insertEmptySubInfoRecord(
@@ -841,7 +925,7 @@
                 // is no valid carrier id present.
                 if (prevCarrierId == TelephonyManager.UNKNOWN_CARRIER_ID) {
                     values.put(SubscriptionManager.CARRIER_ID,
-                            CarrierResolver.getCarrierIdFromIdentifier(mContext, cid));
+                            CarrierResolver.getCarrierIdFromIdentifier(sContext, cid));
                 }
                 String mcc = cid.getMcc();
                 String mnc = cid.getMnc();
@@ -853,10 +937,10 @@
             // If cardId = unsupported or unitialized, we have no reason to update DB.
             // Additionally, if the device does not support cardId for default eUICC, the CARD_ID
             // field should not contain the EID
-            if (cardId >= 0 && UiccController.getInstance().getCardIdForDefaultEuicc()
+            UiccController uiccController = UiccController.getInstance();
+            if (cardId >= 0 && uiccController.getCardIdForDefaultEuicc()
                     != TelephonyManager.UNSUPPORTED_CARD_ID) {
-                values.put(SubscriptionManager.CARD_ID,
-                        mEuiccManager.createForCardId(cardId).getEid());
+                values.put(SubscriptionManager.CARD_ID, uiccController.convertToCardString(cardId));
             }
             hasChanges = true;
             contentResolver.update(SubscriptionManager.CONTENT_URI, values,
@@ -911,14 +995,14 @@
 
     private String getDefaultCarrierServicePackageName() {
         CarrierConfigManager configManager =
-                (CarrierConfigManager) mContext.getSystemService(Context.CARRIER_CONFIG_SERVICE);
+                (CarrierConfigManager) sContext.getSystemService(Context.CARRIER_CONFIG_SERVICE);
         return configManager.getDefaultCarrierServicePackageName();
     }
 
     private boolean isCarrierServicePackage(int phoneId, String pkgName) {
         if (pkgName.equals(getDefaultCarrierServicePackageName())) return false;
 
-        List<String> carrierPackageNames = TelephonyManager.from(mContext)
+        List<String> carrierPackageNames = TelephonyManager.from(sContext)
                 .getCarrierPackageNamesForIntentAndPhone(
                         new Intent(CarrierService.CARRIER_SERVICE_INTERFACE), phoneId);
         if (DBG) logd("Carrier Packages For Subscription = " + carrierPackageNames);
@@ -1011,7 +1095,7 @@
                 }
             }
         }
-        if (cv.size() > 0 && mContext.getContentResolver().update(SubscriptionManager
+        if (cv.size() > 0 && sContext.getContentResolver().update(SubscriptionManager
                     .getUriForSubscriptionId(currentSubId), cv, null, null) > 0) {
             sc.refreshCachedActiveSubscriptionInfoList();
             sc.notifySubscriptionInfoChanged();
@@ -1030,7 +1114,7 @@
 
     private boolean isNewSim(String iccId, String decIccId, String[] oldIccId) {
         boolean newSim = true;
-        for(int i = 0; i < PROJECT_SIM_NUM; i++) {
+        for (int i = 0; i < TelephonyManager.getDefault().getPhoneCount(); i++) {
             if(iccId.equals(oldIccId[i])) {
                 newSim = false;
                 break;
@@ -1045,7 +1129,7 @@
     }
 
     @UnsupportedAppUsage
-    private void broadcastSimStateChanged(int slotId, String state, String reason) {
+    protected void broadcastSimStateChanged(int phoneId, String state, String reason) {
         Intent i = new Intent(TelephonyIntents.ACTION_SIM_STATE_CHANGED);
         // TODO - we'd like this intent to have a single snapshot of all sim state,
         // but until then this should not use REPLACE_PENDING or we may lose
@@ -1056,18 +1140,17 @@
         i.putExtra(PhoneConstants.PHONE_NAME_KEY, "Phone");
         i.putExtra(IccCardConstants.INTENT_KEY_ICC_STATE, state);
         i.putExtra(IccCardConstants.INTENT_KEY_LOCKED_REASON, reason);
-        SubscriptionManager.putPhoneIdAndSubIdExtra(i, slotId);
+        SubscriptionManager.putPhoneIdAndSubIdExtra(i, phoneId);
         logd("Broadcasting intent ACTION_SIM_STATE_CHANGED " + state + " reason " + reason +
-                " for phone: " + slotId);
-        IntentBroadcaster.getInstance().broadcastStickyIntent(i, slotId);
+                " for phone: " + phoneId);
+        IntentBroadcaster.getInstance().broadcastStickyIntent(sContext, i, phoneId);
     }
 
-    private void broadcastSimCardStateChanged(int phoneId, int state) {
+    protected void broadcastSimCardStateChanged(int phoneId, int state) {
         if (state != sSimCardState[phoneId]) {
             sSimCardState[phoneId] = state;
             Intent i = new Intent(TelephonyManager.ACTION_SIM_CARD_STATE_CHANGED);
             i.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
-            i.addFlags(Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
             i.putExtra(TelephonyManager.EXTRA_SIM_STATE, state);
             SubscriptionManager.putPhoneIdAndSubIdExtra(i, phoneId);
             // TODO(b/130664115) we manually populate this intent with the slotId. In the future we
@@ -1076,12 +1159,12 @@
             i.putExtra(PhoneConstants.SLOT_KEY, slotId);
             logd("Broadcasting intent ACTION_SIM_CARD_STATE_CHANGED " + simStateString(state)
                     + " for phone: " + phoneId + " slot: " + slotId);
-            mContext.sendBroadcast(i, Manifest.permission.READ_PRIVILEGED_PHONE_STATE);
+            sContext.sendBroadcast(i, Manifest.permission.READ_PRIVILEGED_PHONE_STATE);
             TelephonyMetrics.getInstance().updateSimState(phoneId, state);
         }
     }
 
-    private void broadcastSimApplicationStateChanged(int phoneId, int state) {
+    protected void broadcastSimApplicationStateChanged(int phoneId, int state) {
         // Broadcast if the state has changed, except if old state was UNKNOWN and new is NOT_READY,
         // because that's the initial state and a broadcast should be sent only on a transition
         // after SIM is PRESENT. The only exception is eSIM boot profile, where NOT_READY is the
@@ -1089,12 +1172,11 @@
         boolean isUnknownToNotReady =
                 (sSimApplicationState[phoneId] == TelephonyManager.SIM_STATE_UNKNOWN
                         && state == TelephonyManager.SIM_STATE_NOT_READY);
-        IccCard iccCard = mPhone[phoneId].getIccCard();
+        IccCard iccCard = PhoneFactory.getPhone(phoneId).getIccCard();
         boolean emptyProfile = iccCard != null && iccCard.isEmptyProfile();
         if (state != sSimApplicationState[phoneId] && (!isUnknownToNotReady || emptyProfile)) {
             sSimApplicationState[phoneId] = state;
             Intent i = new Intent(TelephonyManager.ACTION_SIM_APPLICATION_STATE_CHANGED);
-            i.addFlags(Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
             i.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
             i.putExtra(TelephonyManager.EXTRA_SIM_STATE, state);
             SubscriptionManager.putPhoneIdAndSubIdExtra(i, phoneId);
@@ -1104,12 +1186,18 @@
             i.putExtra(PhoneConstants.SLOT_KEY, slotId);
             logd("Broadcasting intent ACTION_SIM_APPLICATION_STATE_CHANGED " + simStateString(state)
                     + " for phone: " + phoneId + " slot: " + slotId);
-            mContext.sendBroadcast(i, Manifest.permission.READ_PRIVILEGED_PHONE_STATE);
+            sContext.sendBroadcast(i, Manifest.permission.READ_PRIVILEGED_PHONE_STATE);
             TelephonyMetrics.getInstance().updateSimState(phoneId, state);
         }
     }
 
-    private static String simStateString(int state) {
+    /**
+     * Convert SIM state into string
+     *
+     * @param state SIM state
+     * @return SIM state in string format
+     */
+    public static String simStateString(@SimState int state) {
         switch (state) {
             case TelephonyManager.SIM_STATE_UNKNOWN:
                 return "UNKNOWN";
diff --git a/src/java/com/android/internal/telephony/SubscriptionMonitor.java b/src/java/com/android/internal/telephony/SubscriptionMonitor.java
deleted file mode 100644
index 8ccec6f..0000000
--- a/src/java/com/android/internal/telephony/SubscriptionMonitor.java
+++ /dev/null
@@ -1,239 +0,0 @@
-/*
-* Copyright (C) 2015 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;
-
-import static android.telephony.SubscriptionManager.INVALID_PHONE_INDEX;
-import static android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID;
-
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.os.Handler;
-import android.os.Registrant;
-import android.os.RegistrantList;
-import android.os.RemoteException;
-import android.os.ServiceManager;
-import android.telephony.Rlog;
-import android.telephony.SubscriptionManager;
-import android.util.LocalLog;
-
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.telephony.ISub;
-import com.android.internal.telephony.IOnSubscriptionsChangedListener;
-import com.android.internal.telephony.ITelephonyRegistry;
-import com.android.internal.telephony.PhoneConstants;
-
-import java.io.FileDescriptor;
-import java.io.PrintWriter;
-import java.lang.IllegalArgumentException;
-
-/**
- * Utility singleton to monitor subscription changes and help people act on them.
- * Uses Registrant model to post messages to handlers.
- *
- */
-public class SubscriptionMonitor {
-
-    private final RegistrantList mSubscriptionsChangedRegistrants[];
-    private final RegistrantList mDefaultDataSubChangedRegistrants[];
-
-    private final SubscriptionController mSubscriptionController;
-    private final Context mContext;
-
-    private final int mPhoneSubId[];
-    private int mDefaultDataSubId;
-    private int mDefaultDataPhoneId;
-
-    private final Object mLock = new Object();
-
-    private final static boolean VDBG = true;
-    private final static String LOG_TAG = "SubscriptionMonitor";
-
-    private final static int MAX_LOGLINES = 100;
-    private final LocalLog mLocalLog = new LocalLog(MAX_LOGLINES);
-
-    public SubscriptionMonitor(ITelephonyRegistry tr, Context context,
-            SubscriptionController subscriptionController, int numPhones) {
-        try {
-            tr.addOnSubscriptionsChangedListener(context.getOpPackageName(),
-                    mSubscriptionsChangedListener);
-        } catch (RemoteException e) {
-        }
-
-        mSubscriptionController = subscriptionController;
-        mContext = context;
-
-        mSubscriptionsChangedRegistrants = new RegistrantList[numPhones];
-        mDefaultDataSubChangedRegistrants = new RegistrantList[numPhones];
-        mPhoneSubId = new int[numPhones];
-
-        mDefaultDataSubId = mSubscriptionController.getDefaultDataSubId();
-        mDefaultDataPhoneId = mSubscriptionController.getPhoneId(mDefaultDataSubId);
-
-        for (int phoneId = 0; phoneId < numPhones; phoneId++) {
-            mSubscriptionsChangedRegistrants[phoneId] = new RegistrantList();
-            mDefaultDataSubChangedRegistrants[phoneId] = new RegistrantList();
-            mPhoneSubId[phoneId] = mSubscriptionController.getSubIdUsingPhoneId(phoneId);
-        }
-
-        mContext.registerReceiver(mDefaultDataSubscriptionChangedReceiver,
-                new IntentFilter(TelephonyIntents.ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED));
-    }
-
-    @VisibleForTesting
-    public SubscriptionMonitor() {
-        mSubscriptionsChangedRegistrants = null;
-        mDefaultDataSubChangedRegistrants = null;
-        mSubscriptionController = null;
-        mContext = null;
-        mPhoneSubId = null;
-    }
-
-    private final IOnSubscriptionsChangedListener mSubscriptionsChangedListener =
-            new IOnSubscriptionsChangedListener.Stub() {
-        @Override
-        public void onSubscriptionsChanged() {
-            synchronized (mLock) {
-                int newDefaultDataPhoneId = INVALID_PHONE_INDEX;
-                for (int phoneId = 0; phoneId < mPhoneSubId.length; phoneId++) {
-                    final int newSubId = mSubscriptionController.getSubIdUsingPhoneId(phoneId);
-                    final int oldSubId = mPhoneSubId[phoneId];
-                    if (oldSubId != newSubId) {
-                        log("Phone[" + phoneId + "] subId changed " + oldSubId + "->" +
-                                newSubId + ", " +
-                                mSubscriptionsChangedRegistrants[phoneId].size() + " registrants");
-                        mPhoneSubId[phoneId] = newSubId;
-                        mSubscriptionsChangedRegistrants[phoneId].notifyRegistrants();
-
-                        // if the default isn't set, just move along..
-                        if (mDefaultDataSubId == INVALID_SUBSCRIPTION_ID) continue;
-
-                        // check if this affects default data
-                        if (newSubId == mDefaultDataSubId || oldSubId == mDefaultDataSubId) {
-                            log("mDefaultDataSubId = " + mDefaultDataSubId + ", " +
-                                    mDefaultDataSubChangedRegistrants[phoneId].size() +
-                                    " registrants");
-                            mDefaultDataSubChangedRegistrants[phoneId].notifyRegistrants();
-                        }
-                    }
-                    if (newSubId == mDefaultDataSubId) {
-                        newDefaultDataPhoneId = phoneId;
-                    }
-                }
-                mDefaultDataPhoneId = newDefaultDataPhoneId;
-            }
-        }
-    };
-
-    private final BroadcastReceiver mDefaultDataSubscriptionChangedReceiver =
-            new BroadcastReceiver() {
-        @Override
-        public void onReceive(Context context, Intent intent) {
-            final int newDefaultDataSubId = mSubscriptionController.getDefaultDataSubId();
-            synchronized (mLock) {
-                if (mDefaultDataSubId != newDefaultDataSubId) {
-                    log("Default changed " + mDefaultDataSubId + "->" + newDefaultDataSubId);
-                    final int oldDefaultDataSubId = mDefaultDataSubId;
-                    final int oldDefaultDataPhoneId = mDefaultDataPhoneId;
-                    mDefaultDataSubId = newDefaultDataSubId;
-
-                    int newDefaultDataPhoneId =
-                            mSubscriptionController.getPhoneId(INVALID_SUBSCRIPTION_ID);
-                    if (newDefaultDataSubId != INVALID_SUBSCRIPTION_ID) {
-                        for (int phoneId = 0; phoneId < mPhoneSubId.length; phoneId++) {
-                            if (mPhoneSubId[phoneId] == newDefaultDataSubId) {
-                                newDefaultDataPhoneId = phoneId;
-                                if (VDBG) log("newDefaultDataPhoneId=" + newDefaultDataPhoneId);
-                                break;
-                            }
-                        }
-                    }
-                    if (newDefaultDataPhoneId != oldDefaultDataPhoneId) {
-                        log("Default phoneId changed " + oldDefaultDataPhoneId + "->" +
-                                newDefaultDataPhoneId + ", " +
-                                (invalidPhoneId(oldDefaultDataPhoneId) ?
-                                 0 :
-                                 mDefaultDataSubChangedRegistrants[oldDefaultDataPhoneId].size()) +
-                                "," + (invalidPhoneId(newDefaultDataPhoneId) ?
-                                  0 :
-                                  mDefaultDataSubChangedRegistrants[newDefaultDataPhoneId].size()) +
-                                " registrants");
-                        mDefaultDataPhoneId = newDefaultDataPhoneId;
-                        if (!invalidPhoneId(oldDefaultDataPhoneId)) {
-                            mDefaultDataSubChangedRegistrants[oldDefaultDataPhoneId].
-                                    notifyRegistrants();
-                        }
-                        if (!invalidPhoneId(newDefaultDataPhoneId)) {
-                            mDefaultDataSubChangedRegistrants[newDefaultDataPhoneId].
-                                    notifyRegistrants();
-                        }
-                    }
-                }
-            }
-        }
-    };
-
-    public void registerForSubscriptionChanged(int phoneId, Handler h, int what, Object o) {
-        if (invalidPhoneId(phoneId)) {
-            throw new IllegalArgumentException("Invalid PhoneId");
-        }
-        Registrant r = new Registrant(h, what, o);
-        mSubscriptionsChangedRegistrants[phoneId].add(r);
-        r.notifyRegistrant();
-    }
-
-    public void unregisterForSubscriptionChanged(int phoneId, Handler h) {
-        if (invalidPhoneId(phoneId)) {
-            throw new IllegalArgumentException("Invalid PhoneId");
-        }
-        mSubscriptionsChangedRegistrants[phoneId].remove(h);
-    }
-
-    public void registerForDefaultDataSubscriptionChanged(int phoneId, Handler h, int what,
-            Object o) {
-        if (invalidPhoneId(phoneId)) {
-            throw new IllegalArgumentException("Invalid PhoneId");
-        }
-        Registrant r = new Registrant(h, what, o);
-        mDefaultDataSubChangedRegistrants[phoneId].add(r);
-        r.notifyRegistrant();
-    }
-
-    public void unregisterForDefaultDataSubscriptionChanged(int phoneId, Handler h) {
-        if (invalidPhoneId(phoneId)) {
-            throw new IllegalArgumentException("Invalid PhoneId");
-        }
-        mDefaultDataSubChangedRegistrants[phoneId].remove(h);
-    }
-
-    private boolean invalidPhoneId(int phoneId) {
-        if (phoneId >= 0 && phoneId < mPhoneSubId.length) return false;
-        return true;
-    }
-
-    private void log(String s) {
-        Rlog.d(LOG_TAG, s);
-        mLocalLog.log(s);
-    }
-
-    public void dump(FileDescriptor fd, PrintWriter printWriter, String[] args) {
-        synchronized (mLock) {
-            mLocalLog.dump(fd, printWriter, args);
-        }
-    }
-}
diff --git a/src/java/com/android/internal/telephony/TelephonyCapabilities.java b/src/java/com/android/internal/telephony/TelephonyCapabilities.java
index 70f15dc..f2f7ddc 100644
--- a/src/java/com/android/internal/telephony/TelephonyCapabilities.java
+++ b/src/java/com/android/internal/telephony/TelephonyCapabilities.java
@@ -16,10 +16,9 @@
 
 package com.android.internal.telephony;
 
-import android.annotation.UnsupportedAppUsage;
-import android.telephony.Rlog;
+import android.compat.annotation.UnsupportedAppUsage;
 
-import com.android.internal.telephony.Phone;
+import com.android.telephony.Rlog;
 
 /**
  * Utilities that check if the phone supports specified capabilities.
diff --git a/src/java/com/android/internal/telephony/TelephonyComponentFactory.java b/src/java/com/android/internal/telephony/TelephonyComponentFactory.java
index a461324..9a6e244 100644
--- a/src/java/com/android/internal/telephony/TelephonyComponentFactory.java
+++ b/src/java/com/android/internal/telephony/TelephonyComponentFactory.java
@@ -22,15 +22,12 @@
 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.system.ErrnoException;
 import android.system.Os;
 import android.system.OsConstants;
 import android.system.StructStatVfs;
 import android.telephony.AccessNetworkConstants.TransportType;
-import android.telephony.Rlog;
 import android.text.TextUtils;
 
 import com.android.internal.telephony.cdma.CdmaSubscriptionSourceManager;
@@ -42,9 +39,11 @@
 import com.android.internal.telephony.imsphone.ImsExternalCallTracker;
 import com.android.internal.telephony.imsphone.ImsPhone;
 import com.android.internal.telephony.imsphone.ImsPhoneCallTracker;
+import com.android.internal.telephony.nitz.NitzStateMachineImpl;
 import com.android.internal.telephony.uicc.IccCardStatus;
 import com.android.internal.telephony.uicc.UiccCard;
 import com.android.internal.telephony.uicc.UiccProfile;
+import com.android.telephony.Rlog;
 
 import dalvik.system.PathClassLoader;
 
@@ -59,8 +58,6 @@
 import java.util.function.Consumer;
 import java.util.stream.Collectors;
 
-
-
 /**
  * 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
@@ -292,11 +289,13 @@
         return new EmergencyNumberTracker(phone, ci);
     }
 
+    private static final boolean USE_NEW_NITZ_STATE_MACHINE = true;
+
     /**
      * Returns a new {@link NitzStateMachine} instance.
      */
     public NitzStateMachine makeNitzStateMachine(GsmCdmaPhone phone) {
-        return new NitzStateMachineImpl(phone);
+        return NitzStateMachineImpl.createInstance(phone);
     }
 
     public SimActivationTracker makeSimActivationTracker(Phone phone) {
@@ -346,30 +345,31 @@
     /**
      * Create a tracker for a single-part SMS.
      */
-    public InboundSmsTracker makeInboundSmsTracker(byte[] pdu, long timestamp, int destPort,
-            boolean is3gpp2, boolean is3gpp2WapPdu, String address, String displayAddr,
-            String messageBody, boolean isClass0, int subId) {
-        return new InboundSmsTracker(pdu, timestamp, destPort, is3gpp2, is3gpp2WapPdu, address,
-                displayAddr, messageBody, isClass0, subId);
+    public InboundSmsTracker makeInboundSmsTracker(Context context, byte[] pdu, long timestamp,
+            int destPort, boolean is3gpp2, boolean is3gpp2WapPdu, String address,
+            String displayAddr, String messageBody, boolean isClass0, int subId) {
+        return new InboundSmsTracker(context, pdu, timestamp, destPort, is3gpp2, is3gpp2WapPdu,
+                address, displayAddr, messageBody, isClass0, subId);
     }
 
     /**
      * Create a tracker for a multi-part SMS.
      */
-    public InboundSmsTracker makeInboundSmsTracker(byte[] pdu, long timestamp, int destPort,
-            boolean is3gpp2, String address, String displayAddr, int referenceNumber,
+    public InboundSmsTracker makeInboundSmsTracker(Context context, byte[] pdu, long timestamp,
+            int destPort, boolean is3gpp2, String address, String displayAddr, int referenceNumber,
             int sequenceNumber, int messageCount, boolean is3gpp2WapPdu, String messageBody,
             boolean isClass0, int subId) {
-        return new InboundSmsTracker(pdu, timestamp, destPort, is3gpp2, address, displayAddr,
-                referenceNumber, sequenceNumber, messageCount, is3gpp2WapPdu, messageBody,
-                isClass0, subId);
+        return new InboundSmsTracker(context, pdu, timestamp, destPort, is3gpp2, address,
+                displayAddr, referenceNumber, sequenceNumber, messageCount, is3gpp2WapPdu,
+                messageBody, isClass0, subId);
     }
 
     /**
      * Create a tracker from a row of raw table
      */
-    public InboundSmsTracker makeInboundSmsTracker(Cursor cursor, boolean isCurrentFormat3gpp2) {
-        return new InboundSmsTracker(cursor, isCurrentFormat3gpp2);
+    public InboundSmsTracker makeInboundSmsTracker(Context context, Cursor cursor,
+            boolean isCurrentFormat3gpp2) {
+        return new InboundSmsTracker(context, cursor, isCurrentFormat3gpp2);
     }
 
     public ImsPhoneCallTracker makeImsPhoneCallTracker(ImsPhone imsPhone) {
@@ -402,11 +402,6 @@
         return CdmaSubscriptionSourceManager.getInstance(context, ci, h, what, obj);
     }
 
-    public IDeviceIdleController getIDeviceIdleController() {
-        return IDeviceIdleController.Stub.asInterface(
-                ServiceManager.getService(Context.DEVICE_IDLE_CONTROLLER));
-    }
-
     public LocaleTracker makeLocaleTracker(Phone phone, NitzStateMachine nitzStateMachine,
                                            Looper looper) {
         return new LocaleTracker(phone, nitzStateMachine, looper);
@@ -415,4 +410,37 @@
     public DataEnabledSettings makeDataEnabledSettings(Phone phone) {
         return new DataEnabledSettings(phone);
     }
+
+    public Phone makePhone(Context context, CommandsInterface ci, PhoneNotifier notifier,
+            int phoneId, int precisePhoneType,
+            TelephonyComponentFactory telephonyComponentFactory) {
+        return new GsmCdmaPhone(context, ci, notifier, phoneId, precisePhoneType,
+                telephonyComponentFactory);
+    }
+
+    public SubscriptionController initSubscriptionController(Context c) {
+        return SubscriptionController.init(c);
+    }
+
+    public PhoneSwitcher makePhoneSwitcher(int maxDataAttachModemCount, Context context,
+            Looper looper) {
+        return PhoneSwitcher.make(maxDataAttachModemCount, context, looper);
+    }
+
+    /**
+     * Create a new DisplayInfoController.
+     */
+    public DisplayInfoController makeDisplayInfoController(Phone phone) {
+        return new DisplayInfoController(phone);
+    }
+
+    public MultiSimSettingController initMultiSimSettingController(Context c,
+            SubscriptionController sc) {
+        return MultiSimSettingController.init(c, sc);
+    }
+
+    public SubscriptionInfoUpdater makeSubscriptionInfoUpdater(Looper looper, Context context,
+            CommandsInterface[] ci) {
+        return new SubscriptionInfoUpdater(looper, context, ci);
+    }
 }
diff --git a/src/java/com/android/internal/telephony/TelephonyDevController.java b/src/java/com/android/internal/telephony/TelephonyDevController.java
index 3cda417..f97386d 100644
--- a/src/java/com/android/internal/telephony/TelephonyDevController.java
+++ b/src/java/com/android/internal/telephony/TelephonyDevController.java
@@ -17,20 +17,14 @@
 package com.android.internal.telephony;
 
 import android.content.res.Resources;
-import com.android.internal.telephony.*;
-import android.telephony.TelephonyManager;
-
 import android.os.AsyncResult;
-import android.telephony.Rlog;
-import java.util.BitSet;
-import java.util.List;
-import java.util.ArrayList;
-import android.text.TextUtils;
 import android.os.Handler;
 import android.os.Message;
-import android.os.Registrant;
-import android.os.RegistrantList;
-import android.telephony.ServiceState;
+
+import com.android.telephony.Rlog;
+
+import java.util.ArrayList;
+import java.util.List;
 
 /**
  * TelephonyDevController - provides a unified view of the
@@ -59,6 +53,7 @@
         Rlog.e(LOG_TAG, s);
     }
 
+    /** Create TelephonyDevController and set as singleton instance. */
     public static TelephonyDevController create() {
         synchronized (mLock) {
             if (sTelephonyDevController != null) {
@@ -69,6 +64,7 @@
         }
     }
 
+    /** Get TelephonyDevController singleton. */
     public static TelephonyDevController getInstance() {
         synchronized (mLock) {
             if (sTelephonyDevController == null) {
diff --git a/src/java/com/android/internal/telephony/TelephonyTester.java b/src/java/com/android/internal/telephony/TelephonyTester.java
index df3bfb4..e1ada1d 100644
--- a/src/java/com/android/internal/telephony/TelephonyTester.java
+++ b/src/java/com/android/internal/telephony/TelephonyTester.java
@@ -22,11 +22,11 @@
 import android.content.IntentFilter;
 import android.net.Uri;
 import android.os.BadParcelableException;
-import android.os.Build;
 import android.os.Bundle;
 import android.telephony.AccessNetworkConstants;
-import android.telephony.Rlog;
+import android.telephony.NetworkRegistrationInfo;
 import android.telephony.ServiceState;
+import android.telephony.TelephonyManager;
 import android.telephony.ims.ImsCallProfile;
 import android.telephony.ims.ImsConferenceState;
 import android.telephony.ims.ImsExternalCallState;
@@ -37,7 +37,10 @@
 import com.android.internal.telephony.imsphone.ImsExternalCallTracker;
 import com.android.internal.telephony.imsphone.ImsPhone;
 import com.android.internal.telephony.imsphone.ImsPhoneCall;
+import com.android.internal.telephony.imsphone.ImsPhoneCallTracker;
 import com.android.internal.telephony.test.TestConferenceEventPackageParser;
+import com.android.internal.telephony.util.TelephonyUtils;
+import com.android.telephony.Rlog;
 
 import java.io.File;
 import java.io.FileInputStream;
@@ -74,6 +77,15 @@
             "com.android.internal.telephony.TestDialogEventPackage";
 
     private static final String EXTRA_FILENAME = "filename";
+    /**
+     * Used to inject the conference event package by bypassing the ImsCall and doing the
+     * injection via ImsPhoneCallTracker.  This is useful in scenarios where the
+     * adb shell cmd phone ims conference-event-package disable
+     * command is used to disable network CEP data and it is desired to still inject CEP data.
+     * Where the network CEP data is not explicitly disabled using the command above, it is not
+     * necessary to bypass the ImsCall.
+     */
+    private static final String EXTRA_BYPASS_IMSCALL = "bypassImsCall";
     private static final String EXTRA_STARTPACKAGE = "startPackage";
     private static final String EXTRA_SENDPACKAGE = "sendPackage";
     private static final String EXTRA_DIALOGID = "dialogId";
@@ -129,7 +141,10 @@
     private static final String EXTRA_DATA_REG_STATE = "data_reg_state";
     private static final String EXTRA_VOICE_ROAMING_TYPE = "voice_roaming_type";
     private static final String EXTRA_DATA_ROAMING_TYPE = "data_roaming_type";
+    private static final String EXTRA_NR_FREQUENCY_RANGE = "nr_frequency_range";
+    private static final String EXTRA_NR_STATE = "nr_state";
     private static final String EXTRA_OPERATOR = "operator";
+    private static final String EXTRA_OPERATOR_RAW = "operator_raw";
 
     private static final String ACTION_RESET = "reset";
 
@@ -158,7 +173,8 @@
                 } else if (action.equals(ACTION_TEST_CONFERENCE_EVENT_PACKAGE)) {
                     log("inject simulated conference event package");
                     handleTestConferenceEventPackage(context,
-                            intent.getStringExtra(EXTRA_FILENAME));
+                            intent.getStringExtra(EXTRA_FILENAME),
+                            intent.getBooleanExtra(EXTRA_BYPASS_IMSCALL, false));
                 } else if (action.equals(ACTION_TEST_DIALOG_EVENT_PACKAGE)) {
                     log("handle test dialog event package intent");
                     handleTestDialogEventPackageIntent(intent);
@@ -196,7 +212,7 @@
     TelephonyTester(Phone phone) {
         mPhone = phone;
 
-        if (Build.IS_DEBUGGABLE) {
+        if (TelephonyUtils.IS_DEBUGGABLE) {
             IntentFilter filter = new IntentFilter();
 
             filter.addAction(mPhone.getActionDetached());
@@ -214,17 +230,19 @@
                 filter.addAction(ACTION_TEST_SUPP_SRVC_NOTIFICATION);
                 filter.addAction(ACTION_TEST_IMS_E_CALL);
                 mImsExternalCallStates = new ArrayList<ImsExternalCallState>();
-            } else {
-                filter.addAction(ACTION_TEST_SERVICE_STATE);
-                log("register for intent action=" + ACTION_TEST_SERVICE_STATE);
             }
+
+            filter.addAction(ACTION_TEST_SERVICE_STATE);
+            log("register for intent action=" + ACTION_TEST_SERVICE_STATE);
+
             filter.addAction(ACTION_TEST_CHANGE_NUMBER);
+            log("register for intent action=" + ACTION_TEST_CHANGE_NUMBER);
             phone.getContext().registerReceiver(mIntentReceiver, filter, null, mPhone.getHandler());
         }
     }
 
     void dispose() {
-        if (Build.IS_DEBUGGABLE) {
+        if (TelephonyUtils.IS_DEBUGGABLE) {
             mPhone.getContext().unregisterReceiver(mIntentReceiver);
         }
     }
@@ -260,7 +278,7 @@
         }
 
         imsCall.getImsCallSessionListenerProxy().callSessionHandoverFailed(imsCall.getCallSession(),
-                ServiceState.RIL_RADIO_TECHNOLOGY_LTE, ServiceState.RIL_RADIO_TECHNOLOGY_IWLAN,
+                TelephonyManager.NETWORK_TYPE_LTE, TelephonyManager.NETWORK_TYPE_IWLAN,
                 new ImsReasonInfo());
     }
 
@@ -271,22 +289,15 @@
      * @param context The context.
      * @param fileName The name of the test conference event package file to read.
      */
-    private void handleTestConferenceEventPackage(Context context, String fileName) {
+    private void handleTestConferenceEventPackage(Context context, String fileName,
+            boolean isBypassingImsCall) {
         // Attempt to get the active IMS call before parsing the test XML file.
         ImsPhone imsPhone = (ImsPhone) mPhone;
         if (imsPhone == null) {
             return;
         }
 
-        ImsPhoneCall imsPhoneCall = imsPhone.getForegroundCall();
-        if (imsPhoneCall == null) {
-            return;
-        }
-
-        ImsCall imsCall = imsPhoneCall.getImsCall();
-        if (imsCall == null) {
-            return;
-        }
+        ImsPhoneCallTracker tracker = (ImsPhoneCallTracker) imsPhone.getCallTracker();
 
         File packageFile = new File(context.getFilesDir(), fileName);
         final FileInputStream is;
@@ -303,7 +314,21 @@
             return;
         }
 
-        imsCall.conferenceStateUpdated(imsConferenceState);
+        if (isBypassingImsCall) {
+            tracker.injectTestConferenceState(imsConferenceState);
+        } else {
+            ImsPhoneCall imsPhoneCall = imsPhone.getForegroundCall();
+            if (imsPhoneCall == null) {
+                return;
+            }
+
+            ImsCall imsCall = imsPhoneCall.getImsCall();
+            if (imsCall == null) {
+                return;
+            }
+
+            imsCall.conferenceStateUpdated(imsConferenceState);
+        }
     }
 
     /**
@@ -364,43 +389,104 @@
             return;
         }
 
-        // TODO: Fix this with modifing NetworkRegistrationInfo inside ServiceState. Do not call
-        // ServiceState's set methods directly.
-        /*if (mServiceStateTestIntent.hasExtra(EXTRA_VOICE_REG_STATE)) {
+        if (mServiceStateTestIntent.hasExtra(EXTRA_VOICE_REG_STATE)) {
             ss.setVoiceRegState(mServiceStateTestIntent.getIntExtra(EXTRA_VOICE_REG_STATE,
                     ServiceState.STATE_OUT_OF_SERVICE));
-            log("Override voice service state with " + ss.getVoiceRegState());
+            log("Override voice service state with " + ss.getState());
         }
         if (mServiceStateTestIntent.hasExtra(EXTRA_DATA_REG_STATE)) {
             ss.setDataRegState(mServiceStateTestIntent.getIntExtra(EXTRA_DATA_REG_STATE,
                     ServiceState.STATE_OUT_OF_SERVICE));
-            log("Override data service state with " + ss.getDataRegState());
-        }
-        if (mServiceStateTestIntent.hasExtra(EXTRA_VOICE_RAT)) {
-            ss.setRilVoiceRadioTechnology(mServiceStateTestIntent.getIntExtra(EXTRA_VOICE_RAT,
-                    ServiceState.RIL_RADIO_TECHNOLOGY_UNKNOWN));
-            log("Override voice rat with " + ss.getRilVoiceRadioTechnology());
-        }
-        if (mServiceStateTestIntent.hasExtra(EXTRA_DATA_RAT)) {
-            ss.setRilDataRadioTechnology(mServiceStateTestIntent.getIntExtra(EXTRA_DATA_RAT,
-                    ServiceState.RIL_RADIO_TECHNOLOGY_UNKNOWN));
-            log("Override data rat with " + ss.getRilDataRadioTechnology());
-        }*/
-        if (mServiceStateTestIntent.hasExtra(EXTRA_VOICE_ROAMING_TYPE)) {
-            ss.setVoiceRoamingType(mServiceStateTestIntent.getIntExtra(EXTRA_VOICE_ROAMING_TYPE,
-                    ServiceState.ROAMING_TYPE_UNKNOWN));
-            log("Override voice roaming type with " + ss.getVoiceRoamingType());
-        }
-        if (mServiceStateTestIntent.hasExtra(EXTRA_DATA_ROAMING_TYPE)) {
-            ss.setDataRoamingType(mServiceStateTestIntent.getIntExtra(EXTRA_DATA_ROAMING_TYPE,
-                    ServiceState.ROAMING_TYPE_UNKNOWN));
-            log("Override data roaming type with " + ss.getDataRoamingType());
+            log("Override data service state with " + ss.getDataRegistrationState());
         }
         if (mServiceStateTestIntent.hasExtra(EXTRA_OPERATOR)) {
             String operator = mServiceStateTestIntent.getStringExtra(EXTRA_OPERATOR);
             ss.setOperatorName(operator, operator, "");
             log("Override operator with " + operator);
         }
+        if (mServiceStateTestIntent.hasExtra(EXTRA_OPERATOR_RAW)) {
+            String operator_raw = mServiceStateTestIntent.getStringExtra(EXTRA_OPERATOR_RAW);
+            ss.setOperatorAlphaLongRaw(operator_raw);
+            ss.setOperatorAlphaShortRaw(operator_raw);
+            log("Override operator_raw with " + operator_raw);
+        }
+        if (mServiceStateTestIntent.hasExtra(EXTRA_NR_FREQUENCY_RANGE)) {
+            ss.setNrFrequencyRange(mServiceStateTestIntent.getIntExtra(EXTRA_NR_FREQUENCY_RANGE,
+                    ServiceState.FREQUENCY_RANGE_UNKNOWN));
+            log("Override NR frequency range with " + ss.getNrFrequencyRange());
+        }
+        if (mServiceStateTestIntent.hasExtra(EXTRA_NR_STATE)) {
+            NetworkRegistrationInfo nri = ss.getNetworkRegistrationInfo(
+                    NetworkRegistrationInfo.DOMAIN_PS, AccessNetworkConstants.TRANSPORT_TYPE_WWAN);
+            if (nri == null) {
+                nri = new NetworkRegistrationInfo.Builder()
+                        .setDomain(NetworkRegistrationInfo.DOMAIN_PS)
+                        .setTransportType(AccessNetworkConstants.TRANSPORT_TYPE_WWAN)
+                        .build();
+            }
+            nri.setNrState(mServiceStateTestIntent.getIntExtra(EXTRA_NR_STATE,
+                    NetworkRegistrationInfo.NR_STATE_NONE));
+            ss.addNetworkRegistrationInfo(nri);
+            log("Override NR state with " + ss.getNrState());
+        }
+        if (mServiceStateTestIntent.hasExtra(EXTRA_VOICE_RAT)) {
+            NetworkRegistrationInfo nri = ss.getNetworkRegistrationInfo(
+                    NetworkRegistrationInfo.DOMAIN_CS, AccessNetworkConstants.TRANSPORT_TYPE_WWAN);
+            if (nri == null) {
+                nri = new NetworkRegistrationInfo.Builder()
+                        .setDomain(NetworkRegistrationInfo.DOMAIN_CS)
+                        .setTransportType(AccessNetworkConstants.TRANSPORT_TYPE_WWAN)
+                        .build();
+            }
+            nri.setAccessNetworkTechnology(ServiceState.rilRadioTechnologyToNetworkType(
+                    mServiceStateTestIntent.getIntExtra(EXTRA_VOICE_RAT,
+                    ServiceState.RIL_RADIO_TECHNOLOGY_UNKNOWN)));
+            ss.addNetworkRegistrationInfo(nri);
+            log("Override voice rat with " + ss.getRilVoiceRadioTechnology());
+        }
+        if (mServiceStateTestIntent.hasExtra(EXTRA_DATA_RAT)) {
+            NetworkRegistrationInfo nri = ss.getNetworkRegistrationInfo(
+                    NetworkRegistrationInfo.DOMAIN_PS, AccessNetworkConstants.TRANSPORT_TYPE_WWAN);
+            if (nri == null) {
+                nri = new NetworkRegistrationInfo.Builder()
+                        .setDomain(NetworkRegistrationInfo.DOMAIN_PS)
+                        .setTransportType(AccessNetworkConstants.TRANSPORT_TYPE_WWAN)
+                        .build();
+            }
+            nri.setAccessNetworkTechnology(ServiceState.rilRadioTechnologyToNetworkType(
+                    mServiceStateTestIntent.getIntExtra(EXTRA_DATA_RAT,
+                    ServiceState.RIL_RADIO_TECHNOLOGY_UNKNOWN)));
+            ss.addNetworkRegistrationInfo(nri);
+            log("Override data rat with " + ss.getRilDataRadioTechnology());
+        }
+        if (mServiceStateTestIntent.hasExtra(EXTRA_VOICE_ROAMING_TYPE)) {
+            NetworkRegistrationInfo nri = ss.getNetworkRegistrationInfo(
+                    NetworkRegistrationInfo.DOMAIN_CS, AccessNetworkConstants.TRANSPORT_TYPE_WWAN);
+            if (nri == null) {
+                nri = new NetworkRegistrationInfo.Builder()
+                        .setDomain(NetworkRegistrationInfo.DOMAIN_CS)
+                        .setTransportType(AccessNetworkConstants.TRANSPORT_TYPE_WWAN)
+                        .build();
+            }
+            nri.setRoamingType(mServiceStateTestIntent.getIntExtra(EXTRA_VOICE_ROAMING_TYPE,
+                    ServiceState.ROAMING_TYPE_UNKNOWN));
+            ss.addNetworkRegistrationInfo(nri);
+            log("Override voice roaming type with " + ss.getVoiceRoamingType());
+        }
+        if (mServiceStateTestIntent.hasExtra(EXTRA_DATA_ROAMING_TYPE)) {
+            NetworkRegistrationInfo nri = ss.getNetworkRegistrationInfo(
+                    NetworkRegistrationInfo.DOMAIN_PS, AccessNetworkConstants.TRANSPORT_TYPE_WWAN);
+            if (nri == null) {
+                nri = new NetworkRegistrationInfo.Builder()
+                        .setDomain(NetworkRegistrationInfo.DOMAIN_PS)
+                        .setTransportType(AccessNetworkConstants.TRANSPORT_TYPE_WWAN)
+                        .build();
+            }
+            nri.setRoamingType(mServiceStateTestIntent.getIntExtra(EXTRA_DATA_ROAMING_TYPE,
+                    ServiceState.ROAMING_TYPE_UNKNOWN));
+            ss.addNetworkRegistrationInfo(nri);
+            log("Override data roaming type with " + ss.getDataRoamingType());
+        }
     }
 
     void testImsECall() {
@@ -455,4 +541,4 @@
             ((ImsPhone) mPhone).notifyPreciseCallStateChanged();
         }
     }
-}
\ No newline at end of file
+}
diff --git a/src/java/com/android/internal/telephony/TimeServiceHelper.java b/src/java/com/android/internal/telephony/TimeServiceHelper.java
deleted file mode 100644
index 861b7bf..0000000
--- a/src/java/com/android/internal/telephony/TimeServiceHelper.java
+++ /dev/null
@@ -1,79 +0,0 @@
-/*
- * Copyright 2017 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;
-
-import android.app.timedetector.TimeDetector;
-import android.os.SystemClock;
-import android.util.TimestampedValue;
-
-/**
- * An interface to various time / time zone detection behaviors that should be centralized into a
- * new service.
- */
-public interface TimeServiceHelper {
-
-    /**
-     * Callback interface for automatic detection enable/disable changes.
-     */
-    interface Listener {
-        /**
-         * Automatic time zone detection has been enabled or disabled.
-         */
-        void onTimeZoneDetectionChange(boolean enabled);
-    }
-
-    /**
-     * Sets a listener that will be called when the automatic time / time zone detection setting
-     * changes.
-     */
-    void setListener(Listener listener);
-
-    /**
-     * Returns the same value as {@link System#currentTimeMillis()}.
-     */
-    long currentTimeMillis();
-
-    /**
-     * Returns the same value as {@link SystemClock#elapsedRealtime()}.
-     */
-    long elapsedRealtime();
-
-    /**
-     * Returns true if the device has an explicit time zone set.
-     */
-    boolean isTimeZoneSettingInitialized();
-
-    /**
-     * Returns true if automatic time zone detection is enabled in settings.
-     */
-    boolean isTimeZoneDetectionEnabled();
-
-    /**
-     * Set the device time zone and send out a sticky broadcast so the system can
-     * determine if the timezone was set by the carrier.
-     *
-     * @param zoneId timezone set by carrier
-     */
-    void setDeviceTimeZone(String zoneId);
-
-    /**
-     * Suggest the time to the {@link TimeDetector}.
-     *
-     * @param signalTimeMillis the signal time as received from the network
-     */
-    void suggestDeviceTime(TimestampedValue<Long> signalTimeMillis);
-}
diff --git a/src/java/com/android/internal/telephony/TimeServiceHelperImpl.java b/src/java/com/android/internal/telephony/TimeServiceHelperImpl.java
deleted file mode 100644
index 5b2d909..0000000
--- a/src/java/com/android/internal/telephony/TimeServiceHelperImpl.java
+++ /dev/null
@@ -1,140 +0,0 @@
-/*
- * Copyright 2017 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;
-
-import android.app.AlarmManager;
-import android.app.timedetector.TimeDetector;
-import android.app.timedetector.TimeSignal;
-import android.content.ContentResolver;
-import android.content.Context;
-import android.content.Intent;
-import android.database.ContentObserver;
-import android.os.Handler;
-import android.os.SystemClock;
-import android.os.SystemProperties;
-import android.os.UserHandle;
-import android.provider.Settings;
-import android.util.TimestampedValue;
-
-/**
- * An interface to various time / time zone detection behaviors that should be centralized into a
- * new service.
- */
-public class TimeServiceHelperImpl implements TimeServiceHelper {
-
-    private static final String TIMEZONE_PROPERTY = "persist.sys.timezone";
-
-    private final Context mContext;
-    private final ContentResolver mCr;
-    private final TimeDetector mTimeDetector;
-
-    private Listener mListener;
-
-    /** Creates a TimeServiceHelper */
-    public TimeServiceHelperImpl(Context context) {
-        mContext = context;
-        mCr = context.getContentResolver();
-        mTimeDetector = context.getSystemService(TimeDetector.class);
-    }
-
-    @Override
-    public void setListener(Listener listener) {
-        if (listener == null) {
-            throw new NullPointerException("listener==null");
-        }
-        if (mListener != null) {
-            throw new IllegalStateException("listener already set");
-        }
-        this.mListener = listener;
-        mCr.registerContentObserver(
-                Settings.Global.getUriFor(Settings.Global.AUTO_TIME_ZONE), true,
-                new ContentObserver(new Handler()) {
-                    public void onChange(boolean selfChange) {
-                        listener.onTimeZoneDetectionChange(isTimeZoneDetectionEnabled());
-                    }
-                });
-    }
-
-    @Override
-    public long currentTimeMillis() {
-        return System.currentTimeMillis();
-    }
-
-    @Override
-    public long elapsedRealtime() {
-        return SystemClock.elapsedRealtime();
-    }
-
-    @Override
-    public boolean isTimeZoneSettingInitialized() {
-        return isTimeZoneSettingInitializedStatic();
-
-    }
-
-    @Override
-    public boolean isTimeZoneDetectionEnabled() {
-        try {
-            return Settings.Global.getInt(mCr, Settings.Global.AUTO_TIME_ZONE) > 0;
-        } catch (Settings.SettingNotFoundException snfe) {
-            return true;
-        }
-    }
-
-    @Override
-    public void setDeviceTimeZone(String zoneId) {
-        setDeviceTimeZoneStatic(mContext, zoneId);
-    }
-
-    @Override
-    public void suggestDeviceTime(TimestampedValue<Long> signalTimeMillis) {
-        TimeSignal timeSignal = new TimeSignal(TimeSignal.SOURCE_ID_NITZ, signalTimeMillis);
-        mTimeDetector.suggestTime(timeSignal);
-    }
-
-    /**
-     * Static implementation of isTimeZoneSettingInitialized() for use from {@link MccTable}. This
-     * is a hack to deflake TelephonyTests when running on a device with a real SIM: in that
-     * situation real service events may come in while a TelephonyTest is running, leading to flakes
-     * as the real / fake instance of TimeServiceHelper is swapped in and out from
-     * {@link TelephonyComponentFactory}.
-     */
-    static boolean isTimeZoneSettingInitializedStatic() {
-        // timezone.equals("GMT") will be true and only true if the timezone was
-        // set to a default value by the system server (when starting, system server
-        // sets the persist.sys.timezone to "GMT" if it's not set). "GMT" is not used by
-        // any code that sets it explicitly (in case where something sets GMT explicitly,
-        // "Etc/GMT" Olsen ID would be used).
-        // TODO(b/64056758): Remove "timezone.equals("GMT")" hack when there's a
-        // better way of telling if the value has been defaulted.
-
-        String timeZoneId = SystemProperties.get(TIMEZONE_PROPERTY);
-        return timeZoneId != null && timeZoneId.length() > 0 && !timeZoneId.equals("GMT");
-    }
-
-    /**
-     * Static method for use by MccTable. See {@link #isTimeZoneSettingInitializedStatic()} for
-     * explanation.
-     */
-    static void setDeviceTimeZoneStatic(Context context, String zoneId) {
-        AlarmManager alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
-        alarmManager.setTimeZone(zoneId);
-        Intent intent = new Intent(TelephonyIntents.ACTION_NETWORK_SET_TIMEZONE);
-        intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING);
-        intent.putExtra("time-zone", zoneId);
-        context.sendStickyBroadcastAsUser(intent, UserHandle.ALL);
-    }
-}
diff --git a/src/java/com/android/internal/telephony/TimeZoneLookupHelper.java b/src/java/com/android/internal/telephony/TimeZoneLookupHelper.java
deleted file mode 100644
index a0f7447..0000000
--- a/src/java/com/android/internal/telephony/TimeZoneLookupHelper.java
+++ /dev/null
@@ -1,298 +0,0 @@
-/*
- * Copyright 2017 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;
-
-import android.text.TextUtils;
-
-import libcore.timezone.CountryTimeZones;
-import libcore.timezone.TimeZoneFinder;
-
-import java.util.Date;
-import java.util.TimeZone;
-
-/**
- * An interface to various time zone lookup behaviors.
- */
-// Non-final to allow mocking.
-public class TimeZoneLookupHelper {
-
-    /**
-     * The result of looking up a time zone using offset information (and possibly more).
-     */
-    public static final class OffsetResult {
-
-        /** A zone that matches the supplied criteria. See also {@link #isOnlyMatch}. */
-        public final String zoneId;
-
-        /** True if there is only one matching time zone for the supplied criteria. */
-        public final boolean isOnlyMatch;
-
-        public OffsetResult(String zoneId, boolean isOnlyMatch) {
-            this.zoneId = zoneId;
-            this.isOnlyMatch = isOnlyMatch;
-        }
-
-        @Override
-        public boolean equals(Object o) {
-            if (this == o) {
-                return true;
-            }
-            if (o == null || getClass() != o.getClass()) {
-                return false;
-            }
-
-            OffsetResult result = (OffsetResult) o;
-
-            if (isOnlyMatch != result.isOnlyMatch) {
-                return false;
-            }
-            return zoneId.equals(result.zoneId);
-        }
-
-        @Override
-        public int hashCode() {
-            int result = zoneId.hashCode();
-            result = 31 * result + (isOnlyMatch ? 1 : 0);
-            return result;
-        }
-
-        @Override
-        public String toString() {
-            return "Result{"
-                    + "zoneId='" + zoneId + '\''
-                    + ", isOnlyMatch=" + isOnlyMatch
-                    + '}';
-        }
-    }
-
-    /**
-     * The result of looking up a time zone using country information.
-     */
-    public static final class CountryResult {
-
-        /** A time zone for the country. */
-        public final String zoneId;
-
-        /**
-         * True if all the time zones in the country have the same offset at {@link #whenMillis}.
-         */
-        public final boolean allZonesHaveSameOffset;
-
-        /** The time associated with {@link #allZonesHaveSameOffset}. */
-        public final long whenMillis;
-
-        public CountryResult(String zoneId, boolean allZonesHaveSameOffset, long whenMillis) {
-            this.zoneId = zoneId;
-            this.allZonesHaveSameOffset = allZonesHaveSameOffset;
-            this.whenMillis = whenMillis;
-        }
-
-        @Override
-        public boolean equals(Object o) {
-            if (this == o) {
-                return true;
-            }
-            if (o == null || getClass() != o.getClass()) {
-                return false;
-            }
-
-            CountryResult that = (CountryResult) o;
-
-            if (allZonesHaveSameOffset != that.allZonesHaveSameOffset) {
-                return false;
-            }
-            if (whenMillis != that.whenMillis) {
-                return false;
-            }
-            return zoneId.equals(that.zoneId);
-        }
-
-        @Override
-        public int hashCode() {
-            int result = zoneId.hashCode();
-            result = 31 * result + (allZonesHaveSameOffset ? 1 : 0);
-            result = 31 * result + (int) (whenMillis ^ (whenMillis >>> 32));
-            return result;
-        }
-
-        @Override
-        public String toString() {
-            return "CountryResult{"
-                    + "zoneId='" + zoneId + '\''
-                    + ", allZonesHaveSameOffset=" + allZonesHaveSameOffset
-                    + ", whenMillis=" + whenMillis
-                    + '}';
-        }
-    }
-
-    private static final int MS_PER_HOUR = 60 * 60 * 1000;
-
-    /** The last CountryTimeZones object retrieved. */
-    private CountryTimeZones mLastCountryTimeZones;
-
-    public TimeZoneLookupHelper() {}
-
-    /**
-     * Looks for a time zone for the supplied NITZ and country information.
-     *
-     * <p><em>Note:</em> When there are multiple matching zones then one of the matching candidates
-     * will be returned in the result. If the current device default zone matches it will be
-     * returned in preference to other candidates. This method can return {@code null} if no
-     * matching time zones are found.
-     */
-    public OffsetResult lookupByNitzCountry(NitzData nitzData, String isoCountryCode) {
-        CountryTimeZones countryTimeZones = getCountryTimeZones(isoCountryCode);
-        if (countryTimeZones == null) {
-            return null;
-        }
-        android.icu.util.TimeZone bias = android.icu.util.TimeZone.getDefault();
-
-        CountryTimeZones.OffsetResult offsetResult = countryTimeZones.lookupByOffsetWithBias(
-                nitzData.getLocalOffsetMillis(), nitzData.isDst(),
-                nitzData.getCurrentTimeInMillis(), bias);
-
-        if (offsetResult == null) {
-            return null;
-        }
-        return new OffsetResult(offsetResult.mTimeZone.getID(), offsetResult.mOneMatch);
-    }
-
-    /**
-     * Looks for a time zone using only information present in the supplied {@link NitzData} object.
-     *
-     * <p><em>Note:</em> Because multiple time zones can have the same offset / DST state at a given
-     * time this process is error prone; an arbitrary match is returned when there are multiple
-     * candidates. The algorithm can also return a non-exact match by assuming that the DST
-     * information provided by NITZ is incorrect. This method can return {@code null} if no matching
-     * time zones are found.
-     */
-    public OffsetResult lookupByNitz(NitzData nitzData) {
-        return lookupByNitzStatic(nitzData);
-    }
-
-    /**
-     * Returns a time zone ID for the country if possible. For counties that use a single time zone
-     * this will provide a good choice. For countries with multiple time zones, a time zone is
-     * returned if all time zones used in the country currently have the same offset (currently ==
-     * according to the device's current system clock time). If this is not the case then
-     * {@code null} can be returned.
-     */
-    public CountryResult lookupByCountry(String isoCountryCode, long whenMillis) {
-        CountryTimeZones countryTimeZones = getCountryTimeZones(isoCountryCode);
-        if (countryTimeZones == null) {
-            // Unknown country code.
-            return null;
-        }
-        if (countryTimeZones.getDefaultTimeZoneId() == null) {
-            return null;
-        }
-
-        return new CountryResult(
-                countryTimeZones.getDefaultTimeZoneId(),
-                countryTimeZones.isDefaultOkForCountryTimeZoneDetection(whenMillis),
-                whenMillis);
-    }
-
-    /**
-     * Finds a time zone using only information present in the supplied {@link NitzData} object.
-     * This is a static method for use by {@link ServiceStateTracker}.
-     *
-     * <p><em>Note:</em> Because multiple time zones can have the same offset / DST state at a given
-     * time this process is error prone; an arbitrary match is returned when there are multiple
-     * candidates. The algorithm can also return a non-exact match by assuming that the DST
-     * information provided by NITZ is incorrect. This method can return {@code null} if no matching
-     * time zones are found.
-     */
-    static TimeZone guessZoneByNitzStatic(NitzData nitzData) {
-        OffsetResult result = lookupByNitzStatic(nitzData);
-        return result != null ? TimeZone.getTimeZone(result.zoneId) : null;
-    }
-
-    private static OffsetResult lookupByNitzStatic(NitzData nitzData) {
-        int utcOffsetMillis = nitzData.getLocalOffsetMillis();
-        boolean isDst = nitzData.isDst();
-        long timeMillis = nitzData.getCurrentTimeInMillis();
-
-        OffsetResult match = lookupByInstantOffsetDst(timeMillis, utcOffsetMillis, isDst);
-        if (match == null) {
-            // Couldn't find a proper timezone.  Perhaps the DST data is wrong.
-            match = lookupByInstantOffsetDst(timeMillis, utcOffsetMillis, !isDst);
-        }
-        return match;
-    }
-
-    private static OffsetResult lookupByInstantOffsetDst(long timeMillis, int utcOffsetMillis,
-            boolean isDst) {
-        int rawOffset = utcOffsetMillis;
-        if (isDst) {
-            rawOffset -= MS_PER_HOUR;
-        }
-        String[] zones = TimeZone.getAvailableIDs(rawOffset);
-        TimeZone match = null;
-        Date d = new Date(timeMillis);
-        boolean isOnlyMatch = true;
-        for (String zone : zones) {
-            TimeZone tz = TimeZone.getTimeZone(zone);
-            if (tz.getOffset(timeMillis) == utcOffsetMillis && tz.inDaylightTime(d) == isDst) {
-                if (match == null) {
-                    match = tz;
-                } else {
-                    isOnlyMatch = false;
-                    break;
-                }
-            }
-        }
-
-        if (match == null) {
-            return null;
-        }
-        return new OffsetResult(match.getID(), isOnlyMatch);
-    }
-
-    /**
-     * Returns {@code true} if the supplied (lower-case) ISO country code is for a country known to
-     * use a raw offset of zero from UTC at the time specified.
-     */
-    public boolean countryUsesUtc(String isoCountryCode, long whenMillis) {
-        if (TextUtils.isEmpty(isoCountryCode)) {
-            return false;
-        }
-
-        CountryTimeZones countryTimeZones = getCountryTimeZones(isoCountryCode);
-        return countryTimeZones != null && countryTimeZones.hasUtcZone(whenMillis);
-    }
-
-    private CountryTimeZones getCountryTimeZones(String isoCountryCode) {
-        // A single entry cache of the last CountryTimeZones object retrieved since there should
-        // be strong consistency across calls.
-        synchronized (this) {
-            if (mLastCountryTimeZones != null) {
-                if (mLastCountryTimeZones.isForCountryCode(isoCountryCode)) {
-                    return mLastCountryTimeZones;
-                }
-            }
-
-            // Perform the lookup. It's very unlikely to return null, but we won't cache null.
-            CountryTimeZones countryTimeZones =
-                    TimeZoneFinder.getInstance().lookupCountryTimeZones(isoCountryCode);
-            if (countryTimeZones != null) {
-                mLastCountryTimeZones = countryTimeZones;
-            }
-            return countryTimeZones;
-        }
-    }
-}
diff --git a/src/java/com/android/internal/telephony/UUSInfo.java b/src/java/com/android/internal/telephony/UUSInfo.java
index 9ef1893..2b4f674 100644
--- a/src/java/com/android/internal/telephony/UUSInfo.java
+++ b/src/java/com/android/internal/telephony/UUSInfo.java
@@ -16,7 +16,7 @@
 
 package com.android.internal.telephony;
 
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
 
 public class UUSInfo {
 
diff --git a/src/java/com/android/internal/telephony/UiccPhoneBookController.java b/src/java/com/android/internal/telephony/UiccPhoneBookController.java
index 5a4d480..54eae6f 100644
--- a/src/java/com/android/internal/telephony/UiccPhoneBookController.java
+++ b/src/java/com/android/internal/telephony/UiccPhoneBookController.java
@@ -18,29 +18,27 @@
 
 package com.android.internal.telephony;
 
-import android.annotation.UnsupportedAppUsage;
-import android.os.ServiceManager;
-import android.telephony.Rlog;
+import android.compat.annotation.UnsupportedAppUsage;
+import android.os.TelephonyServiceManager.ServiceRegisterer;
+import android.telephony.TelephonyFrameworkInitializer;
 
-import com.android.internal.telephony.IIccPhoneBook;
 import com.android.internal.telephony.uicc.AdnRecord;
+import com.android.telephony.Rlog;
 
-import java.lang.ArrayIndexOutOfBoundsException;
-import java.lang.NullPointerException;
 import java.util.List;
 
 public class UiccPhoneBookController extends IIccPhoneBook.Stub {
     private static final String TAG = "UiccPhoneBookController";
-    @UnsupportedAppUsage
-    private Phone[] mPhone;
 
     /* only one UiccPhoneBookController exists */
     @UnsupportedAppUsage
-    public UiccPhoneBookController(Phone[] phone) {
-        if (ServiceManager.getService("simphonebook") == null) {
-               ServiceManager.addService("simphonebook", this);
+    public UiccPhoneBookController() {
+        ServiceRegisterer iccPhoneBookServiceRegisterer = TelephonyFrameworkInitializer
+                .getTelephonyServiceManager()
+                .getIccPhoneBookServiceRegisterer();
+        if (iccPhoneBookServiceRegisterer.get() == null) {
+            iccPhoneBookServiceRegisterer.register(this);
         }
-        mPhone = phone;
     }
 
     @Override
@@ -139,7 +137,7 @@
 
         int phoneId = SubscriptionController.getInstance().getPhoneId(subId);
         try {
-            return mPhone[phoneId].getIccPhoneBookInterfaceManager();
+            return PhoneFactory.getPhone(phoneId).getIccPhoneBookInterfaceManager();
         } catch (NullPointerException e) {
             Rlog.e(TAG, "Exception is :"+e.toString()+" For subscription :"+subId );
             e.printStackTrace(); //To print stack trace
diff --git a/src/java/com/android/internal/telephony/WakeLockStateMachine.java b/src/java/com/android/internal/telephony/WakeLockStateMachine.java
index 1bc6298..eab9b8e 100644
--- a/src/java/com/android/internal/telephony/WakeLockStateMachine.java
+++ b/src/java/com/android/internal/telephony/WakeLockStateMachine.java
@@ -16,17 +16,17 @@
 
 package com.android.internal.telephony;
 
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
-import android.os.Build;
 import android.os.Message;
 import android.os.PowerManager;
-import android.telephony.Rlog;
 
+import com.android.internal.telephony.util.TelephonyUtils;
 import com.android.internal.util.State;
 import com.android.internal.util.StateMachine;
+import com.android.telephony.Rlog;
 
 import java.util.concurrent.atomic.AtomicInteger;
 
@@ -38,7 +38,7 @@
  * {@link #quit}.
  */
 public abstract class WakeLockStateMachine extends StateMachine {
-    protected static final boolean DBG = Build.IS_DEBUGGABLE;
+    protected static final boolean DBG = TelephonyUtils.IS_DEBUGGABLE;
 
     private final PowerManager.WakeLock mWakeLock;
 
@@ -130,7 +130,7 @@
             switch (msg.what) {
                 default: {
                     String errorText = "processMessage: unhandled message type " + msg.what;
-                    if (Build.IS_DEBUGGABLE) {
+                    if (TelephonyUtils.IS_DEBUGGABLE) {
                         throw new RuntimeException(errorText);
                     } else {
                         loge(errorText);
diff --git a/src/java/com/android/internal/telephony/WapPushOverSms.java b/src/java/com/android/internal/telephony/WapPushOverSms.java
index 14942b1..d755a92 100755
--- a/src/java/com/android/internal/telephony/WapPushOverSms.java
+++ b/src/java/com/android/internal/telephony/WapPushOverSms.java
@@ -20,10 +20,12 @@
 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.annotation.UnsupportedAppUsage;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.app.Activity;
 import android.app.AppOpsManager;
 import android.app.BroadcastOptions;
+import android.compat.annotation.UnsupportedAppUsage;
 import android.content.BroadcastReceiver;
 import android.content.ComponentName;
 import android.content.ContentValues;
@@ -31,26 +33,27 @@
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.content.ServiceConnection;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
 import android.database.Cursor;
 import android.database.DatabaseUtils;
 import android.database.sqlite.SQLiteException;
-import android.database.sqlite.SqliteWrapper;
 import android.net.Uri;
 import android.os.Bundle;
 import android.os.IBinder;
-import android.os.IDeviceIdleController;
+import android.os.PowerWhitelistManager;
 import android.os.RemoteException;
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.provider.Telephony;
 import android.provider.Telephony.Sms.Intents;
-import android.telephony.Rlog;
 import android.telephony.SmsManager;
 import android.telephony.SubscriptionManager;
 import android.text.TextUtils;
 import android.util.Log;
 
 import com.android.internal.telephony.uicc.IccUtils;
+import com.android.telephony.Rlog;
 
 import com.google.android.mms.MmsException;
 import com.google.android.mms.pdu.DeliveryInd;
@@ -62,6 +65,7 @@
 import com.google.android.mms.pdu.ReadOrigInd;
 
 import java.util.HashMap;
+import java.util.List;
 
 /**
  * WAP push handler class.
@@ -74,8 +78,7 @@
 
     @UnsupportedAppUsage
     private final Context mContext;
-    @UnsupportedAppUsage
-    private IDeviceIdleController mDeviceIdleController;
+    PowerWhitelistManager mPowerWhitelistManager;
 
     private String mWapPushManagerPackage;
 
@@ -111,7 +114,7 @@
 
     private void bindWapPushManagerService(Context context) {
         Intent intent = new Intent(IWapPushManager.class.getName());
-        ComponentName comp = intent.resolveSystemService(context.getPackageManager(), 0);
+        ComponentName comp = resolveSystemService(context.getPackageManager(), intent);
         intent.setComponent(comp);
         if (comp == null || !context.bindService(intent, this, Context.BIND_AUTO_CREATE)) {
             Rlog.e(TAG, "bindService() for wappush manager failed");
@@ -123,6 +126,33 @@
         }
     }
 
+    /**
+     * Special function for use by the system to resolve service
+     * intents to system apps.  Throws an exception if there are
+     * multiple potential matches to the Intent.  Returns null if
+     * there are no matches.
+     */
+    private static @Nullable ComponentName resolveSystemService(@NonNull PackageManager pm,
+            @NonNull Intent intent) {
+        List<ResolveInfo> results = pm.queryIntentServices(
+                intent, PackageManager.MATCH_SYSTEM_ONLY);
+        if (results == null) {
+            return null;
+        }
+        ComponentName comp = null;
+        for (int i = 0; i < results.size(); i++) {
+            ResolveInfo ri = results.get(i);
+            ComponentName foundComp = new ComponentName(ri.serviceInfo.applicationInfo.packageName,
+                    ri.serviceInfo.name);
+            if (comp != null) {
+                throw new IllegalStateException("Multiple system services handle " + intent
+                    + ": " + comp + ", " + foundComp);
+            }
+            comp = foundComp;
+        }
+        return comp;
+    }
+
     @Override
     public void onServiceConnected(ComponentName name, IBinder service) {
         mWapPushManager = IWapPushManager.Stub.asInterface(service);
@@ -137,8 +167,7 @@
 
     public WapPushOverSms(Context context) {
         mContext = context;
-        mDeviceIdleController = TelephonyComponentFactory.getInstance()
-                .inject(IDeviceIdleController.class.getName()).getIDeviceIdleController();
+        mPowerWhitelistManager = mContext.getSystemService(PowerWhitelistManager.class);
 
         UserManager userManager = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
 
@@ -332,17 +361,12 @@
      *         to applications
      */
     public int dispatchWapPdu(byte[] pdu, BroadcastReceiver receiver, InboundSmsHandler handler,
-            String address, int subId) {
+            String address, int subId, long messageId) {
         DecodedResult result = decodeWapPdu(pdu, handler);
         if (result.statusCode != Activity.RESULT_OK) {
             return result.statusCode;
         }
 
-        if (SmsManager.getDefault().getAutoPersisting()) {
-            // Store the wap push data in telephony
-            writeInboxMessage(result.subId, result.parsedPdu);
-        }
-
         /**
          * If the pdu has application ID, WapPushManager substitute the message
          * processing. Since WapPushManager is optional module, if WapPushManager
@@ -357,8 +381,8 @@
                     if (DBG) Rlog.w(TAG, "wap push manager not found!");
                 } else {
                     synchronized (this) {
-                        mDeviceIdleController.addPowerSaveTempWhitelistAppForMms(
-                                mWapPushManagerPackage, 0, "mms-mgr");
+                        mPowerWhitelistManager.whitelistAppTemporarilyForEvent(
+                                mWapPushManagerPackage, PowerWhitelistManager.EVENT_MMS, "mms-mgr");
                     }
 
                     Intent intent = new Intent();
@@ -405,6 +429,9 @@
         if (!TextUtils.isEmpty(address)) {
             intent.putExtra("address", address);
         }
+        if (messageId != 0L) {
+            intent.putExtra("messageId", messageId);
+        }
 
         // Direct the intent to only the default MMS app. If we can't find a default MMS app
         // then sent it to all broadcast receivers.
@@ -415,18 +442,15 @@
             intent.setComponent(componentName);
             if (DBG) Rlog.v(TAG, "Delivering MMS to: " + componentName.getPackageName() +
                     " " + componentName.getClassName());
-            try {
-                long duration = mDeviceIdleController.addPowerSaveTempWhitelistAppForMms(
-                        componentName.getPackageName(), 0, "mms-app");
-                BroadcastOptions bopts = BroadcastOptions.makeBasic();
-                bopts.setTemporaryAppWhitelistDuration(duration);
-                options = bopts.toBundle();
-            } catch (RemoteException e) {
-            }
+            long duration = mPowerWhitelistManager.whitelistAppTemporarilyForEvent(
+                    componentName.getPackageName(), PowerWhitelistManager.EVENT_MMS, "mms-app");
+            BroadcastOptions bopts = BroadcastOptions.makeBasic();
+            bopts.setTemporaryAppWhitelistDuration(duration);
+            options = bopts.toBundle();
         }
 
         handler.dispatchIntent(intent, getPermissionForType(result.mimeType),
-                getAppOpsPermissionForIntent(result.mimeType), options, receiver,
+                getAppOpsStringPermissionForIntent(result.mimeType), options, receiver,
                 UserHandle.SYSTEM, subId);
         return Activity.RESULT_OK;
     }
@@ -478,9 +502,7 @@
                     // Update thread ID for ReadOrigInd & DeliveryInd.
                     final ContentValues values = new ContentValues(1);
                     values.put(Telephony.Mms.THREAD_ID, threadId);
-                    if (SqliteWrapper.update(
-                            mContext,
-                            mContext.getContentResolver(),
+                    if (mContext.getContentResolver().update(
                             uri,
                             values,
                             null/*where*/,
@@ -552,17 +574,15 @@
         }
         Cursor cursor = null;
         try {
-            cursor = SqliteWrapper.query(
-                    context,
-                    context.getContentResolver(),
-                    Telephony.Mms.CONTENT_URI,
-                    new String[]{ Telephony.Mms.THREAD_ID },
-                    THREAD_ID_SELECTION,
-                    new String[]{
-                            DatabaseUtils.sqlEscapeString(messageId),
-                            Integer.toString(PduHeaders.MESSAGE_TYPE_SEND_REQ)
-                    },
-                    null/*sortOrder*/);
+            cursor = context.getContentResolver().query(
+                Telephony.Mms.CONTENT_URI,
+                new String[]{ Telephony.Mms.THREAD_ID },
+                THREAD_ID_SELECTION,
+                new String[]{
+                    DatabaseUtils.sqlEscapeString(messageId),
+                    Integer.toString(PduHeaders.MESSAGE_TYPE_SEND_REQ)
+                },
+                null/*sortOrder*/);
             if (cursor != null && cursor.moveToFirst()) {
                 return cursor.getLong(0);
             }
@@ -587,17 +607,15 @@
             String[] selectionArgs = new String[] { location };
             Cursor cursor = null;
             try {
-                cursor = SqliteWrapper.query(
-                        context,
-                        context.getContentResolver(),
-                        Telephony.Mms.CONTENT_URI,
-                        new String[]{Telephony.Mms._ID},
-                        LOCATION_SELECTION,
-                        new String[]{
-                                Integer.toString(PduHeaders.MESSAGE_TYPE_NOTIFICATION_IND),
-                                new String(rawLocation)
-                        },
-                        null/*sortOrder*/);
+                cursor = context.getContentResolver().query(
+                    Telephony.Mms.CONTENT_URI,
+                    new String[]{ Telephony.Mms._ID },
+                    LOCATION_SELECTION,
+                    new String[]{
+                        Integer.toString(PduHeaders.MESSAGE_TYPE_NOTIFICATION_IND),
+                        new String(rawLocation)
+                    },
+                    null/*sortOrder*/);
                 if (cursor != null && cursor.getCount() > 0) {
                     // We already received the same notification before.
                     return true;
@@ -623,12 +641,17 @@
         return permission;
     }
 
-    public static int getAppOpsPermissionForIntent(String mimeType) {
-        int appOp;
+    /**
+     * Return a appOps String for the given MIME type.
+     * @param mimeType MIME type of the Intent
+     * @return The appOps String
+     */
+    public static String getAppOpsStringPermissionForIntent(String mimeType) {
+        String appOp;
         if (WspTypeDecoder.CONTENT_TYPE_B_MMS.equals(mimeType)) {
-            appOp = AppOpsManager.OP_RECEIVE_MMS;
+            appOp = AppOpsManager.OPSTR_RECEIVE_MMS;
         } else {
-            appOp = AppOpsManager.OP_RECEIVE_WAP_PUSH;
+            appOp = AppOpsManager.OPSTR_RECEIVE_WAP_PUSH;
         }
         return appOp;
     }
diff --git a/src/java/com/android/internal/telephony/WspTypeDecoder.java b/src/java/com/android/internal/telephony/WspTypeDecoder.java
index 041db61..372aa73 100755
--- a/src/java/com/android/internal/telephony/WspTypeDecoder.java
+++ b/src/java/com/android/internal/telephony/WspTypeDecoder.java
@@ -16,7 +16,8 @@
 
 package com.android.internal.telephony;
 
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
+
 import java.util.HashMap;
 
 /**
diff --git a/src/java/com/android/internal/telephony/cat/AppInterface.java b/src/java/com/android/internal/telephony/cat/AppInterface.java
index ed7c822..079b3f6 100755
--- a/src/java/com/android/internal/telephony/cat/AppInterface.java
+++ b/src/java/com/android/internal/telephony/cat/AppInterface.java
@@ -16,7 +16,7 @@
 
 package com.android.internal.telephony.cat;
 
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
 import android.content.ComponentName;
 
 /**
@@ -62,12 +62,19 @@
      */
     void onCmdResponse(CatResponseMessage resMsg);
 
+    /**
+     * Dispose when the service is not longer needed.
+     */
+    void dispose();
+
     /*
      * Enumeration for representing "Type of Command" of proactive commands.
      * Those are the only commands which are supported by the Telephony. Any app
      * implementation should support those.
      * Refer to ETSI TS 102.223 section 9.4
      */
+    @UnsupportedAppUsage(implicitMember =
+            "values()[Lcom/android/internal/telephony/cat/AppInterface$CommandType;")
     public static enum CommandType {
         @UnsupportedAppUsage
         DISPLAY_TEXT(0x21),
diff --git a/src/java/com/android/internal/telephony/cat/CatCmdMessage.java b/src/java/com/android/internal/telephony/cat/CatCmdMessage.java
index 313e56f..936158a 100644
--- a/src/java/com/android/internal/telephony/cat/CatCmdMessage.java
+++ b/src/java/com/android/internal/telephony/cat/CatCmdMessage.java
@@ -16,7 +16,7 @@
 
 package com.android.internal.telephony.cat;
 
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
 import android.os.Parcel;
 import android.os.Parcelable;
 
@@ -138,10 +138,10 @@
     }
 
     public CatCmdMessage(Parcel in) {
-        mCmdDet = in.readParcelable(null);
-        mTextMsg = in.readParcelable(null);
-        mMenu = in.readParcelable(null);
-        mInput = in.readParcelable(null);
+        mCmdDet = in.readParcelable(CommandDetails.class.getClassLoader());
+        mTextMsg = in.readParcelable(TextMessage.class.getClassLoader());
+        mMenu = in.readParcelable(Menu.class.getClassLoader());
+        mInput = in.readParcelable(Input.class.getClassLoader());
         mLoadIconFailed = (in.readByte() == 1);
         switch (getCmdType()) {
         case LAUNCH_BROWSER:
@@ -150,12 +150,12 @@
             mBrowserSettings.mode = LaunchBrowserMode.values()[in.readInt()];
             break;
         case PLAY_TONE:
-            mToneSettings = in.readParcelable(null);
+            mToneSettings = in.readParcelable(ToneSettings.class.getClassLoader());
             break;
         case SET_UP_CALL:
             mCallSettings = new CallSettings();
-            mCallSettings.confirmMsg = in.readParcelable(null);
-            mCallSettings.callMsg = in.readParcelable(null);
+            mCallSettings.confirmMsg = in.readParcelable(TextMessage.class.getClassLoader());
+            mCallSettings.callMsg = in.readParcelable(TextMessage.class.getClassLoader());
             break;
         case SET_UP_EVENT_LIST:
             mSetupEventListSettings = new SetupEventListSettings();
diff --git a/src/java/com/android/internal/telephony/cat/CatLog.java b/src/java/com/android/internal/telephony/cat/CatLog.java
index 128d7ed..62bdf4e 100644
--- a/src/java/com/android/internal/telephony/cat/CatLog.java
+++ b/src/java/com/android/internal/telephony/cat/CatLog.java
@@ -16,8 +16,9 @@
 
 package com.android.internal.telephony.cat;
 
-import android.annotation.UnsupportedAppUsage;
-import android.telephony.Rlog;
+import android.compat.annotation.UnsupportedAppUsage;
+
+import com.android.telephony.Rlog;
 
 public abstract class CatLog {
     static final boolean DEBUG = true;
diff --git a/src/java/com/android/internal/telephony/cat/CatResponseMessage.java b/src/java/com/android/internal/telephony/cat/CatResponseMessage.java
index a3f2240..e1d4fe3 100644
--- a/src/java/com/android/internal/telephony/cat/CatResponseMessage.java
+++ b/src/java/com/android/internal/telephony/cat/CatResponseMessage.java
@@ -16,7 +16,7 @@
 
 package com.android.internal.telephony.cat;
 
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
 
 public class CatResponseMessage {
         CommandDetails mCmdDet = null;
diff --git a/src/java/com/android/internal/telephony/cat/CatService.java b/src/java/com/android/internal/telephony/cat/CatService.java
index 76d2e4a..5544292 100644
--- a/src/java/com/android/internal/telephony/cat/CatService.java
+++ b/src/java/com/android/internal/telephony/cat/CatService.java
@@ -16,29 +16,23 @@
 
 package com.android.internal.telephony.cat;
 
-import static com.android.internal.telephony.cat.CatCmdMessage.SetupEventListConstants
-        .IDLE_SCREEN_AVAILABLE_EVENT;
-import static com.android.internal.telephony.cat.CatCmdMessage.SetupEventListConstants
-        .LANGUAGE_SELECTION_EVENT;
-import static com.android.internal.telephony.cat.CatCmdMessage.SetupEventListConstants
-        .USER_ACTIVITY_EVENT;
+import static com.android.internal.telephony.cat.CatCmdMessage.SetupEventListConstants.IDLE_SCREEN_AVAILABLE_EVENT;
+import static com.android.internal.telephony.cat.CatCmdMessage.SetupEventListConstants.LANGUAGE_SELECTION_EVENT;
+import static com.android.internal.telephony.cat.CatCmdMessage.SetupEventListConstants.USER_ACTIVITY_EVENT;
 
-import android.annotation.UnsupportedAppUsage;
-import android.app.ActivityManagerNative;
-import android.app.IActivityManager;
+import android.app.ActivityManager;
 import android.app.backup.BackupManager;
+import android.compat.annotation.UnsupportedAppUsage;
 import android.content.Context;
 import android.content.Intent;
 import android.content.pm.PackageManager;
 import android.content.pm.ResolveInfo;
-import android.content.res.Configuration;
 import android.content.res.Resources.NotFoundException;
 import android.os.AsyncResult;
 import android.os.Handler;
 import android.os.LocaleList;
 import android.os.Message;
 import android.os.RemoteException;
-import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyManager;
 
 import com.android.internal.telephony.CommandsInterface;
@@ -159,7 +153,7 @@
         mSlotId = slotId;
 
         // Get the RilMessagesDecoder for decoding the messages.
-        mMsgDecoder = RilMessageDecoder.getInstance(this, fh, slotId);
+        mMsgDecoder = RilMessageDecoder.getInstance(this, fh, context, slotId);
         if (null == mMsgDecoder) {
             CatLog.d(this, "Null RilMessageDecoder instance");
             return;
@@ -221,7 +215,7 @@
 
         synchronized (sInstanceLock) {
             if (sInstance == null) {
-                int simCount = TelephonyManager.getDefault().getSimCount();
+                int simCount = TelephonyManager.getDefault().getSupportedModemCount();
                 sInstance = new CatService[simCount];
                 for (int i = 0; i < simCount; i++) {
                     sInstance[i] = null;
@@ -252,6 +246,7 @@
     }
 
     @UnsupportedAppUsage
+    @Override
     public void dispose() {
         synchronized (sInstanceLock) {
             CatLog.d(this, "Disposing CatService object");
@@ -271,11 +266,13 @@
                 mUiccController.unregisterForIccChanged(this);
                 mUiccController = null;
             }
-            mMsgDecoder.dispose();
-            mMsgDecoder = null;
+            if (mMsgDecoder != null) {
+                mMsgDecoder.dispose();
+                mMsgDecoder = null;
+            }
             removeCallbacksAndMessages(null);
             if (sInstance != null) {
-                if (SubscriptionManager.isValidSlotIndex(mSlotId)) {
+                if (mSlotId >= 0 && mSlotId < sInstance.length) {
                     sInstance[mSlotId] = null;
                 } else {
                     CatLog.d(this, "error: invaild slot id: " + mSlotId);
@@ -382,10 +379,8 @@
 
         // Log all proactive commands.
         if (isProactiveCmd) {
-            if (mUiccController != null) {
-                mUiccController.addCardLog("ProactiveCommand mSlotId=" + mSlotId +
-                        " cmdParams=" + cmdParams);
-            }
+            UiccController.addLocalLog("CatService[" + mSlotId + "]: ProactiveCommand " +
+                    " cmdParams=" + cmdParams);
         }
 
         CharSequence message;
@@ -543,7 +538,7 @@
 
     private void broadcastCatCmdIntent(CatCmdMessage cmdMsg) {
         Intent intent = new Intent(AppInterface.CAT_CMD_ACTION);
-        intent.putExtra("STK CMD", cmdMsg);
+        intent.putExtra( "STK CMD", cmdMsg);
         intent.putExtra("SLOT_ID", mSlotId);
         intent.setComponent(AppInterface.getDefaultSTKApplication());
         CatLog.d(this, "Sending CmdMsg: " + cmdMsg+ " on slotid:" + mSlotId);
@@ -811,7 +806,7 @@
      */
     //TODO Need to take care for MSIM
     public static AppInterface getInstance() {
-        int slotId = PhoneConstants.DEFAULT_CARD_INDEX;
+        int slotId = PhoneConstants.DEFAULT_SLOT_INDEX;
         SubscriptionController sControl = SubscriptionController.getInstance();
         if (sControl != null) {
             slotId = sControl.getSlotIndex(sControl.getDefaultSubId());
@@ -1176,11 +1171,14 @@
     }
 
     private void changeLanguage(String language) throws RemoteException {
-        IActivityManager am = ActivityManagerNative.getDefault();
-        Configuration config = am.getConfiguration();
-        config.setLocales(new LocaleList(new Locale(language), LocaleList.getDefault()));
-        config.userSetLocale = true;
-        am.updatePersistentConfiguration(config);
+        // get locale list, combined with language locale and default locale list.
+        LocaleList defaultLocaleList = LocaleList.getDefault();
+        Locale[] locales = new Locale[defaultLocaleList.size() + 1];
+        locales[0] = new Locale(language);
+        for (int i = 0; i < defaultLocaleList.size(); i++) {
+            locales[i+1] = defaultLocaleList.get(i);
+        }
+        mContext.getSystemService(ActivityManager.class).setDeviceLocales(new LocaleList(locales));
         BackupManager.dataChanged("com.android.providers.settings");
     }
 }
diff --git a/src/java/com/android/internal/telephony/cat/CommandDetails.java b/src/java/com/android/internal/telephony/cat/CommandDetails.java
index d7c511a..51a6dd4 100644
--- a/src/java/com/android/internal/telephony/cat/CommandDetails.java
+++ b/src/java/com/android/internal/telephony/cat/CommandDetails.java
@@ -16,7 +16,7 @@
 
 package com.android.internal.telephony.cat;
 
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
 import android.os.Parcel;
 import android.os.Parcelable;
 
@@ -28,7 +28,7 @@
  * Class for Command Details object of proactive commands from SIM.
  * {@hide}
  */
-class CommandDetails extends ValueObject implements Parcelable {
+public class CommandDetails extends ValueObject implements Parcelable {
     @UnsupportedAppUsage
     public boolean compRequired;
     @UnsupportedAppUsage
diff --git a/src/java/com/android/internal/telephony/cat/CommandParams.java b/src/java/com/android/internal/telephony/cat/CommandParams.java
index 80e5973..111f0cd 100755
--- a/src/java/com/android/internal/telephony/cat/CommandParams.java
+++ b/src/java/com/android/internal/telephony/cat/CommandParams.java
@@ -16,7 +16,7 @@
 
 package com.android.internal.telephony.cat;
 
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
 import android.graphics.Bitmap;
 
 /**
diff --git a/src/java/com/android/internal/telephony/cat/CommandParamsFactory.java b/src/java/com/android/internal/telephony/cat/CommandParamsFactory.java
index bafb8bb..9284c76 100644
--- a/src/java/com/android/internal/telephony/cat/CommandParamsFactory.java
+++ b/src/java/com/android/internal/telephony/cat/CommandParamsFactory.java
@@ -16,7 +16,15 @@
 
 package com.android.internal.telephony.cat;
 
-import android.annotation.UnsupportedAppUsage;
+import static com.android.internal.telephony.cat.CatCmdMessage.SetupEventListConstants.BROWSER_TERMINATION_EVENT;
+import static com.android.internal.telephony.cat.CatCmdMessage.SetupEventListConstants.BROWSING_STATUS_EVENT;
+import static com.android.internal.telephony.cat.CatCmdMessage.SetupEventListConstants.IDLE_SCREEN_AVAILABLE_EVENT;
+import static com.android.internal.telephony.cat.CatCmdMessage.SetupEventListConstants.LANGUAGE_SELECTION_EVENT;
+import static com.android.internal.telephony.cat.CatCmdMessage.SetupEventListConstants.USER_ACTIVITY_EVENT;
+
+import android.compat.annotation.UnsupportedAppUsage;
+import android.content.Context;
+import android.content.res.Resources.NotFoundException;
 import android.graphics.Bitmap;
 import android.os.Handler;
 import android.os.Message;
@@ -28,17 +36,6 @@
 import java.util.Iterator;
 import java.util.List;
 import java.util.Locale;
-
-import static com.android.internal.telephony.cat.CatCmdMessage
-                   .SetupEventListConstants.BROWSER_TERMINATION_EVENT;
-import static com.android.internal.telephony.cat.CatCmdMessage
-                   .SetupEventListConstants.BROWSING_STATUS_EVENT;
-import static com.android.internal.telephony.cat.CatCmdMessage
-                   .SetupEventListConstants.IDLE_SCREEN_AVAILABLE_EVENT;
-import static com.android.internal.telephony.cat.CatCmdMessage
-                   .SetupEventListConstants.LANGUAGE_SELECTION_EVENT;
-import static com.android.internal.telephony.cat.CatCmdMessage
-                   .SetupEventListConstants.USER_ACTIVITY_EVENT;
 /**
  * Factory class, used for decoding raw byte arrays, received from baseband,
  * into a CommandParams object.
@@ -54,6 +51,7 @@
     private boolean mloadIcon = false;
     private String mSavedLanguage;
     private String mRequestedLanguage;
+    private boolean mNoAlphaUsrCnf = false;
 
     // constants
     static final int MSG_ID_LOAD_ICON_DONE = 1;
@@ -88,19 +86,25 @@
     private static final int MAX_UCS2_CHARS = 118;
 
     static synchronized CommandParamsFactory getInstance(RilMessageDecoder caller,
-            IccFileHandler fh) {
+            IccFileHandler fh, Context context) {
         if (sInstance != null) {
             return sInstance;
         }
         if (fh != null) {
-            return new CommandParamsFactory(caller, fh);
+            return new CommandParamsFactory(caller, fh, context);
         }
         return null;
     }
 
-    private CommandParamsFactory(RilMessageDecoder caller, IccFileHandler fh) {
+    private CommandParamsFactory(RilMessageDecoder caller, IccFileHandler fh, Context context) {
         mCaller = caller;
         mIconLoader = IconLoader.getInstance(this, fh);
+        try {
+            mNoAlphaUsrCnf = context.getResources().getBoolean(
+                    com.android.internal.R.bool.config_stkNoAlphaUsrCnf);
+        } catch (NotFoundException e) {
+            mNoAlphaUsrCnf = false;
+        }
     }
 
     private CommandDetails processCommandDetails(List<ComprehensionTlv> ctlvs) {
@@ -164,62 +168,62 @@
 
         try {
             switch (cmdType) {
-            case SET_UP_MENU:
-                cmdPending = processSelectItem(cmdDet, ctlvs);
-                break;
-            case SELECT_ITEM:
-                cmdPending = processSelectItem(cmdDet, ctlvs);
-                break;
-            case DISPLAY_TEXT:
-                cmdPending = processDisplayText(cmdDet, ctlvs);
-                break;
-             case SET_UP_IDLE_MODE_TEXT:
-                 cmdPending = processSetUpIdleModeText(cmdDet, ctlvs);
-                 break;
-             case GET_INKEY:
-                cmdPending = processGetInkey(cmdDet, ctlvs);
-                break;
-             case GET_INPUT:
-                 cmdPending = processGetInput(cmdDet, ctlvs);
-                 break;
-             case SEND_DTMF:
-             case SEND_SMS:
-             case REFRESH:
-             case RUN_AT:
-             case SEND_SS:
-             case SEND_USSD:
-                 cmdPending = processEventNotify(cmdDet, ctlvs);
-                 break;
-             case GET_CHANNEL_STATUS:
-             case SET_UP_CALL:
-                 cmdPending = processSetupCall(cmdDet, ctlvs);
-                 break;
-             case LAUNCH_BROWSER:
-                 cmdPending = processLaunchBrowser(cmdDet, ctlvs);
-                 break;
-             case PLAY_TONE:
-                cmdPending = processPlayTone(cmdDet, ctlvs);
-                break;
-             case SET_UP_EVENT_LIST:
-                 cmdPending = processSetUpEventList(cmdDet, ctlvs);
-                 break;
-             case PROVIDE_LOCAL_INFORMATION:
-                cmdPending = processProvideLocalInfo(cmdDet, ctlvs);
-                break;
-             case LANGUAGE_NOTIFICATION:
-                 cmdPending = processLanguageNotification(cmdDet, ctlvs);
-                 break;
-             case OPEN_CHANNEL:
-             case CLOSE_CHANNEL:
-             case RECEIVE_DATA:
-             case SEND_DATA:
-                 cmdPending = processBIPClient(cmdDet, ctlvs);
-                 break;
-            default:
-                // unsupported proactive commands
-                mCmdParams = new CommandParams(cmdDet);
-                sendCmdParams(ResultCode.BEYOND_TERMINAL_CAPABILITY);
-                return;
+                case SET_UP_MENU:
+                    cmdPending = processSelectItem(cmdDet, ctlvs);
+                    break;
+                case SELECT_ITEM:
+                    cmdPending = processSelectItem(cmdDet, ctlvs);
+                    break;
+                case DISPLAY_TEXT:
+                    cmdPending = processDisplayText(cmdDet, ctlvs);
+                    break;
+                case SET_UP_IDLE_MODE_TEXT:
+                    cmdPending = processSetUpIdleModeText(cmdDet, ctlvs);
+                    break;
+                case GET_INKEY:
+                    cmdPending = processGetInkey(cmdDet, ctlvs);
+                    break;
+                case GET_INPUT:
+                    cmdPending = processGetInput(cmdDet, ctlvs);
+                    break;
+                case SEND_DTMF:
+                case SEND_SMS:
+                case REFRESH:
+                case RUN_AT:
+                case SEND_SS:
+                case SEND_USSD:
+                    cmdPending = processEventNotify(cmdDet, ctlvs);
+                    break;
+                case GET_CHANNEL_STATUS:
+                case SET_UP_CALL:
+                    cmdPending = processSetupCall(cmdDet, ctlvs);
+                    break;
+                case LAUNCH_BROWSER:
+                    cmdPending = processLaunchBrowser(cmdDet, ctlvs);
+                    break;
+                case PLAY_TONE:
+                    cmdPending = processPlayTone(cmdDet, ctlvs);
+                    break;
+                case SET_UP_EVENT_LIST:
+                    cmdPending = processSetUpEventList(cmdDet, ctlvs);
+                    break;
+                case PROVIDE_LOCAL_INFORMATION:
+                    cmdPending = processProvideLocalInfo(cmdDet, ctlvs);
+                    break;
+                case LANGUAGE_NOTIFICATION:
+                    cmdPending = processLanguageNotification(cmdDet, ctlvs);
+                    break;
+                case OPEN_CHANNEL:
+                case CLOSE_CHANNEL:
+                case RECEIVE_DATA:
+                case SEND_DATA:
+                    cmdPending = processBIPClient(cmdDet, ctlvs);
+                    break;
+                default:
+                    // unsupported proactive commands
+                    mCmdParams = new CommandParams(cmdDet);
+                    sendCmdParams(ResultCode.BEYOND_TERMINAL_CAPABILITY);
+                    return;
             }
         } catch (ResultException e) {
             CatLog.d(this, "make: caught ResultException e=" + e);
@@ -235,11 +239,11 @@
     @Override
     public void handleMessage(Message msg) {
         switch (msg.what) {
-        case MSG_ID_LOAD_ICON_DONE:
-            if (mIconLoader != null) {
-                sendCmdParams(setIcons(msg.obj));
-            }
-            break;
+            case MSG_ID_LOAD_ICON_DONE:
+                if (mIconLoader != null) {
+                    sendCmdParams(setIcons(msg.obj));
+                }
+                break;
         }
     }
 
@@ -252,28 +256,28 @@
             mCmdParams.mLoadIconFailed = true;
             mloadIcon = false;
             /** In case of icon load fail consider the
-            ** received proactive command as valid (sending RESULT OK) as
-            ** The result code, 'PRFRMD_ICON_NOT_DISPLAYED' will be added in the
-            ** terminal response by CatService/StkAppService if needed based on
-            ** the value of mLoadIconFailed.
-            */
+             ** received proactive command as valid (sending RESULT OK) as
+             ** The result code, 'PRFRMD_ICON_NOT_DISPLAYED' will be added in the
+             ** terminal response by CatService/StkAppService if needed based on
+             ** the value of mLoadIconFailed.
+             */
             return ResultCode.OK;
         }
         switch(mIconLoadState) {
-        case LOAD_SINGLE_ICON:
-            mCmdParams.setIcon((Bitmap) data);
-            break;
-        case LOAD_MULTI_ICONS:
-            icons = (Bitmap[]) data;
-            // set each item icon.
-            for (Bitmap icon : icons) {
-                mCmdParams.setIcon(icon);
-                if (icon == null && mloadIcon) {
-                    CatLog.d(this, "Optional Icon data is NULL while loading multi icons");
-                    mCmdParams.mLoadIconFailed = true;
+            case LOAD_SINGLE_ICON:
+                mCmdParams.setIcon((Bitmap) data);
+                break;
+            case LOAD_MULTI_ICONS:
+                icons = (Bitmap[]) data;
+                // set each item icon.
+                for (Bitmap icon : icons) {
+                    mCmdParams.setIcon(icon);
+                    if (icon == null && mloadIcon) {
+                        CatLog.d(this, "Optional Icon data is NULL while loading multi icons");
+                        mCmdParams.mLoadIconFailed = true;
+                    }
                 }
-            }
-            break;
+                break;
         }
         return ResultCode.OK;
     }
@@ -560,11 +564,11 @@
         // be encoded. Limit depends on DCS in Command Qualifier.
         if (input.ucs2 && input.maxLen > MAX_UCS2_CHARS) {
             CatLog.d(this, "UCS2: received maxLen = " + input.maxLen +
-                  ", truncating to " + MAX_UCS2_CHARS);
+                    ", truncating to " + MAX_UCS2_CHARS);
             input.maxLen = MAX_UCS2_CHARS;
         } else if (!input.packed && input.maxLen > MAX_GSM7_DEFAULT_CHARS) {
             CatLog.d(this, "GSM 7Bit Default: received maxLen = " + input.maxLen +
-                  ", truncating to " + MAX_GSM7_DEFAULT_CHARS);
+                    ", truncating to " + MAX_GSM7_DEFAULT_CHARS);
             input.maxLen = MAX_GSM7_DEFAULT_CHARS;
         }
 
@@ -606,7 +610,7 @@
         ComprehensionTlv ctlv = searchForTag(ComprehensionTlvTag.ALPHA_ID,
                 ctlvs);
         if (ctlv != null) {
-            menu.title = ValueParser.retrieveAlphaId(ctlv);
+            menu.title = ValueParser.retrieveAlphaId(ctlv, mNoAlphaUsrCnf);
         } else if (cmdType == AppInterface.CommandType.SET_UP_MENU) {
             // According to spec ETSI TS 102 223 section 6.10.3, the
             // Alpha ID is mandatory (and also part of minimum set of
@@ -666,26 +670,26 @@
 
         // Load icons data if needed.
         switch(mIconLoadState) {
-        case LOAD_NO_ICON:
-            return false;
-        case LOAD_SINGLE_ICON:
-            mloadIcon = true;
-            mIconLoader.loadIcon(titleIconId.recordNumber, this
-                    .obtainMessage(MSG_ID_LOAD_ICON_DONE));
-            break;
-        case LOAD_MULTI_ICONS:
-            int[] recordNumbers = itemsIconId.recordNumbers;
-            if (titleIconId != null) {
-                // Create a new array for all the icons (title and items).
-                recordNumbers = new int[itemsIconId.recordNumbers.length + 1];
-                recordNumbers[0] = titleIconId.recordNumber;
-                System.arraycopy(itemsIconId.recordNumbers, 0, recordNumbers,
-                        1, itemsIconId.recordNumbers.length);
-            }
-            mloadIcon = true;
-            mIconLoader.loadIcons(recordNumbers, this
-                    .obtainMessage(MSG_ID_LOAD_ICON_DONE));
-            break;
+            case LOAD_NO_ICON:
+                return false;
+            case LOAD_SINGLE_ICON:
+                mloadIcon = true;
+                mIconLoader.loadIcon(titleIconId.recordNumber, this
+                        .obtainMessage(MSG_ID_LOAD_ICON_DONE));
+                break;
+            case LOAD_MULTI_ICONS:
+                int[] recordNumbers = itemsIconId.recordNumbers;
+                if (titleIconId != null) {
+                    // Create a new array for all the icons (title and items).
+                    recordNumbers = new int[itemsIconId.recordNumbers.length + 1];
+                    recordNumbers[0] = titleIconId.recordNumber;
+                    System.arraycopy(itemsIconId.recordNumbers, 0, recordNumbers,
+                            1, itemsIconId.recordNumbers.length);
+                }
+                mloadIcon = true;
+                mIconLoader.loadIcons(recordNumbers, this
+                        .obtainMessage(MSG_ID_LOAD_ICON_DONE));
+                break;
         }
         return true;
     }
@@ -709,7 +713,7 @@
 
         ComprehensionTlv ctlv = searchForTag(ComprehensionTlvTag.ALPHA_ID,
                 ctlvs);
-        textMsg.text = ValueParser.retrieveAlphaId(ctlv);
+        textMsg.text = ValueParser.retrieveAlphaId(ctlv, mNoAlphaUsrCnf);
 
         ctlv = searchForTag(ComprehensionTlvTag.ICON_ID, ctlvs);
         if (ctlv != null) {
@@ -818,7 +822,7 @@
 
         // parse alpha identifier.
         ctlv = searchForTag(ComprehensionTlvTag.ALPHA_ID, ctlvs);
-        confirmMsg.text = ValueParser.retrieveAlphaId(ctlv);
+        confirmMsg.text = ValueParser.retrieveAlphaId(ctlv, mNoAlphaUsrCnf);
 
         // parse icon identifier
         ctlv = searchForTag(ComprehensionTlvTag.ICON_ID, ctlvs);
@@ -830,16 +834,16 @@
         // parse command qualifier value.
         LaunchBrowserMode mode;
         switch (cmdDet.commandQualifier) {
-        case 0x00:
-        default:
-            mode = LaunchBrowserMode.LAUNCH_IF_NOT_ALREADY_LAUNCHED;
-            break;
-        case 0x02:
-            mode = LaunchBrowserMode.USE_EXISTING_BROWSER;
-            break;
-        case 0x03:
-            mode = LaunchBrowserMode.LAUNCH_NEW_BROWSER;
-            break;
+            case 0x00:
+            default:
+                mode = LaunchBrowserMode.LAUNCH_IF_NOT_ALREADY_LAUNCHED;
+                break;
+            case 0x02:
+                mode = LaunchBrowserMode.USE_EXISTING_BROWSER;
+                break;
+            case 0x03:
+                mode = LaunchBrowserMode.LAUNCH_NEW_BROWSER;
+                break;
         }
 
         mCmdParams = new LaunchBrowserParams(cmdDet, confirmMsg, url, mode);
@@ -853,7 +857,7 @@
         return false;
     }
 
-     /**
+    /**
      * Processes PLAY_TONE proactive command from the SIM card.
      *
      * @param cmdDet Command Details container object.
@@ -891,7 +895,7 @@
         // parse alpha identifier
         ctlv = searchForTag(ComprehensionTlvTag.ALPHA_ID, ctlvs);
         if (ctlv != null) {
-            textMsg.text = ValueParser.retrieveAlphaId(ctlv);
+            textMsg.text = ValueParser.retrieveAlphaId(ctlv, mNoAlphaUsrCnf);
             // Assign the tone message text to empty string, if alpha identifier
             // data is null. If no alpha identifier tlv is present, then tone
             // message text will be null.
@@ -948,7 +952,7 @@
 
         // get confirmation message string.
         ctlv = searchForNextTag(ComprehensionTlvTag.ALPHA_ID, iter);
-        confirmMsg.text = ValueParser.retrieveAlphaId(ctlv);
+        confirmMsg.text = ValueParser.retrieveAlphaId(ctlv, mNoAlphaUsrCnf);
 
         ctlv = searchForTag(ComprehensionTlvTag.ICON_ID, ctlvs);
         if (ctlv != null) {
@@ -959,7 +963,7 @@
         // get call set up message string.
         ctlv = searchForNextTag(ComprehensionTlvTag.ALPHA_ID, iter);
         if (ctlv != null) {
-            callMsg.text = ValueParser.retrieveAlphaId(ctlv);
+            callMsg.text = ValueParser.retrieveAlphaId(ctlv, mNoAlphaUsrCnf);
         }
 
         ctlv = searchForTag(ComprehensionTlvTag.ICON_ID, ctlvs);
@@ -1067,9 +1071,9 @@
     }
 
     private boolean processBIPClient(CommandDetails cmdDet,
-                                     List<ComprehensionTlv> ctlvs) throws ResultException {
+            List<ComprehensionTlv> ctlvs) throws ResultException {
         AppInterface.CommandType commandType =
-                                    AppInterface.CommandType.fromInt(cmdDet.typeOfCommand);
+                AppInterface.CommandType.fromInt(cmdDet.typeOfCommand);
         if (commandType != null) {
             CatLog.d(this, "process "+ commandType.name());
         }
@@ -1082,7 +1086,7 @@
         // parse alpha identifier
         ctlv = searchForTag(ComprehensionTlvTag.ALPHA_ID, ctlvs);
         if (ctlv != null) {
-            textMsg.text = ValueParser.retrieveAlphaId(ctlv);
+            textMsg.text = ValueParser.retrieveAlphaId(ctlv, mNoAlphaUsrCnf);
             CatLog.d(this, "alpha TLV text=" + textMsg.text);
             has_alpha_id = true;
         }
diff --git a/src/java/com/android/internal/telephony/cat/ComprehensionTlv.java b/src/java/com/android/internal/telephony/cat/ComprehensionTlv.java
index 16615a0..3651a40 100644
--- a/src/java/com/android/internal/telephony/cat/ComprehensionTlv.java
+++ b/src/java/com/android/internal/telephony/cat/ComprehensionTlv.java
@@ -16,13 +16,13 @@
 
 package com.android.internal.telephony.cat;
 
-import android.annotation.UnsupportedAppUsage;
-import android.telephony.Rlog;
+import android.compat.annotation.UnsupportedAppUsage;
+
+import com.android.telephony.Rlog;
 
 import java.util.ArrayList;
 import java.util.List;
 
-
 /**
  * Class for representing COMPREHENSION-TLV objects.
  *
diff --git a/src/java/com/android/internal/telephony/cat/ComprehensionTlvTag.java b/src/java/com/android/internal/telephony/cat/ComprehensionTlvTag.java
index 1ab18a7..f8ba49f 100644
--- a/src/java/com/android/internal/telephony/cat/ComprehensionTlvTag.java
+++ b/src/java/com/android/internal/telephony/cat/ComprehensionTlvTag.java
@@ -16,7 +16,7 @@
 
 package com.android.internal.telephony.cat;
 
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
 
 /**
  * Enumeration for representing the tag value of COMPREHENSION-TLV objects. If
diff --git a/src/java/com/android/internal/telephony/cat/Duration.java b/src/java/com/android/internal/telephony/cat/Duration.java
index a979c54..412be9a 100644
--- a/src/java/com/android/internal/telephony/cat/Duration.java
+++ b/src/java/com/android/internal/telephony/cat/Duration.java
@@ -16,7 +16,7 @@
 
 package com.android.internal.telephony.cat;
 
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
 import android.os.Parcel;
 import android.os.Parcelable;
 
diff --git a/src/java/com/android/internal/telephony/cat/IconLoader.java b/src/java/com/android/internal/telephony/cat/IconLoader.java
index 4e5b555..94b5348 100644
--- a/src/java/com/android/internal/telephony/cat/IconLoader.java
+++ b/src/java/com/android/internal/telephony/cat/IconLoader.java
@@ -16,9 +16,7 @@
 
 package com.android.internal.telephony.cat;
 
-import com.android.internal.telephony.uicc.IccFileHandler;
-
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
 import android.graphics.Bitmap;
 import android.graphics.Color;
 import android.os.AsyncResult;
@@ -26,6 +24,9 @@
 import android.os.HandlerThread;
 import android.os.Looper;
 import android.os.Message;
+
+import com.android.internal.telephony.uicc.IccFileHandler;
+
 import java.util.HashMap;
 
 /**
diff --git a/src/java/com/android/internal/telephony/cat/Input.java b/src/java/com/android/internal/telephony/cat/Input.java
index aaaff43..e9103b8 100644
--- a/src/java/com/android/internal/telephony/cat/Input.java
+++ b/src/java/com/android/internal/telephony/cat/Input.java
@@ -58,7 +58,7 @@
     private Input(Parcel in) {
         text = in.readString();
         defaultText = in.readString();
-        icon = in.readParcelable(null);
+        icon = in.readParcelable(Bitmap.class.getClassLoader());
         minLen = in.readInt();
         maxLen = in.readInt();
         ucs2 = in.readInt() == 1 ? true : false;
diff --git a/src/java/com/android/internal/telephony/cat/Item.java b/src/java/com/android/internal/telephony/cat/Item.java
index 456a46f..702ed4b 100644
--- a/src/java/com/android/internal/telephony/cat/Item.java
+++ b/src/java/com/android/internal/telephony/cat/Item.java
@@ -46,7 +46,7 @@
     public Item(Parcel in) {
         id = in.readInt();
         text = in.readString();
-        icon = in.readParcelable(null);
+        icon = in.readParcelable(Bitmap.class.getClassLoader());
     }
 
     @Override
diff --git a/src/java/com/android/internal/telephony/cat/Menu.java b/src/java/com/android/internal/telephony/cat/Menu.java
index a93fd1f..7606007 100644
--- a/src/java/com/android/internal/telephony/cat/Menu.java
+++ b/src/java/com/android/internal/telephony/cat/Menu.java
@@ -16,7 +16,7 @@
 
 package com.android.internal.telephony.cat;
 
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
 import android.graphics.Bitmap;
 import android.os.Parcel;
 import android.os.Parcelable;
@@ -58,12 +58,12 @@
 
     private Menu(Parcel in) {
         title = in.readString();
-        titleIcon = in.readParcelable(null);
+        titleIcon = in.readParcelable(Bitmap.class.getClassLoader());
         // rebuild items list.
         items = new ArrayList<Item>();
         int size = in.readInt();
         for (int i=0; i<size; i++) {
-            Item item = in.readParcelable(null);
+            Item item = in.readParcelable(Item.class.getClassLoader());
             items.add(item);
         }
         defaultItem = in.readInt();
diff --git a/src/java/com/android/internal/telephony/cat/ResponseData.java b/src/java/com/android/internal/telephony/cat/ResponseData.java
index 838dd0f..8c7f248 100644
--- a/src/java/com/android/internal/telephony/cat/ResponseData.java
+++ b/src/java/com/android/internal/telephony/cat/ResponseData.java
@@ -16,20 +16,25 @@
 
 package com.android.internal.telephony.cat;
 
-import com.android.internal.telephony.EncodeException;
-import com.android.internal.telephony.GsmAlphabet;
-import java.util.Calendar;
-import java.util.TimeZone;
+import android.compat.annotation.UnsupportedAppUsage;
 import android.os.SystemProperties;
 import android.text.TextUtils;
 
+import com.android.internal.telephony.EncodeException;
+import com.android.internal.telephony.GsmAlphabet;
 import com.android.internal.telephony.cat.AppInterface.CommandType;
 
-import android.annotation.UnsupportedAppUsage;
 import java.io.ByteArrayOutputStream;
 import java.io.UnsupportedEncodingException;
+import java.util.Calendar;
+import java.util.TimeZone;
 
 abstract class ResponseData {
+
+    @UnsupportedAppUsage
+    ResponseData() {
+    }
+
     /**
      * Format the data appropriate for TERMINAL RESPONSE and write it into
      * the ByteArrayOutputStream object.
@@ -258,7 +263,7 @@
             data[7] = (byte) 0xFF;    // set FF in terminal response
         } else {
             TimeZone zone = TimeZone.getTimeZone(tz);
-            int zoneOffset = zone.getRawOffset() + zone.getDSTSavings();
+            int zoneOffset = zone.getOffset(mCalendar.getTimeInMillis());
             data[7] = getTZOffSetByte(zoneOffset);
         }
 
diff --git a/src/java/com/android/internal/telephony/cat/ResultCode.java b/src/java/com/android/internal/telephony/cat/ResultCode.java
index 346d74a..dca2999 100644
--- a/src/java/com/android/internal/telephony/cat/ResultCode.java
+++ b/src/java/com/android/internal/telephony/cat/ResultCode.java
@@ -16,7 +16,7 @@
 
 package com.android.internal.telephony.cat;
 
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
 
 
 /**
@@ -26,6 +26,7 @@
  *
  * {@hide}
  */
+@UnsupportedAppUsage(implicitMember = "values()[Lcom/android/internal/telephony/cat/ResultCode;")
 public enum ResultCode {
 
     /*
diff --git a/src/java/com/android/internal/telephony/cat/ResultException.java b/src/java/com/android/internal/telephony/cat/ResultException.java
index 32acae8..208aa9a 100644
--- a/src/java/com/android/internal/telephony/cat/ResultException.java
+++ b/src/java/com/android/internal/telephony/cat/ResultException.java
@@ -16,7 +16,7 @@
 
 package com.android.internal.telephony.cat;
 
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
 
 
 /**
diff --git a/src/java/com/android/internal/telephony/cat/RilMessageDecoder.java b/src/java/com/android/internal/telephony/cat/RilMessageDecoder.java
index 2e41dd4..bd8b0a8 100755
--- a/src/java/com/android/internal/telephony/cat/RilMessageDecoder.java
+++ b/src/java/com/android/internal/telephony/cat/RilMessageDecoder.java
@@ -16,13 +16,13 @@
 
 package com.android.internal.telephony.cat;
 
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
+import android.content.Context;
 import android.os.Handler;
 import android.os.Message;
 import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyManager;
 
-import com.android.internal.telephony.PhoneConstants;
 import com.android.internal.telephony.uicc.IccFileHandler;
 import com.android.internal.telephony.uicc.IccUtils;
 import com.android.internal.util.State;
@@ -41,6 +41,7 @@
     // members
     @UnsupportedAppUsage
     private CommandParamsFactory mCmdParamsFactory = null;
+    @UnsupportedAppUsage
     private RilMessage mCurrentRilMessage = null;
     private Handler mCaller = null;
     private static int mSimCount = 0;
@@ -61,9 +62,9 @@
      */
     @UnsupportedAppUsage
     public static synchronized RilMessageDecoder getInstance(Handler caller, IccFileHandler fh,
-            int slotId) {
+            Context context, int slotId) {
         if (null == mInstance) {
-            mSimCount = TelephonyManager.getDefault().getSimCount();
+            mSimCount = TelephonyManager.getDefault().getSupportedModemCount();
             mInstance = new RilMessageDecoder[mSimCount];
             for (int i = 0; i < mSimCount; i++) {
                 mInstance[i] = null;
@@ -72,7 +73,7 @@
 
         if (slotId != SubscriptionManager.INVALID_SIM_SLOT_INDEX && slotId < mSimCount) {
             if (null == mInstance[slotId]) {
-                mInstance[slotId] = new RilMessageDecoder(caller, fh);
+                mInstance[slotId] = new RilMessageDecoder(caller, fh, context);
             }
         } else {
             CatLog.d("RilMessageDecoder", "invaild slot id: " + slotId);
@@ -88,6 +89,7 @@
      *
      * @param rilMsg
      */
+    @UnsupportedAppUsage
     public void sendStartDecodingMessageParams(RilMessage rilMsg) {
         Message msg = obtainMessage(CMD_START);
         msg.obj = rilMsg;
@@ -107,13 +109,14 @@
         sendMessage(msg);
     }
 
+    @UnsupportedAppUsage
     private void sendCmdForExecution(RilMessage rilMsg) {
         Message msg = mCaller.obtainMessage(CatService.MSG_ID_RIL_MSG_DECODED,
                 new RilMessage(rilMsg));
         msg.sendToTarget();
     }
 
-    private RilMessageDecoder(Handler caller, IccFileHandler fh) {
+    private RilMessageDecoder(Handler caller, IccFileHandler fh, Context context) {
         super("RilMessageDecoder");
 
         addState(mStateStart);
@@ -121,7 +124,7 @@
         setInitialState(mStateStart);
 
         mCaller = caller;
-        mCmdParamsFactory = CommandParamsFactory.getInstance(this, fh);
+        mCmdParamsFactory = CommandParamsFactory.getInstance(this, fh, context);
     }
 
     private RilMessageDecoder() {
diff --git a/src/java/com/android/internal/telephony/cat/TextMessage.java b/src/java/com/android/internal/telephony/cat/TextMessage.java
index eddca4c..96ecd94 100644
--- a/src/java/com/android/internal/telephony/cat/TextMessage.java
+++ b/src/java/com/android/internal/telephony/cat/TextMessage.java
@@ -16,7 +16,7 @@
 
 package com.android.internal.telephony.cat;
 
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
 import android.graphics.Bitmap;
 import android.os.Parcel;
 import android.os.Parcelable;
@@ -40,7 +40,7 @@
     private TextMessage(Parcel in) {
         title = in.readString();
         text = in.readString();
-        icon = in.readParcelable(null);
+        icon = in.readParcelable(Bitmap.class.getClassLoader());
         iconSelfExplanatory = in.readInt() == 1 ? true : false;
         isHighPriority = in.readInt() == 1 ? true : false;
         responseNeeded = in.readInt() == 1 ? true : false;
diff --git a/src/java/com/android/internal/telephony/cat/ToneSettings.java b/src/java/com/android/internal/telephony/cat/ToneSettings.java
index 61c1573..4e94ead 100644
--- a/src/java/com/android/internal/telephony/cat/ToneSettings.java
+++ b/src/java/com/android/internal/telephony/cat/ToneSettings.java
@@ -35,8 +35,8 @@
     }
 
     private ToneSettings(Parcel in) {
-        duration = in.readParcelable(null);
-        tone = in.readParcelable(null);
+        duration = in.readParcelable(Duration.class.getClassLoader());
+        tone = in.readParcelable(Tone.class.getClassLoader());
         vibrate = in.readInt() == 1;
     }
 
diff --git a/src/java/com/android/internal/telephony/cat/ValueParser.java b/src/java/com/android/internal/telephony/cat/ValueParser.java
index 4e528b6..13ddee9 100644
--- a/src/java/com/android/internal/telephony/cat/ValueParser.java
+++ b/src/java/com/android/internal/telephony/cat/ValueParser.java
@@ -16,13 +16,12 @@
 
 package com.android.internal.telephony.cat;
 
+import android.compat.annotation.UnsupportedAppUsage;
+
 import com.android.internal.telephony.GsmAlphabet;
 import com.android.internal.telephony.cat.Duration.TimeUnit;
 import com.android.internal.telephony.uicc.IccUtils;
 
-import android.annotation.UnsupportedAppUsage;
-import android.content.res.Resources;
-import android.content.res.Resources.NotFoundException;
 import java.io.UnsupportedEncodingException;
 import java.util.ArrayList;
 import java.util.List;
@@ -62,6 +61,7 @@
      *         Command Details object is found, ResultException is thrown.
      * @throws ResultException
      */
+    @UnsupportedAppUsage
     static DeviceIdentities retrieveDeviceIdentities(ComprehensionTlv ctlv)
             throws ResultException {
 
@@ -276,7 +276,8 @@
      * @throws ResultException
      */
     @UnsupportedAppUsage
-    static String retrieveAlphaId(ComprehensionTlv ctlv) throws ResultException {
+    static String retrieveAlphaId(ComprehensionTlv ctlv, boolean noAlphaUsrCnf)
+            throws ResultException {
 
         if (ctlv != null) {
             byte[] rawValue = ctlv.getRawValue();
@@ -299,14 +300,6 @@
              * the terminal MAY give information to the user
              * noAlphaUsrCnf defines if you need to show user confirmation or not
              */
-            boolean noAlphaUsrCnf = false;
-            Resources resource = Resources.getSystem();
-            try {
-                noAlphaUsrCnf = resource.getBoolean(
-                        com.android.internal.R.bool.config_stkNoAlphaUsrCnf);
-            } catch (NotFoundException e) {
-                noAlphaUsrCnf = false;
-            }
             return (noAlphaUsrCnf ? null : CatService.STK_DEFAULT);
         }
     }
diff --git a/src/java/com/android/internal/telephony/cdma/CdmaCallWaitingNotification.java b/src/java/com/android/internal/telephony/cdma/CdmaCallWaitingNotification.java
index d1e0881..79c4d35 100644
--- a/src/java/com/android/internal/telephony/cdma/CdmaCallWaitingNotification.java
+++ b/src/java/com/android/internal/telephony/cdma/CdmaCallWaitingNotification.java
@@ -16,9 +16,10 @@
 
 package com.android.internal.telephony.cdma;
 
-import android.annotation.UnsupportedAppUsage;
-import android.telephony.Rlog;
+import android.compat.annotation.UnsupportedAppUsage;
+
 import com.android.internal.telephony.PhoneConstants;
+import com.android.telephony.Rlog;
 
 /**
  * Represents a Supplementary Service Notification received from the network.
diff --git a/src/java/com/android/internal/telephony/cdma/CdmaInboundSmsHandler.java b/src/java/com/android/internal/telephony/cdma/CdmaInboundSmsHandler.java
index ec14efa..61377dc 100644
--- a/src/java/com/android/internal/telephony/cdma/CdmaInboundSmsHandler.java
+++ b/src/java/com/android/internal/telephony/cdma/CdmaInboundSmsHandler.java
@@ -18,13 +18,16 @@
 
 import android.app.Activity;
 import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
 import android.content.res.Resources;
 import android.os.Message;
+import android.os.RemoteCallback;
+import android.os.SystemProperties;
 import android.provider.Telephony.Sms.Intents;
-import android.telephony.SmsCbMessage;
-import android.telephony.TelephonyManager;
+import android.telephony.PhoneNumberUtils;
+import android.telephony.cdma.CdmaSmsCbProgramResults;
 
-import com.android.internal.telephony.CellBroadcastHandler;
 import com.android.internal.telephony.CommandsInterface;
 import com.android.internal.telephony.InboundSmsHandler;
 import com.android.internal.telephony.InboundSmsTracker;
@@ -34,9 +37,15 @@
 import com.android.internal.telephony.SmsStorageMonitor;
 import com.android.internal.telephony.TelephonyComponentFactory;
 import com.android.internal.telephony.WspTypeDecoder;
+import com.android.internal.telephony.cdma.sms.BearerData;
+import com.android.internal.telephony.cdma.sms.CdmaSmsAddress;
 import com.android.internal.telephony.cdma.sms.SmsEnvelope;
 import com.android.internal.util.HexDump;
 
+import java.io.ByteArrayOutputStream;
+import java.io.DataOutputStream;
+import java.io.IOException;
+import java.util.ArrayList;
 import java.util.Arrays;
 
 /**
@@ -45,25 +54,102 @@
 public class CdmaInboundSmsHandler extends InboundSmsHandler {
 
     private final CdmaSMSDispatcher mSmsDispatcher;
-    private final CdmaServiceCategoryProgramHandler mServiceCategoryProgramHandler;
+    private static CdmaCbTestBroadcastReceiver sTestBroadcastReceiver;
+    private static CdmaScpTestBroadcastReceiver sTestScpBroadcastReceiver;
 
     private byte[] mLastDispatchedSmsFingerprint;
     private byte[] mLastAcknowledgedSmsFingerprint;
 
+    // Callback used to process the result of an SCP message
+    private RemoteCallback mScpCallback;
+
     private final boolean mCheckForDuplicatePortsInOmadmWapPush = Resources.getSystem().getBoolean(
             com.android.internal.R.bool.config_duplicate_port_omadm_wappush);
 
+    // When TEST_MODE is on we allow the test intent to trigger an SMS CB alert
+    private static final boolean TEST_MODE = SystemProperties.getInt("ro.debuggable", 0) == 1;
+    private static final String TEST_ACTION = "com.android.internal.telephony.cdma"
+            + ".TEST_TRIGGER_CELL_BROADCAST";
+    private static final String SCP_TEST_ACTION = "com.android.internal.telephony.cdma"
+            + ".TEST_TRIGGER_SCP_MESSAGE";
+
     /**
      * Create a new inbound SMS handler for CDMA.
      */
     private CdmaInboundSmsHandler(Context context, SmsStorageMonitor storageMonitor,
             Phone phone, CdmaSMSDispatcher smsDispatcher) {
-        super("CdmaInboundSmsHandler", context, storageMonitor, phone,
-                CellBroadcastHandler.makeCellBroadcastHandler(context, phone));
+        super("CdmaInboundSmsHandler", context, storageMonitor, phone);
         mSmsDispatcher = smsDispatcher;
-        mServiceCategoryProgramHandler = CdmaServiceCategoryProgramHandler.makeScpHandler(context,
-                phone.mCi);
         phone.mCi.setOnNewCdmaSms(getHandler(), EVENT_NEW_SMS, null);
+
+        mCellBroadcastServiceManager.enable();
+        mScpCallback = new RemoteCallback(result -> {
+            if (result == null) {
+                loge("SCP results error: missing extras");
+                return;
+            }
+            String sender = result.getString("sender");
+            if (sender == null) {
+                loge("SCP results error: missing sender extra.");
+                return;
+            }
+            ArrayList<CdmaSmsCbProgramResults> results = result.getParcelableArrayList("results");
+            if (results == null) {
+                loge("SCP results error: missing results extra.");
+                return;
+            }
+
+            BearerData bData = new BearerData();
+            bData.messageType = BearerData.MESSAGE_TYPE_SUBMIT;
+            bData.messageId = SmsMessage.getNextMessageId();
+            bData.serviceCategoryProgramResults = results;
+            byte[] encodedBearerData = BearerData.encode(bData);
+
+            ByteArrayOutputStream baos = new ByteArrayOutputStream(100);
+            DataOutputStream dos = new DataOutputStream(baos);
+            try {
+                dos.writeInt(SmsEnvelope.TELESERVICE_SCPT);
+                dos.writeInt(0); //servicePresent
+                dos.writeInt(0); //serviceCategory
+                CdmaSmsAddress destAddr = CdmaSmsAddress.parse(
+                        PhoneNumberUtils.cdmaCheckAndProcessPlusCodeForSms(sender));
+                dos.write(destAddr.digitMode);
+                dos.write(destAddr.numberMode);
+                dos.write(destAddr.ton); // number_type
+                dos.write(destAddr.numberPlan);
+                dos.write(destAddr.numberOfDigits);
+                dos.write(destAddr.origBytes, 0, destAddr.origBytes.length); // digits
+                // Subaddress is not supported.
+                dos.write(0); //subaddressType
+                dos.write(0); //subaddr_odd
+                dos.write(0); //subaddr_nbr_of_digits
+                dos.write(encodedBearerData.length);
+                dos.write(encodedBearerData, 0, encodedBearerData.length);
+                // Ignore the RIL response. TODO: implement retry if SMS send fails.
+                mPhone.mCi.sendCdmaSms(baos.toByteArray(), null);
+            } catch (IOException e) {
+                loge("exception creating SCP results PDU", e);
+            } finally {
+                try {
+                    dos.close();
+                } catch (IOException ignored) {
+                }
+            }
+        });
+        if (TEST_MODE) {
+            if (sTestBroadcastReceiver == null) {
+                sTestBroadcastReceiver = new CdmaCbTestBroadcastReceiver();
+                IntentFilter filter = new IntentFilter();
+                filter.addAction(TEST_ACTION);
+                context.registerReceiver(sTestBroadcastReceiver, filter);
+            }
+            if (sTestScpBroadcastReceiver == null) {
+                sTestScpBroadcastReceiver = new CdmaScpTestBroadcastReceiver();
+                IntentFilter filter = new IntentFilter();
+                filter.addAction(SCP_TEST_ACTION);
+                context.registerReceiver(sTestScpBroadcastReceiver, filter);
+            }
+        }
     }
 
     /**
@@ -72,7 +158,6 @@
     @Override
     protected void onQuitting() {
         mPhone.mCi.unSetOnNewCdmaSms(getHandler());
-        mCellBroadcastHandler.dispose();
 
         if (DBG) log("unregistered for 3GPP2 SMS");
         super.onQuitting();
@@ -91,6 +176,7 @@
 
     /**
      * Return true if this handler is for 3GPP2 messages; false for 3GPP format.
+     *
      * @return true (3GPP2)
      */
     @Override
@@ -100,6 +186,7 @@
 
     /**
      * Process Cell Broadcast, Voicemail Notification, and other 3GPP/3GPP2-specific messages.
+     *
      * @param smsb the SmsMessageBase object from the RIL
      * @return true if the message was handled here; false to continue processing
      */
@@ -111,14 +198,7 @@
         // Handle CMAS emergency broadcast messages.
         if (isBroadcastType) {
             log("Broadcast type message");
-            String plmn =
-                    TelephonyManager.from(mContext).getNetworkOperatorForPhone(mPhone.getPhoneId());
-            SmsCbMessage cbMessage = sms.parseBroadcastSms(plmn);
-            if (cbMessage != null) {
-                mCellBroadcastHandler.dispatchSmsMessage(cbMessage);
-            } else {
-                loge("error trying to parse broadcast SMS");
-            }
+            mCellBroadcastServiceManager.sendCdmaMessageToHandler(sms);
             return Intents.RESULT_SMS_HANDLED;
         }
 
@@ -149,9 +229,15 @@
                 break;
 
             case SmsEnvelope.TELESERVICE_SCPT:
-                mServiceCategoryProgramHandler.dispatchSmsMessage(sms);
+                mCellBroadcastServiceManager.sendCdmaScpMessageToHandler(sms, mScpCallback);
                 return Intents.RESULT_SMS_HANDLED;
 
+            case SmsEnvelope.TELESERVICE_FDEA_WAP:
+                if (!sms.preprocessCdmaFdeaWap()) {
+                    return Intents.RESULT_SMS_HANDLED;
+                }
+                teleService = SmsEnvelope.TELESERVICE_WAP;
+                // fall through
             case SmsEnvelope.TELESERVICE_WAP:
                 // handled below, after storage check
                 break;
@@ -180,8 +266,9 @@
 
     /**
      * Send an acknowledge message.
-     * @param success indicates that last message was successfully received.
-     * @param result result code indicating any error
+     *
+     * @param success  indicates that last message was successfully received.
+     * @param result   result code indicating any error
      * @param response callback message sent when operation completes.
      */
     @Override
@@ -197,27 +284,29 @@
 
     /**
      * Convert Android result code to CDMA SMS failure cause.
+     *
      * @param rc the Android SMS intent result value
      * @return 0 for success, or a CDMA SMS failure cause value
      */
     private static int resultToCause(int rc) {
         switch (rc) {
-        case Activity.RESULT_OK:
-        case Intents.RESULT_SMS_HANDLED:
-            // Cause code is ignored on success.
-            return 0;
-        case Intents.RESULT_SMS_OUT_OF_MEMORY:
-            return CommandsInterface.CDMA_SMS_FAIL_CAUSE_RESOURCE_SHORTAGE;
-        case Intents.RESULT_SMS_UNSUPPORTED:
-            return CommandsInterface.CDMA_SMS_FAIL_CAUSE_INVALID_TELESERVICE_ID;
-        case Intents.RESULT_SMS_GENERIC_ERROR:
-        default:
-            return CommandsInterface.CDMA_SMS_FAIL_CAUSE_OTHER_TERMINAL_PROBLEM;
+            case Activity.RESULT_OK:
+            case Intents.RESULT_SMS_HANDLED:
+                // Cause code is ignored on success.
+                return 0;
+            case Intents.RESULT_SMS_OUT_OF_MEMORY:
+                return CommandsInterface.CDMA_SMS_FAIL_CAUSE_RESOURCE_SHORTAGE;
+            case Intents.RESULT_SMS_UNSUPPORTED:
+                return CommandsInterface.CDMA_SMS_FAIL_CAUSE_INVALID_TELESERVICE_ID;
+            case Intents.RESULT_SMS_GENERIC_ERROR:
+            default:
+                return CommandsInterface.CDMA_SMS_FAIL_CAUSE_OTHER_TERMINAL_PROBLEM;
         }
     }
 
     /**
      * Handle {@link SmsEnvelope#TELESERVICE_VMN} and {@link SmsEnvelope#TELESERVICE_MWI}.
+     *
      * @param sms the message to process
      */
     private void handleVoicemailTeleservice(SmsMessage sms) {
@@ -245,8 +334,8 @@
      *
      * @param pdu The WAP-WDP PDU segment
      * @return a result code from {@link android.provider.Telephony.Sms.Intents}, or
-     *         {@link Activity#RESULT_OK} if the message has been broadcast
-     *         to applications
+     * {@link Activity#RESULT_OK} if the message has been broadcast
+     * to applications
      */
     private int processCdmaWapPdu(byte[] pdu, int referenceNumber, String address, String dispAddr,
             long timestamp) {
@@ -292,9 +381,11 @@
         byte[] userData = new byte[pdu.length - index];
         System.arraycopy(pdu, index, userData, 0, pdu.length - index);
         InboundSmsTracker tracker = TelephonyComponentFactory.getInstance()
-                .inject(InboundSmsTracker.class.getName()).makeInboundSmsTracker(
-                userData, timestamp, destinationPort, true, address, dispAddr, referenceNumber,
-                segment, totalSegments, true, HexDump.toHexString(userData), false /* isClass0 */,
+                .inject(InboundSmsTracker.class.getName()).makeInboundSmsTracker(mContext,
+                        userData, timestamp, destinationPort, true, address, dispAddr,
+                        referenceNumber,
+                        segment, totalSegments, true, HexDump.toHexString(userData),
+                        false /* isClass0 */,
                         mPhone.getSubId());
 
         // de-duping is done only for text messages
@@ -306,11 +397,12 @@
      * extra port fields.
      * - Some carriers make this mistake.
      * ex: MSGTYPE-TotalSegments-CurrentSegment
-     *       -SourcePortDestPort-SourcePortDestPort-OMADM PDU
+     * -SourcePortDestPort-SourcePortDestPort-OMADM PDU
+     *
      * @param origPdu The WAP-WDP PDU segment
-     * @param index Current Index while parsing the PDU.
+     * @param index   Current Index while parsing the PDU.
      * @return True if OrigPdu is OmaDM Push Message which has duplicate ports.
-     *         False if OrigPdu is NOT OmaDM Push Message which has duplicate ports.
+     * False if OrigPdu is NOT OmaDM Push Message which has duplicate ports.
      */
     private static boolean checkDuplicatePortOmadmWapPush(byte[] origPdu, int index) {
         index += 4;
@@ -343,4 +435,101 @@
         mMetrics.writeIncomingVoiceMailSms(mPhone.getPhoneId(),
                 android.telephony.SmsMessage.FORMAT_3GPP2);
     }
+
+    /**
+     * A broadcast receiver used for testing emergency cell broadcasts. To trigger test CDMA cell
+     * broadcasts with adb run e.g:
+     *
+     * adb shell am broadcast -a com.android.internal.telephony.cdma.TEST_TRIGGER_CELL_BROADCAST \
+     * --ei service_category 4097 \
+     * --es bearer_data_string 00031303900801C00D0101015C02D00002BFD1931054D208119313D3D10815D05 \
+     * 493925391C81193D48814D3D555120810D3D0D3D3925393C810D3D5539516480B481393D495120810D1539514 \
+     * 9053081054925693D390481553951253080D0C4D481413481354D500
+     *
+     * adb shell am broadcast -a com.android.internal.telephony.cdma.TEST_TRIGGER_CELL_BROADCAST \
+     * --ei service_category 4097 \
+     * --es bearer_data_string 00031303900801C00D0101015C02D00002BFD1931054D208119313D3D10815D05 \
+     * 493925391C81193D48814D3D555120810D3D0D3D3925393C810D3D5539516480B481393D495120810D1539514 \
+     * 9053081054925693D390481553951253080D0C4D481413481354D500 \
+     * --ei phone_id 0 \
+     */
+    private class CdmaCbTestBroadcastReceiver extends CbTestBroadcastReceiver {
+
+        CdmaCbTestBroadcastReceiver() {
+            super(TEST_ACTION);
+        }
+
+        @Override
+        protected void handleTestAction(Intent intent) {
+            SmsEnvelope envelope = new SmsEnvelope();
+            // the CdmaSmsAddress is not used for a test cell broadcast message, but needs to be
+            // supplied to avoid a null pointer exception in the platform
+            CdmaSmsAddress nonNullAddress = new CdmaSmsAddress();
+            nonNullAddress.origBytes = new byte[]{(byte) 0xFF};
+            envelope.origAddress = nonNullAddress;
+
+            // parse service category from intent
+            envelope.serviceCategory = intent.getIntExtra("service_category", -1);
+            if (envelope.serviceCategory == -1) {
+                log("No service category, ignoring CB test intent");
+                return;
+            }
+
+            // parse bearer data from intent
+            String bearerDataString = intent.getStringExtra("bearer_data_string");
+            envelope.bearerData = decodeHexString(bearerDataString);
+            if (envelope.bearerData == null) {
+                log("No bearer data, ignoring CB test intent");
+                return;
+            }
+
+            SmsMessage sms = new SmsMessage(new CdmaSmsAddress(), envelope);
+                mCellBroadcastServiceManager.sendCdmaMessageToHandler(sms);
+        }
+    }
+
+    /**
+     * A broadcast receiver used for testing CDMA SCP messages. To trigger test CDMA SCP messages
+     * with adb run e.g:
+     *
+     * adb shell am broadcast -a com.android.internal.telephony.cdma.TEST_TRIGGER_SCP_MESSAGE \
+     * --es originating_address_string 1234567890 \
+     * --es bearer_data_string 00031007B0122610880080B2091C5F1D3965DB95054D1CB2E1E883A6F41334E \
+     * 6CA830EEC882872DFC32F2E9E40
+     */
+    private class CdmaScpTestBroadcastReceiver extends CbTestBroadcastReceiver {
+
+        CdmaScpTestBroadcastReceiver() {
+            super(SCP_TEST_ACTION);
+        }
+
+        @Override
+        protected void handleTestAction(Intent intent) {
+            SmsEnvelope envelope = new SmsEnvelope();
+            // the CdmaSmsAddress is not used for a test SCP message, but needs to be supplied to
+            // avoid a null pointer exception in the platform
+            CdmaSmsAddress nonNullAddress = new CdmaSmsAddress();
+            nonNullAddress.origBytes = new byte[]{(byte) 0xFF};
+            envelope.origAddress = nonNullAddress;
+
+            // parse bearer data from intent
+            String bearerDataString = intent.getStringExtra("bearer_data_string");
+            envelope.bearerData = decodeHexString(bearerDataString);
+            if (envelope.bearerData == null) {
+                log("No bearer data, ignoring SCP test intent");
+                return;
+            }
+
+            CdmaSmsAddress origAddr = new CdmaSmsAddress();
+            String addressString = intent.getStringExtra("originating_address_string");
+            origAddr.origBytes = decodeHexString(addressString);
+            if (origAddr.origBytes == null) {
+                log("No address data, ignoring SCP test intent");
+                return;
+            }
+            SmsMessage sms = new SmsMessage(origAddr, envelope);
+            sms.parseSms();
+            mCellBroadcastServiceManager.sendCdmaScpMessageToHandler(sms, mScpCallback);
+        }
+    }
 }
diff --git a/src/java/com/android/internal/telephony/cdma/CdmaInformationRecords.java b/src/java/com/android/internal/telephony/cdma/CdmaInformationRecords.java
index 4987e51..1766d05 100644
--- a/src/java/com/android/internal/telephony/cdma/CdmaInformationRecords.java
+++ b/src/java/com/android/internal/telephony/cdma/CdmaInformationRecords.java
@@ -184,6 +184,7 @@
 
         public CdmaNumberInfoRec(int id, String number, int numberType, int numberPlan, int pi,
                 int si) {
+            this.id = id;
             this.number = number;
             this.numberType = (byte)numberType;
             this.numberPlan = (byte)numberPlan;
diff --git a/src/java/com/android/internal/telephony/cdma/CdmaMmiCode.java b/src/java/com/android/internal/telephony/cdma/CdmaMmiCode.java
index 7600c1a..88872c9 100644
--- a/src/java/com/android/internal/telephony/cdma/CdmaMmiCode.java
+++ b/src/java/com/android/internal/telephony/cdma/CdmaMmiCode.java
@@ -16,24 +16,23 @@
 
 package com.android.internal.telephony.cdma;
 
+import android.compat.annotation.UnsupportedAppUsage;
 import android.content.Context;
-
-import com.android.internal.telephony.CommandException;
-import com.android.internal.telephony.GsmCdmaPhone;
-import com.android.internal.telephony.uicc.UiccCardApplication;
-import com.android.internal.telephony.uicc.IccCardApplicationStatus.AppState;
-import com.android.internal.telephony.MmiCode;
-import com.android.internal.telephony.Phone;
-
-import android.annotation.UnsupportedAppUsage;
 import android.os.AsyncResult;
 import android.os.Handler;
 import android.os.Message;
 import android.os.ResultReceiver;
-import android.telephony.Rlog;
 
-import java.util.regex.Pattern;
+import com.android.internal.telephony.CommandException;
+import com.android.internal.telephony.GsmCdmaPhone;
+import com.android.internal.telephony.MmiCode;
+import com.android.internal.telephony.Phone;
+import com.android.internal.telephony.uicc.IccCardApplicationStatus.AppState;
+import com.android.internal.telephony.uicc.UiccCardApplication;
+import com.android.telephony.Rlog;
+
 import java.util.regex.Matcher;
+import java.util.regex.Pattern;
 
 /**
  * This class can handle Puk code Mmi
@@ -381,4 +380,10 @@
     public ResultReceiver getUssdCallbackReceiver() {
         return null;
     }
+
+    @Override
+    public boolean isNetworkInitiatedUssd() {
+        Rlog.w(LOG_TAG, "isNetworkInitiated is not implemented in CdmaMmiCode");
+        return false;
+    }
 }
diff --git a/src/java/com/android/internal/telephony/cdma/CdmaSMSDispatcher.java b/src/java/com/android/internal/telephony/cdma/CdmaSMSDispatcher.java
old mode 100755
new mode 100644
index 04bfbf2..cb7d5a1
--- a/src/java/com/android/internal/telephony/cdma/CdmaSMSDispatcher.java
+++ b/src/java/com/android/internal/telephony/cdma/CdmaSMSDispatcher.java
@@ -16,24 +16,25 @@
 
 package com.android.internal.telephony.cdma;
 
-import android.annotation.UnsupportedAppUsage;
+import static com.android.internal.telephony.SmsResponse.NO_ERROR_CODE;
+
+import android.compat.annotation.UnsupportedAppUsage;
 import android.os.Message;
-import android.telephony.Rlog;
 import android.telephony.ServiceState;
 import android.telephony.TelephonyManager;
 import android.util.Pair;
 
+import com.android.internal.telephony.GsmAlphabet.TextEncodingDetails;
 import com.android.internal.telephony.GsmCdmaPhone;
 import com.android.internal.telephony.Phone;
 import com.android.internal.telephony.PhoneConstants;
-import com.android.internal.telephony.SmsConstants;
 import com.android.internal.telephony.SMSDispatcher;
+import com.android.internal.telephony.SmsConstants;
 import com.android.internal.telephony.SmsDispatchersController;
 import com.android.internal.telephony.SmsHeader;
-import com.android.internal.telephony.util.SMSDispatcherUtil;
-import com.android.internal.telephony.GsmAlphabet.TextEncodingDetails;
 import com.android.internal.telephony.SmsMessageBase;
-
+import com.android.internal.telephony.util.SMSDispatcherUtil;
+import com.android.telephony.Rlog;
 
 public class CdmaSMSDispatcher extends SMSDispatcher {
     private static final String TAG = "CdmaSMSDispatcher";
@@ -100,35 +101,44 @@
      */
     @UnsupportedAppUsage
     private void handleCdmaStatusReport(SmsMessage sms) {
+        byte[] pdu = sms.getPdu();
+        int messageRef = sms.mMessageRef;
+        boolean handled = false;
         for (int i = 0, count = deliveryPendingList.size(); i < count; i++) {
             SmsTracker tracker = deliveryPendingList.get(i);
-            if (tracker.mMessageRef == sms.mMessageRef) {
+            if (tracker.mMessageRef == messageRef) {
                 Pair<Boolean, Boolean> result =
-                        mSmsDispatchersController.handleSmsStatusReport(tracker, getFormat(),
-                                sms.getPdu());
+                        mSmsDispatchersController.handleSmsStatusReport(tracker, getFormat(), pdu);
                 if (result.second) {
                     deliveryPendingList.remove(i);
                 }
-                break;  // Only expect to see one tracker matching this message.
+                handled = true;
+                break; // Only expect to see one tracker matching this message.
             }
         }
+        if (!handled) {
+            // Try to find the sent SMS from the map in ImsSmsDispatcher.
+            mSmsDispatchersController.handleSentOverImsStatusReport(messageRef, getFormat(), pdu);
+        }
     }
 
     /** {@inheritDoc} */
     @Override
     public void sendSms(SmsTracker tracker) {
+        int ss = mPhone.getServiceState().getState();
+
         Rlog.d(TAG, "sendSms: "
                 + " isIms()=" + isIms()
                 + " mRetryCount=" + tracker.mRetryCount
                 + " mImsRetry=" + tracker.mImsRetry
                 + " mMessageRef=" + tracker.mMessageRef
                 + " mUsesImsServiceForIms=" + tracker.mUsesImsServiceForIms
-                + " SS=" + mPhone.getServiceState().getState());
+                + " SS=" + ss
+                + " id=" + tracker.mMessageId);
 
-        int ss = mPhone.getServiceState().getState();
         // if sms over IMS is not supported on data and voice is not available...
         if (!isIms() && ss != ServiceState.STATE_IN_SERVICE) {
-            tracker.onFailed(mContext, getNotInServiceError(ss), 0/*errorCode*/);
+            tracker.onFailed(mContext, getNotInServiceError(ss), NO_ERROR_CODE);
             return;
         }
 
@@ -137,11 +147,13 @@
 
         int currentDataNetwork = mPhone.getServiceState().getDataNetworkType();
         boolean imsSmsDisabled = (currentDataNetwork == TelephonyManager.NETWORK_TYPE_EHRPD
-                    || (ServiceState.isLte(currentDataNetwork)
-                    && !mPhone.getServiceStateTracker().isConcurrentVoiceAndDataAllowed()))
-                    && mPhone.getServiceState().getVoiceNetworkType()
-                    == TelephonyManager.NETWORK_TYPE_1xRTT
-                    && ((GsmCdmaPhone) mPhone).mCT.mState != PhoneConstants.State.IDLE;
+                || (currentDataNetwork == TelephonyManager.NETWORK_TYPE_LTE
+                || currentDataNetwork == TelephonyManager.NETWORK_TYPE_LTE_CA
+                || currentDataNetwork == TelephonyManager.NETWORK_TYPE_NR)
+                && !mPhone.getServiceStateTracker().isConcurrentVoiceAndDataAllowed())
+                && mPhone.getServiceState().getVoiceNetworkType()
+                        == TelephonyManager.NETWORK_TYPE_1xRTT
+                && ((GsmCdmaPhone) mPhone).mCT.mState != PhoneConstants.State.IDLE;
 
         // sms over cdma is used:
         //   if sms over IMS is not supported AND
@@ -151,7 +163,11 @@
         //   SMS over IMS is being handled by the ImsSmsDispatcher implementation and has indicated
         //   that the message should fall back to sending over CS.
         if (0 == tracker.mImsRetry && !isIms() || imsSmsDisabled || tracker.mUsesImsServiceForIms) {
-            mCi.sendCdmaSms(pdu, reply);
+            if (tracker.mRetryCount == 0 && tracker.mExpectMore) {
+                mCi.sendCdmaSMSExpectMore(pdu, reply);
+            } else {
+                mCi.sendCdmaSms(pdu, reply);
+            }
         } else {
             mCi.sendImsCdmaSms(pdu, tracker.mImsRetry, tracker.mMessageRef, reply);
             // increment it here, so in case of SMS_FAIL_RETRY over IMS
diff --git a/src/java/com/android/internal/telephony/cdma/CdmaServiceCategoryProgramHandler.java b/src/java/com/android/internal/telephony/cdma/CdmaServiceCategoryProgramHandler.java
deleted file mode 100644
index 5412911..0000000
--- a/src/java/com/android/internal/telephony/cdma/CdmaServiceCategoryProgramHandler.java
+++ /dev/null
@@ -1,196 +0,0 @@
-/*
- * Copyright (C) 2013 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.cdma;
-
-import android.Manifest;
-import android.app.Activity;
-import android.app.AppOpsManager;
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.os.Bundle;
-import android.os.Message;
-import android.provider.Telephony.Sms.Intents;
-import android.telephony.PhoneNumberUtils;
-import android.telephony.SubscriptionManager;
-import android.telephony.cdma.CdmaSmsCbProgramData;
-import android.telephony.cdma.CdmaSmsCbProgramResults;
-
-import com.android.internal.telephony.CommandsInterface;
-import com.android.internal.telephony.WakeLockStateMachine;
-import com.android.internal.telephony.cdma.sms.BearerData;
-import com.android.internal.telephony.cdma.sms.CdmaSmsAddress;
-import com.android.internal.telephony.cdma.sms.SmsEnvelope;
-
-import java.io.ByteArrayOutputStream;
-import java.io.DataOutputStream;
-import java.io.IOException;
-import java.util.ArrayList;
-
-/**
- * Handle CDMA Service Category Program Data requests and responses.
- */
-public final class CdmaServiceCategoryProgramHandler extends WakeLockStateMachine {
-
-    final CommandsInterface mCi;
-
-    /**
-     * Create a new CDMA inbound SMS handler.
-     */
-    CdmaServiceCategoryProgramHandler(Context context, CommandsInterface commandsInterface) {
-        super("CdmaServiceCategoryProgramHandler", context, null);
-        mContext = context;
-        mCi = commandsInterface;
-    }
-
-    /**
-     * Create a new State machine for SCPD requests.
-     * @param context the context to use
-     * @param commandsInterface the radio commands interface
-     * @return the new SCPD handler
-     */
-    static CdmaServiceCategoryProgramHandler makeScpHandler(Context context,
-            CommandsInterface commandsInterface) {
-        CdmaServiceCategoryProgramHandler handler = new CdmaServiceCategoryProgramHandler(
-                context, commandsInterface);
-        handler.start();
-        return handler;
-    }
-
-    /**
-     * Handle Cell Broadcast messages from {@code CdmaInboundSmsHandler}.
-     * 3GPP-format Cell Broadcast messages sent from radio are handled in the subclass.
-     *
-     * @param message the message to process
-     * @return true if an ordered broadcast was sent; false on failure
-     */
-    @Override
-    protected boolean handleSmsMessage(Message message) {
-        if (message.obj instanceof SmsMessage) {
-            return handleServiceCategoryProgramData((SmsMessage) message.obj);
-        } else {
-            loge("handleMessage got object of type: " + message.obj.getClass().getName());
-            return false;
-        }
-    }
-
-
-    /**
-     * Send SCPD request to CellBroadcastReceiver as an ordered broadcast.
-     * @param sms the CDMA SmsMessage containing the SCPD request
-     * @return true if an ordered broadcast was sent; false on failure
-     */
-    private boolean handleServiceCategoryProgramData(SmsMessage sms) {
-        ArrayList<CdmaSmsCbProgramData> programDataList = sms.getSmsCbProgramData();
-        if (programDataList == null) {
-            loge("handleServiceCategoryProgramData: program data list is null!");
-            return false;
-        }
-
-        Intent intent = new Intent(Intents.SMS_SERVICE_CATEGORY_PROGRAM_DATA_RECEIVED_ACTION);
-        intent.putExtra("sender", sms.getOriginatingAddress());
-        intent.putParcelableArrayListExtra("program_data", programDataList);
-        SubscriptionManager.putPhoneIdAndSubIdExtra(intent, mPhone.getPhoneId());
-
-        String[] pkgs = mContext.getResources().getStringArray(
-                com.android.internal.R.array.config_defaultCellBroadcastReceiverPkgs);
-        mReceiverCount.addAndGet(pkgs.length);
-        for (String pkg : pkgs) {
-            intent.setPackage(pkg);
-            mContext.sendOrderedBroadcast(intent, Manifest.permission.RECEIVE_SMS,
-                    AppOpsManager.OP_RECEIVE_SMS, mScpResultsReceiver,
-                    getHandler(), Activity.RESULT_OK, null, null);
-        }
-        return true;
-    }
-
-    /**
-     * Broadcast receiver to handle results of ordered broadcast. Sends the SCPD results
-     * as a reply SMS, then sends a message to state machine to transition to idle.
-     */
-    private final BroadcastReceiver mScpResultsReceiver = new BroadcastReceiver() {
-        @Override
-        public void onReceive(Context context, Intent intent) {
-            sendScpResults();
-            if (DBG) log("mScpResultsReceiver finished");
-            if (mReceiverCount.decrementAndGet() == 0) {
-                sendMessage(EVENT_BROADCAST_COMPLETE);
-            }
-        }
-
-        private void sendScpResults() {
-            int resultCode = getResultCode();
-            if ((resultCode != Activity.RESULT_OK) && (resultCode != Intents.RESULT_SMS_HANDLED)) {
-                loge("SCP results error: result code = " + resultCode);
-                return;
-            }
-            Bundle extras = getResultExtras(false);
-            if (extras == null) {
-                loge("SCP results error: missing extras");
-                return;
-            }
-            String sender = extras.getString("sender");
-            if (sender == null) {
-                loge("SCP results error: missing sender extra.");
-                return;
-            }
-            ArrayList<CdmaSmsCbProgramResults> results
-                    = extras.getParcelableArrayList("results");
-            if (results == null) {
-                loge("SCP results error: missing results extra.");
-                return;
-            }
-
-            BearerData bData = new BearerData();
-            bData.messageType = BearerData.MESSAGE_TYPE_SUBMIT;
-            bData.messageId = SmsMessage.getNextMessageId();
-            bData.serviceCategoryProgramResults = results;
-            byte[] encodedBearerData = BearerData.encode(bData);
-
-            ByteArrayOutputStream baos = new ByteArrayOutputStream(100);
-            DataOutputStream dos = new DataOutputStream(baos);
-            try {
-                dos.writeInt(SmsEnvelope.TELESERVICE_SCPT);
-                dos.writeInt(0); //servicePresent
-                dos.writeInt(0); //serviceCategory
-                CdmaSmsAddress destAddr = CdmaSmsAddress.parse(
-                        PhoneNumberUtils.cdmaCheckAndProcessPlusCodeForSms(sender));
-                dos.write(destAddr.digitMode);
-                dos.write(destAddr.numberMode);
-                dos.write(destAddr.ton); // number_type
-                dos.write(destAddr.numberPlan);
-                dos.write(destAddr.numberOfDigits);
-                dos.write(destAddr.origBytes, 0, destAddr.origBytes.length); // digits
-                // Subaddress is not supported.
-                dos.write(0); //subaddressType
-                dos.write(0); //subaddr_odd
-                dos.write(0); //subaddr_nbr_of_digits
-                dos.write(encodedBearerData.length);
-                dos.write(encodedBearerData, 0, encodedBearerData.length);
-                // Ignore the RIL response. TODO: implement retry if SMS send fails.
-                mCi.sendCdmaSms(baos.toByteArray(), null);
-            } catch (IOException e) {
-                loge("exception creating SCP results PDU", e);
-            } finally {
-                try {
-                    dos.close();
-                } catch (IOException ignored) {
-                }
-            }
-        }
-    };
-}
diff --git a/src/java/com/android/internal/telephony/cdma/CdmaSubscriptionSourceManager.java b/src/java/com/android/internal/telephony/cdma/CdmaSubscriptionSourceManager.java
index bc09e77..b261dc5 100644
--- a/src/java/com/android/internal/telephony/cdma/CdmaSubscriptionSourceManager.java
+++ b/src/java/com/android/internal/telephony/cdma/CdmaSubscriptionSourceManager.java
@@ -16,11 +16,7 @@
 
 package com.android.internal.telephony.cdma;
 
-import java.util.concurrent.atomic.AtomicInteger;
-
-import android.annotation.UnsupportedAppUsage;
-import com.android.internal.telephony.CommandsInterface;
-import com.android.internal.telephony.Phone;
+import android.compat.annotation.UnsupportedAppUsage;
 import android.content.Context;
 import android.os.AsyncResult;
 import android.os.Handler;
@@ -28,7 +24,12 @@
 import android.os.Registrant;
 import android.os.RegistrantList;
 import android.provider.Settings;
-import android.telephony.Rlog;
+
+import com.android.internal.telephony.CommandsInterface;
+import com.android.internal.telephony.Phone;
+import com.android.telephony.Rlog;
+
+import java.util.concurrent.atomic.AtomicInteger;
 
 /**
  * Class that handles the CDMA subscription source changed events from RIL
diff --git a/src/java/com/android/internal/telephony/cdma/EriInfo.java b/src/java/com/android/internal/telephony/cdma/EriInfo.java
index 3e9680b..bd27f94 100644
--- a/src/java/com/android/internal/telephony/cdma/EriInfo.java
+++ b/src/java/com/android/internal/telephony/cdma/EriInfo.java
@@ -16,14 +16,16 @@
 
 package com.android.internal.telephony.cdma;
 
+import android.telephony.CdmaEriInformation;
+
 public final class EriInfo {
 
-    public static final int ROAMING_INDICATOR_ON    = 0;
-    public static final int ROAMING_INDICATOR_OFF   = 1;
-    public static final int ROAMING_INDICATOR_FLASH = 2;
+    public static final int ROAMING_INDICATOR_ON    = CdmaEriInformation.ERI_ON;
+    public static final int ROAMING_INDICATOR_OFF   = CdmaEriInformation.ERI_OFF;
+    public static final int ROAMING_INDICATOR_FLASH = CdmaEriInformation.ERI_FLASH;
 
-    public static final int ROAMING_ICON_MODE_NORMAL    = 0;
-    public static final int ROAMING_ICON_MODE_FLASH     = 1;
+    public static final int ROAMING_ICON_MODE_NORMAL    = CdmaEriInformation.ERI_ICON_MODE_NORMAL;
+    public static final int ROAMING_ICON_MODE_FLASH     = CdmaEriInformation.ERI_ICON_MODE_FLASH;
 
     public int roamingIndicator;
     public int iconIndex;
diff --git a/src/java/com/android/internal/telephony/cdma/EriManager.java b/src/java/com/android/internal/telephony/cdma/EriManager.java
index eb19963..8c67149 100644
--- a/src/java/com/android/internal/telephony/cdma/EriManager.java
+++ b/src/java/com/android/internal/telephony/cdma/EriManager.java
@@ -16,18 +16,17 @@
 
 package com.android.internal.telephony.cdma;
 
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
 import android.content.Context;
 import android.content.res.Resources;
 import android.content.res.XmlResourceParser;
 import android.os.PersistableBundle;
 import android.telephony.CarrierConfigManager;
-import android.telephony.Rlog;
 import android.util.Xml;
 
 import com.android.internal.telephony.Phone;
-import com.android.internal.util.XmlUtils;
-
+import com.android.internal.telephony.util.XmlUtils;
+import com.android.telephony.Rlog;
 
 import org.xmlpull.v1.XmlPullParser;
 import org.xmlpull.v1.XmlPullParserException;
diff --git a/src/java/com/android/internal/telephony/cdma/SmsMessageConverter.java b/src/java/com/android/internal/telephony/cdma/SmsMessageConverter.java
index 4ac2dea..84da264 100644
--- a/src/java/com/android/internal/telephony/cdma/SmsMessageConverter.java
+++ b/src/java/com/android/internal/telephony/cdma/SmsMessageConverter.java
@@ -17,40 +17,11 @@
 package com.android.internal.telephony.cdma;
 
 import android.hardware.radio.V1_0.CdmaSmsMessage;
-import android.os.Parcel;
-import android.os.SystemProperties;
-import android.telephony.PhoneNumberUtils;
-import android.telephony.SmsCbLocation;
-import android.telephony.SmsCbMessage;
-import android.telephony.TelephonyManager;
-import android.telephony.cdma.CdmaSmsCbProgramData;
-import android.telephony.Rlog;
-import android.util.Log;
-import android.text.TextUtils;
-import android.content.res.Resources;
 
-import com.android.internal.telephony.GsmAlphabet.TextEncodingDetails;
-import com.android.internal.telephony.SmsConstants;
-import com.android.internal.telephony.SmsHeader;
 import com.android.internal.telephony.SmsMessageBase;
-import com.android.internal.telephony.TelephonyProperties;
-import com.android.internal.telephony.cdma.sms.BearerData;
 import com.android.internal.telephony.cdma.sms.CdmaSmsAddress;
 import com.android.internal.telephony.cdma.sms.CdmaSmsSubaddress;
 import com.android.internal.telephony.cdma.sms.SmsEnvelope;
-import com.android.internal.telephony.cdma.sms.UserData;
-import com.android.internal.telephony.uicc.IccUtils;
-import com.android.internal.util.BitwiseInputStream;
-import com.android.internal.util.HexDump;
-import com.android.internal.telephony.Sms7BitEncodingTranslator;
-
-import java.io.BufferedOutputStream;
-import java.io.ByteArrayInputStream;
-import java.io.ByteArrayOutputStream;
-import java.io.DataInputStream;
-import java.io.DataOutputStream;
-import java.io.IOException;
-import java.util.ArrayList;
 
 /**
  * A Factory class to convert from RIL to Framework SMS
diff --git a/src/java/com/android/internal/telephony/cdnr/CarrierConfigEfData.java b/src/java/com/android/internal/telephony/cdnr/CarrierConfigEfData.java
index b17b492..faa5e1f 100644
--- a/src/java/com/android/internal/telephony/cdnr/CarrierConfigEfData.java
+++ b/src/java/com/android/internal/telephony/cdnr/CarrierConfigEfData.java
@@ -19,12 +19,12 @@
 import android.annotation.NonNull;
 import android.os.PersistableBundle;
 import android.telephony.CarrierConfigManager;
-import android.telephony.Rlog;
 import android.text.TextUtils;
 
 import com.android.internal.telephony.uicc.IccRecords;
 import com.android.internal.telephony.uicc.IccRecords.OperatorPlmnInfo;
 import com.android.internal.telephony.uicc.IccRecords.PlmnNetworkName;
+import com.android.telephony.Rlog;
 
 import java.util.ArrayList;
 import java.util.Arrays;
diff --git a/src/java/com/android/internal/telephony/cdnr/CarrierDisplayNameResolver.java b/src/java/com/android/internal/telephony/cdnr/CarrierDisplayNameResolver.java
index 87ee39d..dad9985 100644
--- a/src/java/com/android/internal/telephony/cdnr/CarrierDisplayNameResolver.java
+++ b/src/java/com/android/internal/telephony/cdnr/CarrierDisplayNameResolver.java
@@ -32,7 +32,6 @@
 import android.content.res.Resources;
 import android.os.PersistableBundle;
 import android.telephony.CarrierConfigManager;
-import android.telephony.Rlog;
 import android.telephony.ServiceState;
 import android.text.TextUtils;
 import android.util.LocalLog;
@@ -49,6 +48,7 @@
 import com.android.internal.telephony.uicc.RuimRecords;
 import com.android.internal.telephony.uicc.SIMRecords;
 import com.android.internal.util.IndentingPrintWriter;
+import com.android.telephony.Rlog;
 
 import java.util.Arrays;
 import java.util.Collections;
@@ -337,7 +337,7 @@
         if (useRootLocale) r.getConfiguration().setLocale(Locale.ROOT);
         String[] wfcSpnFormats = r.getStringArray(com.android.internal.R.array.wfcSpnFormats);
         WfcCarrierNameFormatter wfcFormatter = new WfcCarrierNameFormatter(config, wfcSpnFormats,
-                getServiceState().getVoiceRegState() == ServiceState.STATE_POWER_OFF);
+                getServiceState().getState() == ServiceState.STATE_POWER_OFF);
 
         // Override the spn, data spn, plmn by wifi-calling
         String wfcSpn = wfcFormatter.formatVoiceName(rawCarrierDisplayNameData.getSpn());
@@ -380,7 +380,7 @@
         boolean forceDisplayNoService =
                 mPhone.getServiceStateTracker().shouldForceDisplayNoService() && !isSimReady;
         ServiceState ss = getServiceState();
-        if (ss.getVoiceRegState() == ServiceState.STATE_POWER_OFF
+        if (ss.getState() == ServiceState.STATE_POWER_OFF
                 || forceDisplayNoService || !Phone.isEmergencyCallOnly()) {
             plmn = mContext.getResources().getString(
                     com.android.internal.R.string.lockscreen_carrier_default);
@@ -545,7 +545,7 @@
      * @param ss service state.
      */
     private static int getCombinedRegState(ServiceState ss) {
-        if (ss.getVoiceRegState() != ServiceState.STATE_IN_SERVICE) return ss.getDataRegState();
-        return ss.getVoiceRegState();
+        if (ss.getState() != ServiceState.STATE_IN_SERVICE) return ss.getDataRegistrationState();
+        return ss.getState();
     }
 }
diff --git a/src/java/com/android/internal/telephony/dataconnection/AccessNetworksManager.java b/src/java/com/android/internal/telephony/dataconnection/AccessNetworksManager.java
index 421feb9..9460ddc 100644
--- a/src/java/com/android/internal/telephony/dataconnection/AccessNetworksManager.java
+++ b/src/java/com/android/internal/telephony/dataconnection/AccessNetworksManager.java
@@ -23,6 +23,7 @@
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.content.ServiceConnection;
+import android.content.pm.PackageManager;
 import android.os.Handler;
 import android.os.IBinder;
 import android.os.Message;
@@ -32,10 +33,9 @@
 import android.os.RemoteException;
 import android.os.UserHandle;
 import android.telephony.AccessNetworkConstants.AccessNetworkType;
+import android.telephony.Annotation.ApnType;
 import android.telephony.CarrierConfigManager;
-import android.telephony.Rlog;
 import android.telephony.data.ApnSetting;
-import android.telephony.data.ApnSetting.ApnType;
 import android.telephony.data.IQualifiedNetworksService;
 import android.telephony.data.IQualifiedNetworksServiceCallback;
 import android.telephony.data.QualifiedNetworksService;
@@ -44,6 +44,7 @@
 
 import com.android.internal.telephony.Phone;
 import com.android.internal.util.IndentingPrintWriter;
+import com.android.telephony.Rlog;
 
 import java.io.FileDescriptor;
 import java.util.ArrayList;
@@ -160,7 +161,6 @@
         @Override
         public void onServiceDisconnected(ComponentName name) {
             if (DBG) log("onServiceDisconnected " + name);
-            mIQualifiedNetworksService.asBinder().unlinkToDeath(mDeathRecipient, 0);
             mTargetBindingPackageName = null;
         }
     }
@@ -212,8 +212,14 @@
 
         IntentFilter intentFilter = new IntentFilter();
         intentFilter.addAction(CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED);
-        phone.getContext().registerReceiverAsUser(mConfigChangedReceiver, UserHandle.ALL,
-                intentFilter, null, null);
+        try {
+            Context contextAsUser = phone.getContext().createPackageContextAsUser(
+                phone.getContext().getPackageName(), 0, UserHandle.ALL);
+            contextAsUser.registerReceiver(mConfigChangedReceiver, intentFilter,
+                null /* broadcastPermission */, null);
+        } catch (PackageManager.NameNotFoundException e) {
+            Rlog.e(TAG, "Package name not found: " + e.getMessage());
+        }
         sendEmptyMessage(EVENT_BIND_QUALIFIED_NETWORKS_SERVICE);
     }
 
@@ -238,7 +244,9 @@
      * configuration from carrier config if it exists. If not, read it from resources.
      */
     private void bindQualifiedNetworksService() {
+        Intent intent = null;
         String packageName = getQualifiedNetworksServicePackageName();
+        String className = getQualifiedNetworksServiceClassName();
 
         if (DBG) log("Qualified network service package = " + packageName);
         if (TextUtils.isEmpty(packageName)) {
@@ -246,6 +254,15 @@
             return;
         }
 
+        if (TextUtils.isEmpty(className)) {
+            intent = new Intent(QualifiedNetworksService.QUALIFIED_NETWORKS_SERVICE_INTERFACE);
+            intent.setPackage(packageName);
+        } else {
+            ComponentName cm = new ComponentName(packageName, className);
+            intent = new Intent(QualifiedNetworksService.QUALIFIED_NETWORKS_SERVICE_INTERFACE)
+                    .setComponent(cm);
+        }
+
         if (TextUtils.equals(packageName, mTargetBindingPackageName)) {
             if (DBG) log("Service " + packageName + " already bound or being bound.");
             return;
@@ -266,11 +283,8 @@
         try {
             mServiceConnection = new QualifiedNetworksServiceConnection();
             log("bind to " + packageName);
-            if (!mPhone.getContext().bindService(
-                    new Intent(QualifiedNetworksService.QUALIFIED_NETWORKS_SERVICE_INTERFACE)
-                            .setPackage(packageName),
-                    mServiceConnection,
-                    Context.BIND_AUTO_CREATE)) {
+            if (!mPhone.getContext().bindService(intent, mServiceConnection,
+                        Context.BIND_AUTO_CREATE)) {
                 loge("Cannot bind to the qualified networks service.");
                 return;
             }
@@ -306,6 +320,30 @@
         return packageName;
     }
 
+    /**
+     * Get the qualified network service class name.
+     *
+     * @return class name of the qualified networks service package.
+     */
+    private String getQualifiedNetworksServiceClassName() {
+        // Read package name from the resource
+        String className = mPhone.getContext().getResources().getString(
+                com.android.internal.R.string.config_qualified_networks_service_class);
+
+        PersistableBundle b = mCarrierConfigManager.getConfigForSubId(mPhone.getSubId());
+
+        if (b != null) {
+            // If carrier config overrides it, use the one from carrier config
+            String carrierConfigClassName =  b.getString(CarrierConfigManager
+                    .KEY_CARRIER_QUALIFIED_NETWORKS_SERVICE_CLASS_OVERRIDE_STRING);
+            if (!TextUtils.isEmpty(carrierConfigClassName)) {
+                if (DBG) log("Found carrier config override " + carrierConfigClassName);
+                className = carrierConfigClassName;
+            }
+        }
+
+        return className;
+    }
 
     private @NonNull List<QualifiedNetworks> getQualifiedNetworksList() {
         List<QualifiedNetworks> qualifiedNetworksList = new ArrayList<>();
diff --git a/src/java/com/android/internal/telephony/dataconnection/ApnConfigType.java b/src/java/com/android/internal/telephony/dataconnection/ApnConfigType.java
new file mode 100644
index 0000000..827dbdd
--- /dev/null
+++ b/src/java/com/android/internal/telephony/dataconnection/ApnConfigType.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2020 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.dataconnection;
+
+import android.telephony.Annotation;
+
+/**
+ * Container of network configuration settings relevant for telephony module.
+ *
+ */
+public class ApnConfigType {
+
+    private final int mType;
+    private final int mPriority;
+
+    public ApnConfigType(@Annotation.ApnType int type, int priority) {
+        mType = type;
+        mPriority = priority;
+    }
+
+    /**
+     * Returns the apn type of this config type
+     * @return Type of apn.
+     */
+    public int getType() {
+        return mType;
+    }
+
+    /**
+     * Returns the priority of this apn config type.
+     * @return The priority of this apn.
+     */
+    public int getPriority() {
+        return mPriority;
+    }
+}
diff --git a/src/java/com/android/internal/telephony/dataconnection/ApnConfigTypeRepository.java b/src/java/com/android/internal/telephony/dataconnection/ApnConfigTypeRepository.java
new file mode 100644
index 0000000..f7dbcfc
--- /dev/null
+++ b/src/java/com/android/internal/telephony/dataconnection/ApnConfigTypeRepository.java
@@ -0,0 +1,127 @@
+/*
+ * Copyright (C) 2020 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.dataconnection;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.PersistableBundle;
+import android.telephony.Annotation;
+import android.telephony.CarrierConfigManager;
+import android.telephony.Rlog;
+import android.telephony.data.ApnSetting;
+import android.util.ArrayMap;
+
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Hard coded configuration of specific network types that the telephony module needs.
+ * Formerly stored in network attributes within the resources file.
+ */
+public class ApnConfigTypeRepository {
+
+    private static final String TAG = ApnConfigTypeRepository.class.getSimpleName();
+
+    private final Map<Integer, ApnConfigType> mConfigTypeMap;
+
+    public ApnConfigTypeRepository(PersistableBundle carrierConfig) {
+        mConfigTypeMap = new HashMap<>();
+        setup(carrierConfig);
+    }
+
+    /**
+     * Gets list of apn config types.
+     * @return All apn config types.
+     */
+    public Collection<ApnConfigType> getTypes() {
+        return mConfigTypeMap.values();
+    }
+
+    /**
+     * Gets the apn config type by apn type.
+     * @param type The ApnType to search for.
+     * @return The config type matching the given apn type.
+     */
+    @Nullable
+    public ApnConfigType getByType(@Annotation.ApnType int type) {
+        return mConfigTypeMap.get(type);
+    }
+
+    private void setup(PersistableBundle carrierConfig) {
+        addApns(getCarrierApnTypeMap(CarrierConfigManager.getDefaultConfig()));
+        addApns(getCarrierApnTypeMap(carrierConfig));
+    }
+
+    private void addApns(Map<Integer, Integer> apnTypeMap) {
+        add(ApnSetting.TYPE_DEFAULT, apnTypeMap);
+        add(ApnSetting.TYPE_MMS, apnTypeMap);
+        add(ApnSetting.TYPE_SUPL, apnTypeMap);
+        add(ApnSetting.TYPE_DUN, apnTypeMap);
+        add(ApnSetting.TYPE_HIPRI, apnTypeMap);
+        add(ApnSetting.TYPE_FOTA, apnTypeMap);
+        add(ApnSetting.TYPE_IMS, apnTypeMap);
+        add(ApnSetting.TYPE_CBS, apnTypeMap);
+        add(ApnSetting.TYPE_IA, apnTypeMap);
+        add(ApnSetting.TYPE_EMERGENCY, apnTypeMap);
+        add(ApnSetting.TYPE_MCX, apnTypeMap);
+        add(ApnSetting.TYPE_XCAP, apnTypeMap);
+    }
+
+    @NonNull
+    private Map<Integer, Integer> getCarrierApnTypeMap(PersistableBundle carrierConfig) {
+        if (carrierConfig == null) {
+            Rlog.w(TAG, "carrier config is null");
+            return new ArrayMap<>();
+        }
+
+        final String[] apnTypeConfig =
+                carrierConfig.getStringArray(CarrierConfigManager.KEY_APN_PRIORITY_STRING_ARRAY);
+
+        final Map<Integer, Integer> apnTypeMap = new ArrayMap<>();
+        if (apnTypeConfig != null) {
+            for (final String entry : apnTypeConfig) {
+                try {
+                    final String[] keyValue = entry.split(":");
+                    if (keyValue.length != 2) {
+                        Rlog.e(TAG, "Apn type entry must have exactly one ':'");
+                    } else if (keyValue[0].contains(",")) {
+                        //getApnTypesBitmaskFromString parses commas to a list, not valid here.
+                        Rlog.e(TAG, "Invalid apn type name, entry: " + entry);
+                    } else {
+                        int apnTypeBitmask = ApnSetting.getApnTypesBitmaskFromString(keyValue[0]);
+                        if (apnTypeBitmask > 0) {
+                            apnTypeMap.put(apnTypeBitmask, Integer.parseInt(keyValue[1]));
+                        } else {
+                            Rlog.e(TAG, "Invalid apn type name, entry: " + entry);
+                        }
+                    }
+
+                } catch (Exception ex) {
+                    Rlog.e(TAG, "Exception on apn type entry: " + entry + "\n", ex);
+                }
+            }
+        }
+        return apnTypeMap;
+    }
+
+    private void add(@Annotation.ApnType int type, Map<Integer, Integer> apnTypeMap) {
+        if (apnTypeMap.containsKey(type)) {
+            mConfigTypeMap.put(type, new ApnConfigType(type, apnTypeMap.get(type)));
+        }
+    }
+}
diff --git a/src/java/com/android/internal/telephony/dataconnection/ApnContext.java b/src/java/com/android/internal/telephony/dataconnection/ApnContext.java
index f86230c..75ba120 100644
--- a/src/java/com/android/internal/telephony/dataconnection/ApnContext.java
+++ b/src/java/com/android/internal/telephony/dataconnection/ApnContext.java
@@ -16,15 +16,12 @@
 
 package com.android.internal.telephony.dataconnection;
 
-import android.app.PendingIntent;
-import android.net.ConnectivityManager;
+import android.annotation.Nullable;
 import android.net.NetworkCapabilities;
-import android.net.NetworkConfig;
 import android.net.NetworkRequest;
 import android.os.Message;
-import android.telephony.Rlog;
+import android.telephony.Annotation.ApnType;
 import android.telephony.data.ApnSetting;
-import android.telephony.data.ApnSetting.ApnType;
 import android.text.TextUtils;
 import android.util.LocalLog;
 import android.util.SparseIntArray;
@@ -36,6 +33,7 @@
 import com.android.internal.telephony.dataconnection.DcTracker.ReleaseNetworkType;
 import com.android.internal.telephony.dataconnection.DcTracker.RequestNetworkType;
 import com.android.internal.util.IndentingPrintWriter;
+import com.android.telephony.Rlog;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
@@ -60,15 +58,13 @@
 
     private DctConstants.State mState;
 
-    public final int priority;
+    private int mPriority;
 
     private ApnSetting mApnSetting;
 
     private DataConnection mDataConnection;
 
-    String mReason;
-
-    PendingIntent mReconnectAlarmIntent;
+    private String mReason;
 
     /**
      * user/app requested connection on this APN
@@ -76,15 +72,10 @@
     AtomicBoolean mDataEnabled;
 
     private final Object mRefCountLock = new Object();
-    private int mRefCount = 0;
-
-    /**
-     * carrier requirements met
-     */
-    AtomicBoolean mDependencyMet;
 
     private final DcTracker mDcTracker;
 
+
     /**
      * Remember this as a change in this value to a more permissive state
      * should cause us to retry even permanent failures
@@ -103,27 +94,40 @@
     private final RetryManager mRetryManager;
 
     /**
-     * AonContext constructor
+     * ApnContext constructor
+     * @param phone phone object
+     * @param typeId APN type Id
+     * @param logTag Tag for logging
+     * @param tracker Data call tracker
+     * @param priority Priority of APN type
+     */
+    public ApnContext(Phone phone, int typeId, String logTag, DcTracker tracker, int priority) {
+        this(phone, ApnSetting.getApnTypeString(typeId), logTag, tracker, priority);
+    }
+
+    /**
+     * ApnContext constructor
      * @param phone phone object
      * @param apnType APN type (e.g. default, supl, mms, etc...)
      * @param logTag Tag for logging
-     * @param config Network configuration
      * @param tracker Data call tracker
+     * @param priority Priority of APN type
      */
-    public ApnContext(Phone phone, String apnType, String logTag, NetworkConfig config,
-            DcTracker tracker) {
+    public ApnContext(Phone phone, String apnType, String logTag, DcTracker tracker,
+            int priority) {
         mPhone = phone;
         mApnType = apnType;
         mState = DctConstants.State.IDLE;
         setReason(Phone.REASON_DATA_ENABLED);
         mDataEnabled = new AtomicBoolean(false);
-        mDependencyMet = new AtomicBoolean(config.dependencyMet);
-        priority = config.priority;
+        mPriority = priority;
         LOG_TAG = logTag;
         mDcTracker = tracker;
         mRetryManager = new RetryManager(phone, apnType);
     }
 
+
+
     /**
      * Get the APN type
      * @return The APN type
@@ -149,6 +153,31 @@
     }
 
     /**
+     * This priority is taken into account when concurrent data connections are not allowed.  The
+     * APN with the HIGHER priority is given preference.
+     * @return The priority of the APN type
+     */
+    public int getPriority() {
+        return mPriority;
+    }
+
+    /**
+     * Updates the priority of this context.
+     * @param priority The priority of the APN type
+     */
+    public void setPriority(int priority) {
+        mPriority = priority;
+    }
+
+    /**
+     * Keeping for backwards compatibility and in case it's needed in the future
+     * @return true
+     */
+    public boolean isDependencyMet() {
+        return true;
+    }
+
+    /**
      * Set the associated data connection.
      * @param dc data connection
      */
@@ -170,22 +199,6 @@
     }
 
     /**
-     * Get the reconnect intent.
-     * @return The reconnect intent
-     */
-    public synchronized PendingIntent getReconnectIntent() {
-        return mReconnectAlarmIntent;
-    }
-
-    /**
-     * Save the reconnect intent which can be used for cancelling later.
-     * @param intent The reconnect intent
-     */
-    public synchronized void setReconnectIntent(PendingIntent intent) {
-        mReconnectAlarmIntent = intent;
-    }
-
-    /**
      * Get the current APN setting.
      * @return APN setting
      */
@@ -213,10 +226,10 @@
 
     /**
      * Get the next available APN to try.
-     * @return APN setting which will be used for data call setup. Return null if there is no
+     * @return APN setting which will be used for data call setup.{@code null} if there is no
      * APN can be retried.
      */
-    public ApnSetting getNextApnSetting() {
+    public @Nullable ApnSetting getNextApnSetting() {
         return mRetryManager.getNextApnSetting();
     }
 
@@ -331,7 +344,7 @@
      * @return True if ready, otherwise false.
      */
     public boolean isReady() {
-        return mDataEnabled.get() && mDependencyMet.get();
+        return mDataEnabled.get() && isDependencyMet();
     }
 
     /**
@@ -379,10 +392,6 @@
         return mDataEnabled.get();
     }
 
-    public boolean isDependencyMet() {
-       return mDependencyMet.get();
-    }
-
     public boolean isProvisioningApn() {
         String provisioningApn = mPhone.getContext().getResources()
                 .getString(R.string.mobile_provisioning_apn);
@@ -533,31 +542,6 @@
         return mRetryManager.getRetryAfterDisconnectDelay();
     }
 
-    public static int getApnTypeFromNetworkType(int networkType) {
-        switch (networkType) {
-            case ConnectivityManager.TYPE_MOBILE:
-                return ApnSetting.TYPE_DEFAULT;
-            case ConnectivityManager.TYPE_MOBILE_MMS:
-                return ApnSetting.TYPE_MMS;
-            case ConnectivityManager.TYPE_MOBILE_SUPL:
-                return ApnSetting.TYPE_SUPL;
-            case ConnectivityManager.TYPE_MOBILE_DUN:
-                return ApnSetting.TYPE_DUN;
-            case ConnectivityManager.TYPE_MOBILE_FOTA:
-                return ApnSetting.TYPE_FOTA;
-            case ConnectivityManager.TYPE_MOBILE_IMS:
-                return ApnSetting.TYPE_IMS;
-            case ConnectivityManager.TYPE_MOBILE_CBS:
-                return ApnSetting.TYPE_CBS;
-            case ConnectivityManager.TYPE_MOBILE_IA:
-                return ApnSetting.TYPE_IA;
-            case ConnectivityManager.TYPE_MOBILE_EMERGENCY:
-                return ApnSetting.TYPE_EMERGENCY;
-            default:
-                return ApnSetting.TYPE_NONE;
-        }
-    }
-
     static @ApnType int getApnTypeFromNetworkRequest(NetworkRequest nr) {
         NetworkCapabilities nc = nr.networkCapabilities;
         // For now, ignore the bandwidth stuff
@@ -610,6 +594,10 @@
             if (apnType != ApnSetting.TYPE_NONE) error = true;
             apnType = ApnSetting.TYPE_MCX;
         }
+        if (nc.hasCapability(NetworkCapabilities.NET_CAPABILITY_XCAP)) {
+            if (apnType != ApnSetting.TYPE_NONE) error = true;
+            apnType = ApnSetting.TYPE_XCAP;
+        }
         if (error) {
             // TODO: If this error condition is removed, the framework's handling of
             // NET_CAPABILITY_NOT_RESTRICTED will need to be updated so requests for
@@ -632,10 +620,10 @@
     @Override
     public synchronized String toString() {
         // We don't print mDataConnection because its recursive.
-        return "{mApnType=" + mApnType + " mState=" + getState() + " mWaitingApns={" +
-                mRetryManager.getWaitingApns() + "}" + " mApnSetting={" + mApnSetting +
-                "} mReason=" + mReason + " mDataEnabled=" + mDataEnabled + " mDependencyMet=" +
-                mDependencyMet + "}";
+        return "{mApnType=" + mApnType + " mState=" + getState() + " mWaitingApns={"
+                    + mRetryManager.getWaitingApns() + " priority=" + mPriority + "}"
+                    + " mApnSetting={" + mApnSetting
+                    + "} mReason=" + mReason + " mDataEnabled=" + mDataEnabled + "}";
     }
 
     private void log(String s) {
diff --git a/src/java/com/android/internal/telephony/dataconnection/ApnSettingUtils.java b/src/java/com/android/internal/telephony/dataconnection/ApnSettingUtils.java
index 9a8d3ab..d072a68 100644
--- a/src/java/com/android/internal/telephony/dataconnection/ApnSettingUtils.java
+++ b/src/java/com/android/internal/telephony/dataconnection/ApnSettingUtils.java
@@ -18,14 +18,14 @@
 
 import android.content.Context;
 import android.os.PersistableBundle;
+import android.telephony.Annotation.ApnType;
 import android.telephony.CarrierConfigManager;
-import android.telephony.Rlog;
 import android.telephony.data.ApnSetting;
-import android.telephony.data.ApnSetting.ApnType;
 import android.util.Log;
 
 import com.android.internal.telephony.Phone;
 import com.android.internal.telephony.uicc.IccRecords;
+import com.android.telephony.Rlog;
 
 import java.util.Arrays;
 import java.util.HashSet;
diff --git a/src/java/com/android/internal/telephony/dataconnection/CellularDataService.java b/src/java/com/android/internal/telephony/dataconnection/CellularDataService.java
index b7676b7..a4b5929 100644
--- a/src/java/com/android/internal/telephony/dataconnection/CellularDataService.java
+++ b/src/java/com/android/internal/telephony/dataconnection/CellularDataService.java
@@ -19,10 +19,8 @@
 import android.net.LinkProperties;
 import android.os.AsyncResult;
 import android.os.Handler;
-import android.os.HandlerThread;
 import android.os.Looper;
 import android.os.Message;
-import android.telephony.Rlog;
 import android.telephony.SubscriptionManager;
 import android.telephony.data.DataCallResponse;
 import android.telephony.data.DataProfile;
@@ -31,6 +29,7 @@
 
 import com.android.internal.telephony.Phone;
 import com.android.internal.telephony.PhoneFactory;
+import com.android.telephony.Rlog;
 
 import java.util.HashMap;
 import java.util.List;
@@ -56,12 +55,8 @@
 
         private final Map<Message, DataServiceCallback> mCallbackMap = new HashMap<>();
 
-        private final Looper mLooper;
-
         private final Handler mHandler;
 
-        private final HandlerThread mHandlerThread;
-
         private final Phone mPhone;
 
         private CellularDataServiceProvider(int slotId) {
@@ -69,10 +64,7 @@
 
             mPhone = PhoneFactory.getPhone(getSlotIndex());
 
-            mHandlerThread = new HandlerThread(CellularDataService.class.getSimpleName());
-            mHandlerThread.start();
-            mLooper = mHandlerThread.getLooper();
-            mHandler = new Handler(mLooper) {
+            mHandler = new Handler(Looper.myLooper()) {
                 @Override
                 public void handleMessage(Message message) {
                     DataServiceCallback callback = mCallbackMap.remove(message);
@@ -206,7 +198,6 @@
         @Override
         public void close() {
             mPhone.mCi.unregisterForDataCallListChanged(mHandler);
-            mHandlerThread.quit();
         }
     }
 
diff --git a/src/java/com/android/internal/telephony/dataconnection/DataConnection.java b/src/java/com/android/internal/telephony/dataconnection/DataConnection.java
index 9384eaa..f9f23c1 100644
--- a/src/java/com/android/internal/telephony/dataconnection/DataConnection.java
+++ b/src/java/com/android/internal/telephony/dataconnection/DataConnection.java
@@ -16,27 +16,28 @@
 
 package com.android.internal.telephony.dataconnection;
 
-import static android.net.NetworkPolicyManager.OVERRIDE_CONGESTED;
-import static android.net.NetworkPolicyManager.OVERRIDE_UNMETERED;
+import static android.net.NetworkPolicyManager.SUBSCRIPTION_OVERRIDE_CONGESTED;
+import static android.net.NetworkPolicyManager.SUBSCRIPTION_OVERRIDE_UNMETERED;
 
 import android.annotation.IntDef;
 import android.annotation.Nullable;
 import android.app.PendingIntent;
 import android.content.Context;
 import android.net.ConnectivityManager;
+import android.net.InetAddresses;
 import android.net.KeepalivePacketData;
 import android.net.LinkAddress;
 import android.net.LinkProperties;
+import android.net.NetworkAgentConfig;
 import android.net.NetworkCapabilities;
 import android.net.NetworkFactory;
 import android.net.NetworkInfo;
-import android.net.NetworkMisc;
+import android.net.NetworkProvider;
 import android.net.NetworkRequest;
-import android.net.NetworkUtils;
 import android.net.ProxyInfo;
 import android.net.RouteInfo;
 import android.net.SocketKeepalive;
-import android.net.StringNetworkSpecifier;
+import android.net.TelephonyNetworkSpecifier;
 import android.os.AsyncResult;
 import android.os.Message;
 import android.os.PersistableBundle;
@@ -45,15 +46,15 @@
 import android.provider.Telephony;
 import android.telephony.AccessNetworkConstants;
 import android.telephony.AccessNetworkConstants.TransportType;
+import android.telephony.Annotation.ApnType;
+import android.telephony.Annotation.DataFailureCause;
 import android.telephony.CarrierConfigManager;
 import android.telephony.DataFailCause;
 import android.telephony.NetworkRegistrationInfo;
-import android.telephony.Rlog;
 import android.telephony.ServiceState;
 import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyManager;
 import android.telephony.data.ApnSetting;
-import android.telephony.data.ApnSetting.ApnType;
 import android.telephony.data.DataCallResponse;
 import android.telephony.data.DataProfile;
 import android.telephony.data.DataService;
@@ -61,7 +62,6 @@
 import android.text.TextUtils;
 import android.util.LocalLog;
 import android.util.Pair;
-import android.util.StatsLog;
 import android.util.TimeUtils;
 
 import com.android.internal.annotations.VisibleForTesting;
@@ -72,10 +72,11 @@
 import com.android.internal.telephony.Phone;
 import com.android.internal.telephony.PhoneConstants;
 import com.android.internal.telephony.PhoneFactory;
+import com.android.internal.telephony.RIL;
 import com.android.internal.telephony.RILConstants;
 import com.android.internal.telephony.RetryManager;
 import com.android.internal.telephony.ServiceStateTracker;
-import com.android.internal.telephony.TelephonyIntents;
+import com.android.internal.telephony.TelephonyStatsLog;
 import com.android.internal.telephony.dataconnection.DcTracker.ReleaseNetworkType;
 import com.android.internal.telephony.dataconnection.DcTracker.RequestNetworkType;
 import com.android.internal.telephony.metrics.TelephonyMetrics;
@@ -85,6 +86,7 @@
 import com.android.internal.util.Protocol;
 import com.android.internal.util.State;
 import com.android.internal.util.StateMachine;
+import com.android.telephony.Rlog;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
@@ -94,6 +96,7 @@
 import java.net.InetAddress;
 import java.net.UnknownHostException;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collection;
 import java.util.List;
 import java.util.Locale;
@@ -181,6 +184,8 @@
 
     private final LocalLog mHandoverLocalLog = new LocalLog(100);
 
+    private int[] mAdministratorUids = new int[0];
+
     /**
      * Used internally for saving connecting parameters.
      */
@@ -194,10 +199,12 @@
         @RequestNetworkType
         final int mRequestType;
         final int mSubId;
+        final boolean mIsPreferredApn;
 
         ConnectionParams(ApnContext apnContext, int profileId, int rilRadioTechnology,
                          Message onCompletedMsg, int connectionGeneration,
-                         @RequestNetworkType int requestType, int subId) {
+                         @RequestNetworkType int requestType, int subId,
+                         boolean isPreferredApn) {
             mApnContext = apnContext;
             mProfileId = profileId;
             mRilRat = rilRadioTechnology;
@@ -205,6 +212,7 @@
             mConnectionGeneration = connectionGeneration;
             mRequestType = requestType;
             mSubId = subId;
+            mIsPreferredApn = isPreferredApn;
         }
 
         @Override
@@ -215,6 +223,7 @@
                     + " mOnCompletedMsg=" + msgToString(mOnCompletedMsg)
                     + " mRequestType=" + DcTracker.requestTypeToString(mRequestType)
                     + " mSubId=" + mSubId
+                    + " mIsPreferredApn=" + mIsPreferredApn
                     + "}";
         }
     }
@@ -250,7 +259,7 @@
     private ApnSetting mApnSetting;
     private ConnectionParams mConnectionParams;
     private DisconnectParams mDisconnectParams;
-    @DataFailCause.FailCause
+    @DataFailureCause
     private int mDcFailCause;
 
     private Phone mPhone;
@@ -259,15 +268,17 @@
     private LinkProperties mLinkProperties = new LinkProperties();
     private long mCreateTime;
     private long mLastFailTime;
-    @DataFailCause.FailCause
+    @DataFailureCause
     private int mLastFailCause;
     private static final String NULL_IP = "0.0.0.0";
     private Object mUserData;
     private int mSubscriptionOverride;
-    private int mRilRat = ServiceState.RIL_RADIO_TECHNOLOGY_UNKNOWN;
     private boolean mUnmeteredOverride;
+    private int mRilRat = ServiceState.RIL_RADIO_TECHNOLOGY_UNKNOWN;
     private int mDataRegState = Integer.MAX_VALUE;
     private NetworkInfo mNetworkInfo;
+    private int mDownlinkBandwidth = 14;
+    private int mUplinkBandwidth = 14;
 
     /** The corresponding network agent for this data connection. */
     private DcNetworkAgent mNetworkAgent;
@@ -321,7 +332,9 @@
     static final int EVENT_NR_STATE_CHANGED = BASE + 27;
     static final int EVENT_DATA_CONNECTION_METEREDNESS_CHANGED = BASE + 28;
     static final int EVENT_NR_FREQUENCY_CHANGED = BASE + 29;
-    private static final int CMD_TO_STRING_COUNT = EVENT_NR_FREQUENCY_CHANGED - BASE + 1;
+    static final int EVENT_CARRIER_CONFIG_LINK_BANDWIDTHS_CHANGED = BASE + 30;
+    static final int EVENT_CARRIER_PRIVILEGED_UIDS_CHANGED = BASE + 31;
+    private static final int CMD_TO_STRING_COUNT = EVENT_CARRIER_PRIVILEGED_UIDS_CHANGED - BASE + 1;
 
     private static String[] sCmdToString = new String[CMD_TO_STRING_COUNT];
     static {
@@ -361,6 +374,10 @@
         sCmdToString[EVENT_DATA_CONNECTION_METEREDNESS_CHANGED - BASE] =
                 "EVENT_DATA_CONNECTION_METEREDNESS_CHANGED";
         sCmdToString[EVENT_NR_FREQUENCY_CHANGED - BASE] = "EVENT_NR_FREQUENCY_CHANGED";
+        sCmdToString[EVENT_CARRIER_CONFIG_LINK_BANDWIDTHS_CHANGED - BASE] =
+                "EVENT_CARRIER_CONFIG_LINK_BANDWIDTHS_CHANGED";
+        sCmdToString[EVENT_CARRIER_PRIVILEGED_UIDS_CHANGED - BASE] =
+                "EVENT_CARRIER_PRIVILEGED_UIDS_CHANGED";
     }
     // Convert cmd to string or null if unknown
     static String cmdToString(int cmd) {
@@ -417,7 +434,8 @@
         return getCurrentState() == mDisconnectingState;
     }
 
-    boolean isActive() {
+    @VisibleForTesting
+    public boolean isActive() {
         return getCurrentState() == mActiveState;
     }
 
@@ -591,7 +609,7 @@
         mId = id;
         mCid = -1;
         ServiceState ss = mPhone.getServiceState();
-        mDataRegState = mPhone.getServiceState().getDataRegState();
+        mDataRegState = mPhone.getServiceState().getDataRegistrationState();
         int networkType = TelephonyManager.NETWORK_TYPE_UNKNOWN;
 
         NetworkRegistrationInfo nri = ss.getNetworkRegistrationInfo(
@@ -599,12 +617,11 @@
         if (nri != null) {
             networkType = nri.getAccessNetworkTechnology();
             mRilRat = ServiceState.networkTypeToRilRadioTechnology(networkType);
+            updateLinkBandwidthsFromCarrierConfig(mRilRat);
         }
 
         mNetworkInfo = new NetworkInfo(ConnectivityManager.TYPE_MOBILE,
                 networkType, NETWORK_TYPE, TelephonyManager.getNetworkTypeName(networkType));
-        mNetworkInfo.setRoaming(ss.getDataRoaming());
-        mNetworkInfo.setIsAvailable(true);
 
         addState(mDefaultState);
             addState(mInactiveState, mDefaultState);
@@ -634,7 +651,7 @@
      *
      * @return Fail cause if failed to setup data connection. {@link DataFailCause#NONE} if success.
      */
-    private @DataFailCause.FailCause int connect(ConnectionParams cp) {
+    private @DataFailureCause int connect(ConnectionParams cp) {
         log("connect: carrier='" + mApnSetting.getEntryName()
                 + "' APN='" + mApnSetting.getApnName()
                 + "' proxy='" + mApnSetting.getProxyAddressAsString()
@@ -643,10 +660,13 @@
 
         // Check if we should fake an error.
         if (mDcTesterFailBringUpAll.getDcFailBringUp().mCounter  > 0) {
-            DataCallResponse response = new DataCallResponse(
-                    mDcTesterFailBringUpAll.getDcFailBringUp().mFailCause,
-                    mDcTesterFailBringUpAll.getDcFailBringUp().mSuggestedRetryTime, 0, 0, 0, "",
-                    null, null, null, null, PhoneConstants.UNSET_MTU);
+            DataCallResponse response = new DataCallResponse.Builder()
+                    .setCause(mDcTesterFailBringUpAll.getDcFailBringUp().mFailCause)
+                    .setSuggestedRetryTime(
+                            mDcTesterFailBringUpAll.getDcFailBringUp().mSuggestedRetryTime)
+                    .setMtuV4(PhoneConstants.UNSET_MTU)
+                    .setMtuV6(PhoneConstants.UNSET_MTU)
+                    .build();
 
             Message msg = obtainMessage(EVENT_SETUP_DATA_CONNECTION_DONE, cp);
             AsyncResult.forMessage(msg, response, null);
@@ -667,18 +687,33 @@
         msg.obj = cp;
 
         DataProfile dp = DcTracker.createDataProfile(mApnSetting, cp.mProfileId,
-                mApnSetting.equals(mDct.getPreferredApn()));
+                cp.mIsPreferredApn);
 
         // We need to use the actual modem roaming state instead of the framework roaming state
         // here. This flag is only passed down to ril_service for picking the correct protocol (for
         // old modem backward compatibility).
         boolean isModemRoaming = mPhone.getServiceState().getDataRoamingFromRegistration();
 
+        // If the apn is NOT metered, we will allow data roaming regardless of the setting.
+        boolean isUnmeteredApnType = !ApnSettingUtils.isMeteredApnType(
+                cp.mApnContext.getApnTypeBitmask(), mPhone);
+
         // Set this flag to true if the user turns on data roaming. Or if we override the roaming
         // state in framework, we should set this flag to true as well so the modem will not reject
         // the data call setup (because the modem actually thinks the device is roaming).
         boolean allowRoaming = mPhone.getDataRoamingEnabled()
-                || (isModemRoaming && !mPhone.getServiceState().getDataRoaming());
+                || (isModemRoaming && (!mPhone.getServiceState().getDataRoaming()
+                || isUnmeteredApnType));
+
+        if (DBG) {
+            log("allowRoaming=" + allowRoaming
+                    + ", mPhone.getDataRoamingEnabled()=" + mPhone.getDataRoamingEnabled()
+                    + ", isModemRoaming=" + isModemRoaming
+                    + ", mPhone.getServiceState().getDataRoaming()="
+                    + mPhone.getServiceState().getDataRoaming()
+                    + ", isUnmeteredApnType=" + isUnmeteredApnType
+            );
+        }
 
         // Check if this data setup is a handover.
         LinkProperties linkProperties = null;
@@ -699,19 +734,25 @@
                 return DataFailCause.HANDOVER_FAILED;
             }
 
-            linkProperties = dc.getLinkProperties();
             // Preserve the potential network agent from the source data connection. The ownership
             // is not transferred at this moment.
-            mHandoverLocalLog.log("Handover started. Preserved the agent.");
             mHandoverSourceNetworkAgent = dc.getNetworkAgent();
-            log("Get the handover source network agent: " + mHandoverSourceNetworkAgent);
-            dc.setHandoverState(HANDOVER_STATE_BEING_TRANSFERRED);
-            if (linkProperties == null) {
+            if (mHandoverSourceNetworkAgent == null) {
+                loge("Cannot get network agent from the source dc " + dc.getName());
+                return DataFailCause.HANDOVER_FAILED;
+            }
+
+            linkProperties = dc.getLinkProperties();
+            if (linkProperties == null || linkProperties.getLinkAddresses().isEmpty()) {
                 loge("connect: Can't find link properties of handover data connection. dc="
                         + dc);
                 return DataFailCause.HANDOVER_FAILED;
             }
 
+            mHandoverLocalLog.log("Handover started. Preserved the agent.");
+            log("Get the handover source network agent: " + mHandoverSourceNetworkAgent);
+
+            dc.setHandoverState(HANDOVER_STATE_BEING_TRANSFERRED);
             reason = DataService.REQUEST_REASON_HANDOVER;
         }
 
@@ -790,7 +831,7 @@
      * @param cause and if no error the cause is DataFailCause.NONE
      * @param sendAll is true if all contexts are to be notified
      */
-    private void notifyConnectCompleted(ConnectionParams cp, @DataFailCause.FailCause int cause,
+    private void notifyConnectCompleted(ConnectionParams cp, @DataFailureCause int cause,
                                         boolean sendAll) {
         ApnContext alreadySent = null;
 
@@ -913,6 +954,8 @@
         mSubId = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
         mSubscriptionOverride = 0;
         mUnmeteredOverride = false;
+        mDownlinkBandwidth = 14;
+        mUplinkBandwidth = 14;
     }
 
     /**
@@ -997,13 +1040,14 @@
             "122334,734003,2202010,32040,192239,576717";
     private static final String TCP_BUFFER_SIZES_NR =
             "2097152,6291456,16777216,512000,2097152,8388608";
+    private static final String TCP_BUFFER_SIZES_LTE_CA =
+            "4096,6291456,12582912,4096,1048576,2097152";
 
     private void updateTcpBufferSizes(int rilRat) {
         String sizes = null;
-        if (rilRat == ServiceState.RIL_RADIO_TECHNOLOGY_LTE_CA) {
-            // for now treat CA as LTE.  Plan to surface the extra bandwith in a more
-            // precise manner which should affect buffer sizes
-            rilRat = ServiceState.RIL_RADIO_TECHNOLOGY_LTE;
+        ServiceState ss = mPhone.getServiceState();
+        if (rilRat == ServiceState.RIL_RADIO_TECHNOLOGY_LTE && ss.isUsingCarrierAggregation()) {
+            rilRat = ServiceState.RIL_RADIO_TECHNOLOGY_LTE_CA;
         }
         String ratName = ServiceState.rilRadioTechnologyToString(rilRat).toLowerCase(Locale.ROOT);
         // ServiceState gives slightly different names for EVDO tech ("evdo-rev.0" for ex)
@@ -1017,7 +1061,8 @@
         // NR 5G Non-Standalone use LTE cell as the primary cell, the ril technology is LTE in this
         // case. We use NR 5G TCP buffer size when connected to NR 5G Non-Standalone network.
         if (mTransportType == AccessNetworkConstants.TRANSPORT_TYPE_WWAN
-                && rilRat == ServiceState.RIL_RADIO_TECHNOLOGY_LTE && isNRConnected()
+                && ((rilRat == ServiceState.RIL_RADIO_TECHNOLOGY_LTE
+                || rilRat == ServiceState.RIL_RADIO_TECHNOLOGY_LTE_CA) && isNRConnected())
                 && mPhone.getServiceStateTracker().getNrContextIds().contains(mCid)) {
             ratName = RAT_NAME_5G;
         }
@@ -1068,7 +1113,6 @@
                     sizes = TCP_BUFFER_SIZES_HSPA;
                     break;
                 case ServiceState.RIL_RADIO_TECHNOLOGY_LTE:
-                case ServiceState.RIL_RADIO_TECHNOLOGY_LTE_CA:
                     // Use NR 5G TCP buffer size when connected to NR 5G Non-Standalone network.
                     if (RAT_NAME_5G.equals(ratName)) {
                         sizes = TCP_BUFFER_SIZES_NR;
@@ -1076,6 +1120,14 @@
                         sizes = TCP_BUFFER_SIZES_LTE;
                     }
                     break;
+                case ServiceState.RIL_RADIO_TECHNOLOGY_LTE_CA:
+                    // Use NR 5G TCP buffer size when connected to NR 5G Non-Standalone network.
+                    if (RAT_NAME_5G.equals(ratName)) {
+                        sizes = TCP_BUFFER_SIZES_NR;
+                    } else {
+                        sizes = TCP_BUFFER_SIZES_LTE_CA;
+                    }
+                    break;
                 case ServiceState.RIL_RADIO_TECHNOLOGY_HSPAP:
                     sizes = TCP_BUFFER_SIZES_HSPAP;
                     break;
@@ -1090,7 +1142,7 @@
         mLinkProperties.setTcpBufferSizes(sizes);
     }
 
-    private void updateLinkBandwidths(NetworkCapabilities caps, int rilRat) {
+    private void updateLinkBandwidthsFromCarrierConfig(int rilRat) {
         String ratName = ServiceState.rilRadioTechnologyToString(rilRat);
         if (rilRat == ServiceState.RIL_RADIO_TECHNOLOGY_LTE && isNRConnected()) {
             ratName = mPhone.getServiceState().getNrFrequencyRange()
@@ -1098,14 +1150,58 @@
                     ? DctConstants.RAT_NAME_NR_NSA_MMWAVE : DctConstants.RAT_NAME_NR_NSA;
         }
 
-        if (DBG) log("updateLinkBandwidths: " + ratName);
+        if (DBG) log("updateLinkBandwidthsFromCarrierConfig: " + ratName);
 
-        Pair<Integer, Integer> values = mDct.getLinkBandwidths(ratName);
+        Pair<Integer, Integer> values = mDct.getLinkBandwidthsFromCarrierConfig(ratName);
         if (values == null) {
             values = new Pair<>(14, 14);
         }
-        caps.setLinkDownstreamBandwidthKbps(values.first);
-        caps.setLinkUpstreamBandwidthKbps(values.second);
+        mDownlinkBandwidth = values.first;
+        mUplinkBandwidth = values.second;
+    }
+
+
+    private void updateLinkBandwidthsFromModem(LinkCapacityEstimate lce) {
+        if (DBG) log("updateLinkBandwidthsFromModem: lce=" + lce);
+        boolean downlinkUpdated = false;
+        boolean uplinkUpdated = false;
+        // LCE status deprecated in IRadio 1.2, so only check for IRadio < 1.2
+        if (mPhone.getHalVersion().greaterOrEqual(RIL.RADIO_HAL_VERSION_1_2)
+                || mPhone.getLceStatus() == RILConstants.LCE_ACTIVE) {
+            if (lce.downlinkCapacityKbps != LinkCapacityEstimate.INVALID) {
+                mDownlinkBandwidth = lce.downlinkCapacityKbps;
+                downlinkUpdated = true;
+            }
+            if (lce.uplinkCapacityKbps != LinkCapacityEstimate.INVALID) {
+                mUplinkBandwidth = lce.uplinkCapacityKbps;
+                uplinkUpdated = true;
+            }
+        }
+        if (!downlinkUpdated || !uplinkUpdated) {
+            String ratName = ServiceState.rilRadioTechnologyToString(mRilRat);
+            if (mRilRat == ServiceState.RIL_RADIO_TECHNOLOGY_LTE && isNRConnected()) {
+                ratName = mPhone.getServiceState().getNrFrequencyRange()
+                        == ServiceState.FREQUENCY_RANGE_MMWAVE
+                        ? DctConstants.RAT_NAME_NR_NSA_MMWAVE : DctConstants.RAT_NAME_NR_NSA;
+            }
+            Pair<Integer, Integer> values = mDct.getLinkBandwidthsFromCarrierConfig(ratName);
+            if (values != null) {
+                if (!downlinkUpdated) {
+                    mDownlinkBandwidth = values.first;
+                }
+                if (!uplinkUpdated) {
+                    mUplinkBandwidth = values.second;
+                }
+            }
+        }
+        if (mNetworkAgent != null) {
+            mNetworkAgent.sendNetworkCapabilities(getNetworkCapabilities(), DataConnection.this);
+        }
+    }
+
+    private boolean isBandwidthSourceKey(String source) {
+        return source.equals(mPhone.getContext().getResources().getString(
+                com.android.internal.R.string.config_bandwidthEstimateSource));
     }
 
     /**
@@ -1196,15 +1292,13 @@
         }
 
         // If data is enabled, this data connection can't be for unmetered used only because
-        // everyone should be able to use it.
+        // everyone should be able to use it if:
+        // 1. Device is not roaming, or
+        // 2. Device is roaming and data roaming is turned on
         if (mPhone.getDataEnabledSettings().isDataEnabled()) {
-            return false;
-        }
-
-        // If the device is roaming and data roaming it turned on, then this data connection can't
-        // be for unmetered use only.
-        if (mDct.getDataRoamingEnabled() && mPhone.getServiceState().getDataRoaming()) {
-            return false;
+            if (!mPhone.getServiceState().getDataRoaming() || mDct.getDataRoamingEnabled()) {
+                return false;
+            }
         }
 
         // The data connection can only be unmetered used only if all attached APN contexts
@@ -1218,6 +1312,11 @@
     }
 
     /**
+     * Get the network capabilities for this data connection.
+     *
+     * Note that this method reads fields from mNetworkInfo, so its output is only as fresh
+     * as mNetworkInfo. Call updateNetworkInfoSuspendState before calling this.
+     *
      * @return the {@link NetworkCapabilities} of this data connection.
      */
     public NetworkCapabilities getNetworkCapabilities() {
@@ -1286,21 +1385,23 @@
                         result.addCapability(NetworkCapabilities.NET_CAPABILITY_MCX);
                         break;
                     }
+                    case PhoneConstants.APN_TYPE_XCAP: {
+                        result.addCapability(NetworkCapabilities.NET_CAPABILITY_XCAP);
+                        break;
+                    }
                     default:
                 }
             }
 
-            // Mark NOT_METERED in the following cases,
-            // 1. All APNs in APN settings are unmetered.
-            // 2. The non-restricted data and is intended for unmetered use only.
-            if ((mUnmeteredUseOnly && !mRestrictedNetworkOverride)
-                    || !ApnSettingUtils.isMetered(mApnSetting, mPhone)) {
-                result.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED);
-            } else {
-                result.removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED);
-            }
+            // DataConnection has the immutable NOT_METERED capability only if all APNs in the
+            // APN setting are unmetered according to carrier config METERED_APN_TYPES_STRINGS.
+            // All other cases should use the dynamic TEMPORARILY_NOT_METERED capability instead.
+            result.setCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED,
+                    !ApnSettingUtils.isMetered(mApnSetting, mPhone));
 
-            result.maybeMarkCapabilitiesRestricted();
+            if (result.deduceRestrictedCapability()) {
+                result.removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED);
+            }
         }
 
         if (mRestrictedNetworkOverride) {
@@ -1309,31 +1410,49 @@
             result.removeCapability(NetworkCapabilities.NET_CAPABILITY_DUN);
         }
 
-        updateLinkBandwidths(result, mRilRat);
+        result.setLinkDownstreamBandwidthKbps(mDownlinkBandwidth);
+        result.setLinkUpstreamBandwidthKbps(mUplinkBandwidth);
 
-        result.setNetworkSpecifier(new StringNetworkSpecifier(Integer.toString(mSubId)));
+        result.setNetworkSpecifier(new TelephonyNetworkSpecifier.Builder()
+                .setSubscriptionId(mSubId).build());
 
         result.setCapability(NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING,
                 !mPhone.getServiceState().getDataRoaming());
 
-        result.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_CONGESTED);
-
-        // Override values set above when requested by policy
-        if ((mSubscriptionOverride & OVERRIDE_UNMETERED) != 0) {
-            result.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED);
-        }
-        if ((mSubscriptionOverride & OVERRIDE_CONGESTED) != 0) {
-            result.removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_CONGESTED);
+        if ((mSubscriptionOverride & SUBSCRIPTION_OVERRIDE_CONGESTED) == 0) {
+            result.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_CONGESTED);
         }
 
-        // Override set by DcTracker
-        if (mUnmeteredOverride) {
-            result.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED);
+        // Mark TEMPORARILY_NOT_METERED in the following cases:
+        // 1. The non-restricted data is intended for unmetered use only.
+        // 2. DcTracker set an unmetered override due to network/location (eg. 5G).
+        // 3. SubscriptionManager set an unmetered override as requested by policy.
+        if ((mUnmeteredUseOnly && !mRestrictedNetworkOverride) || mUnmeteredOverride
+                || (mSubscriptionOverride & SUBSCRIPTION_OVERRIDE_UNMETERED) != 0) {
+            result.addCapability(NetworkCapabilities.NET_CAPABILITY_TEMPORARILY_NOT_METERED);
+        } else {
+            result.removeCapability(NetworkCapabilities.NET_CAPABILITY_TEMPORARILY_NOT_METERED);
         }
 
+        final boolean suspended =
+                mNetworkInfo.getDetailedState() == NetworkInfo.DetailedState.SUSPENDED;
+        result.setCapability(NetworkCapabilities.NET_CAPABILITY_NOT_SUSPENDED, !suspended);
+
+        result.setAdministratorUids(mAdministratorUids);
+
         return result;
     }
 
+    /** @return {@code true} if validation is required, {@code false} otherwise. */
+    public boolean isValidationRequired() {
+        final NetworkCapabilities nc = getNetworkCapabilities();
+        return nc != null
+                && nc.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
+                && nc.hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED)
+                && nc.hasCapability(NetworkCapabilities.NET_CAPABILITY_TRUSTED)
+                && nc.hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VPN);
+    }
+
     /**
      * @return {@code True} if 464xlat should be skipped.
      */
@@ -1362,7 +1481,11 @@
     public static boolean isIpAddress(String address) {
         if (address == null) return false;
 
-        return InetAddress.isNumeric(address);
+        // Accept IPv6 addresses (only) in square brackets for compatibility.
+        if (address.startsWith("[") && address.endsWith("]") && address.indexOf(':') != -1) {
+            address = address.substring(1, address.length() - 1);
+        }
+        return InetAddresses.isNumericAddress(address);
     }
 
     private SetupResult setLinkProperties(DataCallResponse response,
@@ -1391,7 +1514,7 @@
                         if (!la.getAddress().isAnyLocalAddress()) {
                             if (DBG) {
                                 log("addr/pl=" + la.getAddress() + "/"
-                                        + la.getNetworkPrefixLength());
+                                        + la.getPrefixLength());
                             }
                             linkProperties.addLinkAddress(la);
                         }
@@ -1414,7 +1537,7 @@
                         if (dnsAddr.isEmpty()) continue;
                         InetAddress ia;
                         try {
-                            ia = NetworkUtils.numericToInetAddress(dnsAddr);
+                            ia = InetAddresses.parseNumericAddress(dnsAddr);
                         } catch (IllegalArgumentException e) {
                             throw new UnknownHostException("Non-numeric dns addr=" + dnsAddr);
                         }
@@ -1434,13 +1557,17 @@
                 }
 
                 for (InetAddress gateway : response.getGatewayAddresses()) {
+                    int mtu = gateway instanceof java.net.Inet6Address ? response.getMtuV6()
+                            : response.getMtuV4();
                     // Allow 0.0.0.0 or :: as a gateway;
                     // this indicates a point-to-point interface.
-                    linkProperties.addRoute(new RouteInfo(gateway));
+                    linkProperties.addRoute(new RouteInfo(null, gateway, null,
+                            RouteInfo.RTN_UNICAST, mtu));
                 }
 
                 // set interface MTU
                 // this may clobber the setting read from the APN db, but that's ok
+                // TODO: remove once LinkProperties#setMtu is deprecated
                 linkProperties.setMtu(response.getMtu());
 
                 result = SetupResult.SUCCESS;
@@ -1604,8 +1731,9 @@
                     AsyncResult ar = (AsyncResult)msg.obj;
                     Pair<Integer, Integer> drsRatPair = (Pair<Integer, Integer>)ar.result;
                     mDataRegState = drsRatPair.first;
-                    if (mRilRat != drsRatPair.second) {
-                        updateTcpBufferSizes(drsRatPair.second);
+                    updateTcpBufferSizes(drsRatPair.second);
+                    if (isBandwidthSourceKey(DctConstants.BANDWIDTH_SOURCE_CARRIER_CONFIG_KEY)) {
+                        updateLinkBandwidthsFromCarrierConfig(drsRatPair.second);
                     }
                     mRilRat = drsRatPair.second;
                     if (DBG) {
@@ -1614,38 +1742,6 @@
                                 + " mRilRat=" + mRilRat);
                     }
                     updateNetworkInfo();
-                    updateNetworkInfoSuspendState();
-                    if (mNetworkAgent != null) {
-                        mNetworkAgent.sendNetworkCapabilities(getNetworkCapabilities(),
-                                DataConnection.this);
-                        mNetworkAgent.sendNetworkInfo(mNetworkInfo, DataConnection.this);
-                        mNetworkAgent.sendLinkProperties(mLinkProperties, DataConnection.this);
-                    }
-                    break;
-                case EVENT_DATA_CONNECTION_METEREDNESS_CHANGED:
-                    boolean isUnmetered = (boolean) msg.obj;
-                    if (isUnmetered == mUnmeteredOverride) {
-                        break;
-                    }
-                    mUnmeteredOverride = isUnmetered;
-                    // fallthrough
-                case EVENT_NR_FREQUENCY_CHANGED:
-                case EVENT_DATA_CONNECTION_ROAM_ON:
-                case EVENT_DATA_CONNECTION_ROAM_OFF:
-                case EVENT_DATA_CONNECTION_OVERRIDE_CHANGED:
-                    updateNetworkInfo();
-                    if (mNetworkAgent != null) {
-                        mNetworkAgent.sendNetworkCapabilities(getNetworkCapabilities(),
-                                DataConnection.this);
-                        mNetworkAgent.sendNetworkInfo(mNetworkInfo, DataConnection.this);
-                    }
-                    break;
-                case EVENT_KEEPALIVE_START_REQUEST:
-                case EVENT_KEEPALIVE_STOP_REQUEST:
-                    if (mNetworkAgent != null) {
-                        mNetworkAgent.onSocketKeepaliveEvent(
-                                msg.arg1, SocketKeepalive.ERROR_INVALID_NETWORK);
-                    }
                     break;
                 default:
                     if (DBG) {
@@ -1660,9 +1756,15 @@
 
     private void updateNetworkInfo() {
         final ServiceState state = mPhone.getServiceState();
-        final int subtype = state.getDataNetworkType();
+
+        NetworkRegistrationInfo nri = state.getNetworkRegistrationInfo(
+                NetworkRegistrationInfo.DOMAIN_PS, mTransportType);
+        int subtype = TelephonyManager.NETWORK_TYPE_UNKNOWN;
+        if (nri != null) {
+            subtype = nri.getAccessNetworkTechnology();
+        }
+
         mNetworkInfo.setSubtype(subtype, TelephonyManager.getNetworkTypeName(subtype));
-        mNetworkInfo.setRoaming(state.getDataRoaming());
     }
 
     private void updateNetworkInfoSuspendState() {
@@ -1699,7 +1801,7 @@
     private class DcInactiveState extends State {
         // Inform all contexts we've failed connecting
         public void setEnterNotificationParams(ConnectionParams cp,
-                                               @DataFailCause.FailCause int cause) {
+                                               @DataFailureCause int cause) {
             if (VDBG) log("DcInactiveState: setEnterNotificationParams cp,cause");
             mConnectionParams = cp;
             mDisconnectParams = null;
@@ -1715,7 +1817,7 @@
         }
 
         // Inform all contexts of the failure cause
-        public void setEnterNotificationParams(@DataFailCause.FailCause int cause) {
+        public void setEnterNotificationParams(@DataFailureCause int cause) {
             mConnectionParams = null;
             mDisconnectParams = null;
             mDcFailCause = cause;
@@ -1725,14 +1827,14 @@
         public void enter() {
             mTag += 1;
             if (DBG) log("DcInactiveState: enter() mTag=" + mTag);
-            StatsLog.write(StatsLog.MOBILE_CONNECTION_STATE_CHANGED,
-                    StatsLog.MOBILE_CONNECTION_STATE_CHANGED__STATE__INACTIVE,
+            TelephonyStatsLog.write(TelephonyStatsLog.MOBILE_CONNECTION_STATE_CHANGED,
+                    TelephonyStatsLog.MOBILE_CONNECTION_STATE_CHANGED__STATE__INACTIVE,
                     mPhone.getPhoneId(), mId,
                     mApnSetting != null ? (long) mApnSetting.getApnTypeBitmask() : 0L,
                     mApnSetting != null
                         ? mApnSetting.canHandleType(ApnSetting.TYPE_DEFAULT) : false);
             if (mHandoverState == HANDOVER_STATE_BEING_TRANSFERRED) {
-                mHandoverState = HANDOVER_STATE_COMPLETED;
+                setHandoverState(HANDOVER_STATE_COMPLETED);
             }
 
             // Check for dangling agent. Ideally the handover source agent should be null if
@@ -1745,7 +1847,8 @@
                     // If the source data connection still owns this agent, then just reset the
                     // handover state back to idle because handover is already failed.
                     mHandoverLocalLog.log(
-                            "Handover failed. Reset the source dc state to idle");
+                            "Handover failed. Reset the source dc " + sourceDc.getName()
+                                    + " state to idle");
                     sourceDc.setHandoverState(HANDOVER_STATE_IDLE);
                 } else {
                     // The agent is now a dangling agent. No data connection owns this agent.
@@ -1754,7 +1857,15 @@
                             "Handover failed and dangling agent found.");
                     mHandoverSourceNetworkAgent.acquireOwnership(
                             DataConnection.this, mTransportType);
-                    mHandoverSourceNetworkAgent.sendNetworkInfo(mNetworkInfo, DataConnection.this);
+                    NetworkInfo networkInfo = mHandoverSourceNetworkAgent.getNetworkInfo();
+                    if (networkInfo != null) {
+                        log("Cleared dangling network agent. " + mHandoverSourceNetworkAgent);
+                        mHandoverSourceNetworkAgent.unregister(DataConnection.this);
+                    } else {
+                        String str = "Failed to get network info.";
+                        loge(str);
+                        mHandoverLocalLog.log(str);
+                    }
                     mHandoverSourceNetworkAgent.releaseOwnership(DataConnection.this);
                 }
                 mHandoverSourceNetworkAgent = null;
@@ -1854,8 +1965,8 @@
     private class DcActivatingState extends State {
         @Override
         public void enter() {
-            StatsLog.write(StatsLog.MOBILE_CONNECTION_STATE_CHANGED,
-                    StatsLog.MOBILE_CONNECTION_STATE_CHANGED__STATE__ACTIVATING,
+            TelephonyStatsLog.write(TelephonyStatsLog.MOBILE_CONNECTION_STATE_CHANGED,
+                    TelephonyStatsLog.MOBILE_CONNECTION_STATE_CHANGED__STATE__ACTIVATING,
                     mPhone.getPhoneId(), mId,
                     mApnSetting != null ? (long) mApnSetting.getApnTypeBitmask() : 0L,
                     mApnSetting != null
@@ -1869,6 +1980,10 @@
             // tear down the connection through networkAgent unwanted callback if all requests for
             // this connection are going away.
             mRestrictedNetworkOverride = shouldRestrictNetwork();
+
+            mPhone.getCarrierPrivilegesTracker()
+                    .registerCarrierPrivilegesListener(
+                            getHandler(), EVENT_CARRIER_PRIVILEGED_UIDS_CHANGED, null);
         }
         @Override
         public boolean processMessage(Message msg) {
@@ -1958,6 +2073,12 @@
                     }
                     retVal = HANDLED;
                     break;
+                case EVENT_CARRIER_PRIVILEGED_UIDS_CHANGED:
+                    AsyncResult asyncResult = (AsyncResult) msg.obj;
+                    int[] administratorUids = (int[]) asyncResult.result;
+                    mAdministratorUids = Arrays.copyOf(administratorUids, administratorUids.length);
+                    retVal = HANDLED;
+                    break;
                 default:
                     if (VDBG) {
                         log("DcActivatingState not handled msg.what=" +
@@ -1978,8 +2099,8 @@
 
         @Override public void enter() {
             if (DBG) log("DcActiveState: enter dc=" + DataConnection.this);
-            StatsLog.write(StatsLog.MOBILE_CONNECTION_STATE_CHANGED,
-                    StatsLog.MOBILE_CONNECTION_STATE_CHANGED__STATE__ACTIVE,
+            TelephonyStatsLog.write(TelephonyStatsLog.MOBILE_CONNECTION_STATE_CHANGED,
+                    TelephonyStatsLog.MOBILE_CONNECTION_STATE_CHANGED__STATE__ACTIVE,
                     mPhone.getPhoneId(), mId,
                     mApnSetting != null ? (long) mApnSetting.getApnTypeBitmask() : 0L,
                     mApnSetting != null
@@ -2004,18 +2125,28 @@
                     mNetworkInfo.getReason(), null);
             mNetworkInfo.setExtraInfo(mApnSetting.getApnName());
             updateTcpBufferSizes(mRilRat);
+            updateLinkBandwidthsFromCarrierConfig(mRilRat);
 
-            final NetworkMisc misc = new NetworkMisc();
+            final NetworkAgentConfig.Builder configBuilder = new NetworkAgentConfig.Builder();
+            configBuilder.setLegacyType(ConnectivityManager.TYPE_MOBILE);
+            configBuilder.setLegacyTypeName(NETWORK_TYPE);
+            configBuilder.setLegacyExtraInfo(mApnSetting.getApnName());
             final CarrierSignalAgent carrierSignalAgent = mPhone.getCarrierSignalAgent();
-            if (carrierSignalAgent.hasRegisteredReceivers(TelephonyIntents
+            if (carrierSignalAgent.hasRegisteredReceivers(TelephonyManager
                     .ACTION_CARRIER_SIGNAL_REDIRECTED)) {
                 // carrierSignal Receivers will place the carrier-specific provisioning notification
-                misc.provisioningNotificationDisabled = true;
+                configBuilder.disableProvisioningNotification();
             }
-            misc.subscriberId = mPhone.getSubscriberId();
+
+            final String subscriberId = mPhone.getSubscriberId();
+            if (!TextUtils.isEmpty(subscriberId)) {
+                configBuilder.setSubscriberId(subscriberId);
+            }
 
             // set skip464xlat if it is not default otherwise
-            misc.skip464xlat = shouldSkip464Xlat();
+            if (shouldSkip464Xlat()) {
+                configBuilder.disableNat64Detection();
+            }
 
             mUnmeteredUseOnly = isUnmeteredUseOnly();
 
@@ -2041,7 +2172,8 @@
                 }
 
                 if (mHandoverSourceNetworkAgent != null) {
-                    String logStr = "Transfer network agent successfully.";
+                    String logStr = "Transfer network agent " + mHandoverSourceNetworkAgent.getTag()
+                            + " successfully.";
                     log(logStr);
                     mHandoverLocalLog.log(logStr);
                     mNetworkAgent = mHandoverSourceNetworkAgent;
@@ -2066,13 +2198,17 @@
                 mScore = calculateScore();
                 final NetworkFactory factory = PhoneFactory.getNetworkFactory(
                         mPhone.getPhoneId());
-                final int factorySerialNumber = (null == factory)
-                        ? NetworkFactory.SerialNumber.NONE : factory.getSerialNumber();
+                final NetworkProvider provider = (null == factory) ? null : factory.getProvider();
 
                 mDisabledApnTypeBitMask |= getDisallowedApnTypes();
 
-                mNetworkAgent = DcNetworkAgent.createDcNetworkAgent(DataConnection.this,
-                        mPhone, mNetworkInfo, mScore, misc, factorySerialNumber, mTransportType);
+                mNetworkAgent = new DcNetworkAgent(DataConnection.this,
+                        mPhone, mNetworkInfo, mScore, configBuilder.build(), provider,
+                        mTransportType);
+                // All network agents start out in CONNECTING mode, but DcNetworkAgents are
+                // created when the network is already connected. Hence, send the connected
+                // notification immediately.
+                mNetworkAgent.markConnected();
             }
 
             if (mTransportType == AccessNetworkConstants.TRANSPORT_TYPE_WWAN) {
@@ -2122,6 +2258,8 @@
 
             TelephonyMetrics.getInstance().writeRilDataCallEvent(mPhone.getPhoneId(),
                     mCid, mApnSetting.getApnTypeBitmask(), RilDataCall.State.DISCONNECTED);
+
+            mPhone.getCarrierPrivilegesTracker().unregisterCarrierPrivilegesListener(getHandler());
         }
 
         @Override
@@ -2206,6 +2344,42 @@
                     retVal = HANDLED;
                     break;
                 }
+                case EVENT_DATA_CONNECTION_DRS_OR_RAT_CHANGED: {
+                    AsyncResult ar = (AsyncResult) msg.obj;
+                    Pair<Integer, Integer> drsRatPair = (Pair<Integer, Integer>) ar.result;
+                    mDataRegState = drsRatPair.first;
+                    updateTcpBufferSizes(drsRatPair.second);
+                    if (isBandwidthSourceKey(DctConstants.BANDWIDTH_SOURCE_CARRIER_CONFIG_KEY)) {
+                        updateLinkBandwidthsFromCarrierConfig(drsRatPair.second);
+                    }
+                    mRilRat = drsRatPair.second;
+                    if (DBG) {
+                        log("DcActiveState: EVENT_DATA_CONNECTION_DRS_OR_RAT_CHANGED"
+                                + " drs=" + mDataRegState
+                                + " mRilRat=" + mRilRat);
+                    }
+                    updateNetworkInfoSuspendState();
+                    if (mNetworkAgent != null) {
+                        mNetworkAgent.sendNetworkCapabilities(getNetworkCapabilities(),
+                                DataConnection.this);
+                        mNetworkAgent.sendNetworkInfo(mNetworkInfo, DataConnection.this);
+                        mNetworkAgent.sendLinkProperties(mLinkProperties, DataConnection.this);
+                    }
+                    retVal = HANDLED;
+                    break;
+                }
+                case EVENT_NR_FREQUENCY_CHANGED:
+                    // fallthrough
+                case EVENT_CARRIER_CONFIG_LINK_BANDWIDTHS_CHANGED:
+                    if (isBandwidthSourceKey(DctConstants.BANDWIDTH_SOURCE_CARRIER_CONFIG_KEY)) {
+                        updateLinkBandwidthsFromCarrierConfig(mRilRat);
+                    }
+                    if (mNetworkAgent != null) {
+                        mNetworkAgent.sendNetworkCapabilities(getNetworkCapabilities(),
+                                DataConnection.this);
+                    }
+                    retVal = HANDLED;
+                    break;
                 case EVENT_DATA_CONNECTION_METEREDNESS_CHANGED:
                     boolean isUnmetered = (boolean) msg.obj;
                     if (isUnmetered == mUnmeteredOverride) {
@@ -2221,7 +2395,6 @@
                     if (mNetworkAgent != null) {
                         mNetworkAgent.sendNetworkCapabilities(getNetworkCapabilities(),
                                 DataConnection.this);
-                        mNetworkAgent.sendNetworkInfo(mNetworkInfo, DataConnection.this);
                     }
                     retVal = HANDLED;
                     break;
@@ -2231,21 +2404,8 @@
                     if (ar.exception != null) {
                         log("EVENT_BW_REFRESH_RESPONSE: error ignoring, e=" + ar.exception);
                     } else {
-                        boolean useModem = DctConstants.BANDWIDTH_SOURCE_MODEM_KEY.equals(
-                                mPhone.getContext().getResources().getString(com.android.internal.R
-                                        .string.config_bandwidthEstimateSource));
-                        NetworkCapabilities nc = getNetworkCapabilities();
-                        LinkCapacityEstimate lce = (LinkCapacityEstimate) ar.result;
-                        if (useModem && mPhone.getLceStatus() == RILConstants.LCE_ACTIVE) {
-                            if (lce.downlinkCapacityKbps != LinkCapacityEstimate.INVALID) {
-                                nc.setLinkDownstreamBandwidthKbps(lce.downlinkCapacityKbps);
-                            }
-                            if (lce.uplinkCapacityKbps != LinkCapacityEstimate.INVALID) {
-                                nc.setLinkUpstreamBandwidthKbps(lce.uplinkCapacityKbps);
-                            }
-                        }
-                        if (mNetworkAgent != null) {
-                            mNetworkAgent.sendNetworkCapabilities(nc, DataConnection.this);
+                        if (isBandwidthSourceKey(DctConstants.BANDWIDTH_SOURCE_MODEM_KEY)) {
+                            updateLinkBandwidthsFromModem((LinkCapacityEstimate) ar.result);
                         }
                     }
                     retVal = HANDLED;
@@ -2281,7 +2441,7 @@
                         // so that keepalive requests can be handled (if supported) by the
                         // underlying transport.
                         if (mNetworkAgent != null) {
-                            mNetworkAgent.onSocketKeepaliveEvent(
+                            mNetworkAgent.sendSocketKeepaliveEvent(
                                     msg.arg1, SocketKeepalive.ERROR_INVALID_NETWORK);
                         }
                     }
@@ -2293,6 +2453,8 @@
                     int handle = mNetworkAgent.keepaliveTracker.getHandleForSlot(slotId);
                     if (handle < 0) {
                         loge("No slot found for stopSocketKeepalive! " + slotId);
+                        mNetworkAgent.sendSocketKeepaliveEvent(
+                                slotId, SocketKeepalive.NO_KEEPALIVE);
                         retVal = HANDLED;
                         break;
                     } else {
@@ -2311,7 +2473,7 @@
                     if (ar.exception != null || ar.result == null) {
                         loge("EVENT_KEEPALIVE_STARTED: error starting keepalive, e="
                                 + ar.exception);
-                        mNetworkAgent.onSocketKeepaliveEvent(
+                        mNetworkAgent.sendSocketKeepaliveEvent(
                                 slot, SocketKeepalive.ERROR_HARDWARE_ERROR);
                     } else {
                         KeepaliveStatus ks = (KeepaliveStatus) ar.result;
@@ -2361,21 +2523,8 @@
                     if (ar.exception != null) {
                         loge("EVENT_LINK_CAPACITY_CHANGED e=" + ar.exception);
                     } else {
-                        boolean useModem = DctConstants.BANDWIDTH_SOURCE_MODEM_KEY.equals(
-                                mPhone.getContext().getResources().getString(com.android.internal.R
-                                        .string.config_bandwidthEstimateSource));
-                        NetworkCapabilities nc = getNetworkCapabilities();
-                        if (useModem) {
-                            LinkCapacityEstimate lce = (LinkCapacityEstimate) ar.result;
-                            if (lce.downlinkCapacityKbps != LinkCapacityEstimate.INVALID) {
-                                nc.setLinkDownstreamBandwidthKbps(lce.downlinkCapacityKbps);
-                            }
-                            if (lce.uplinkCapacityKbps != LinkCapacityEstimate.INVALID) {
-                                nc.setLinkUpstreamBandwidthKbps(lce.uplinkCapacityKbps);
-                            }
-                        }
-                        if (mNetworkAgent != null) {
-                            mNetworkAgent.sendNetworkCapabilities(nc, DataConnection.this);
+                        if (isBandwidthSourceKey(DctConstants.BANDWIDTH_SOURCE_MODEM_KEY)) {
+                            updateLinkBandwidthsFromModem((LinkCapacityEstimate) ar.result);
                         }
                     }
                     retVal = HANDLED;
@@ -2422,12 +2571,30 @@
                 }
                 case EVENT_NR_STATE_CHANGED: {
                     updateTcpBufferSizes(mRilRat);
+                    if (isBandwidthSourceKey(DctConstants.BANDWIDTH_SOURCE_CARRIER_CONFIG_KEY)) {
+                        updateLinkBandwidthsFromCarrierConfig(mRilRat);
+                    }
                     if (mNetworkAgent != null) {
                         mNetworkAgent.sendLinkProperties(mLinkProperties, DataConnection.this);
+                        mNetworkAgent.sendNetworkCapabilities(getNetworkCapabilities(),
+                                DataConnection.this);
                     }
                     retVal = HANDLED;
                     break;
                 }
+                case EVENT_CARRIER_PRIVILEGED_UIDS_CHANGED:
+                    AsyncResult asyncResult = (AsyncResult) msg.obj;
+                    int[] administratorUids = (int[]) asyncResult.result;
+                    mAdministratorUids = Arrays.copyOf(administratorUids, administratorUids.length);
+
+                    // Administrator UIDs changed, so update NetworkAgent with new
+                    // NetworkCapabilities
+                    if (mNetworkAgent != null) {
+                        mNetworkAgent.sendNetworkCapabilities(
+                                getNetworkCapabilities(), DataConnection.this);
+                    }
+                    retVal = HANDLED;
+                    break;
                 default:
                     if (VDBG) {
                         log("DcActiveState not handled msg.what=" + getWhatToString(msg.what));
@@ -2446,8 +2613,8 @@
     private class DcDisconnectingState extends State {
         @Override
         public void enter() {
-            StatsLog.write(StatsLog.MOBILE_CONNECTION_STATE_CHANGED,
-                    StatsLog.MOBILE_CONNECTION_STATE_CHANGED__STATE__DISCONNECTING,
+            TelephonyStatsLog.write(TelephonyStatsLog.MOBILE_CONNECTION_STATE_CHANGED,
+                    TelephonyStatsLog.MOBILE_CONNECTION_STATE_CHANGED__STATE__DISCONNECTING,
                     mPhone.getPhoneId(), mId,
                     mApnSetting != null ? (long) mApnSetting.getApnTypeBitmask() : 0L,
                     mApnSetting != null
@@ -2504,8 +2671,9 @@
     private class DcDisconnectionErrorCreatingConnection extends State {
         @Override
         public void enter() {
-            StatsLog.write(StatsLog.MOBILE_CONNECTION_STATE_CHANGED,
-                    StatsLog.MOBILE_CONNECTION_STATE_CHANGED__STATE__DISCONNECTION_ERROR_CREATING_CONNECTION,
+            TelephonyStatsLog.write(TelephonyStatsLog.MOBILE_CONNECTION_STATE_CHANGED,
+                    TelephonyStatsLog
+                            .MOBILE_CONNECTION_STATE_CHANGED__STATE__DISCONNECTION_ERROR_CREATING_CONNECTION,
                     mPhone.getPhoneId(), mId,
                     mApnSetting != null ? (long) mApnSetting.getApnTypeBitmask() : 0L,
                     mApnSetting != null
@@ -2567,16 +2735,17 @@
      *                             ignored if obsolete.
      * @param requestType Data request type
      * @param subId the subscription id associated with this data connection.
+     * @param isApnPreferred whether or not the apn is preferred.
      */
     public void bringUp(ApnContext apnContext, int profileId, int rilRadioTechnology,
                         Message onCompletedMsg, int connectionGeneration,
-                        @RequestNetworkType int requestType, int subId) {
+                        @RequestNetworkType int requestType, int subId, boolean isApnPreferred) {
         if (DBG) {
             log("bringUp: apnContext=" + apnContext + " onCompletedMsg=" + onCompletedMsg);
         }
         sendMessage(DataConnection.EVENT_CONNECT,
                 new ConnectionParams(apnContext, profileId, rilRadioTechnology, onCompletedMsg,
-                        connectionGeneration, requestType, subId));
+                        connectionGeneration, requestType, subId, isApnPreferred));
     }
 
     /**
@@ -2703,9 +2872,11 @@
     }
 
     void setHandoverState(@HandoverState int state) {
-        mHandoverLocalLog.log("State changed from " + handoverStateToString(mHandoverState)
-                + " to " + handoverStateToString(state));
-        mHandoverState = state;
+        if (mHandoverState != state) {
+            mHandoverLocalLog.log("State changed from " + handoverStateToString(mHandoverState)
+                    + " to " + handoverStateToString(state));
+            mHandoverState = state;
+        }
     }
 
     /**
@@ -2918,7 +3089,7 @@
         for (ApnContext apnContext : mApnContexts.keySet()) {
             for (NetworkRequest networkRequest : apnContext.getNetworkRequests()) {
                 if (networkRequest.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
-                        && networkRequest.networkCapabilities.getNetworkSpecifier() == null) {
+                        && networkRequest.getNetworkSpecifier() == null) {
                     score = DEFAULT_INTERNET_CONNECTION_SCORE;
                     break;
                 }
@@ -2976,9 +3147,11 @@
         pw.println("mSubscriptionOverride=" + Integer.toHexString(mSubscriptionOverride));
         pw.println("mRestrictedNetworkOverride=" + mRestrictedNetworkOverride);
         pw.println("mUnmeteredUseOnly=" + mUnmeteredUseOnly);
+        pw.println("mUnmeteredOverride=" + mUnmeteredOverride);
+        pw.println("mDownlinkBandwidth" + mDownlinkBandwidth);
+        pw.println("mUplinkBandwidth=" + mUplinkBandwidth);
         pw.println("disallowedApnTypes="
                 + ApnSetting.getApnTypesStringFromBitmask(getDisallowedApnTypes()));
-        pw.println("mUnmeteredOverride=" + mUnmeteredOverride);
         pw.println("mInstanceNumber=" + mInstanceNumber);
         pw.println("mAc=" + mAc);
         pw.println("mScore=" + mScore);
diff --git a/src/java/com/android/internal/telephony/dataconnection/DataConnectionReasons.java b/src/java/com/android/internal/telephony/dataconnection/DataConnectionReasons.java
index 9fc5b98..6aaadf5 100644
--- a/src/java/com/android/internal/telephony/dataconnection/DataConnectionReasons.java
+++ b/src/java/com/android/internal/telephony/dataconnection/DataConnectionReasons.java
@@ -106,7 +106,7 @@
 
         // Belows are all hard failure reasons.
         NOT_ATTACHED(true),
-        RECORD_NOT_LOADED(true),
+        SIM_NOT_READY(true),
         INVALID_PHONE_STATE(true),
         CONCURRENT_VOICE_DATA_NOT_ALLOWED(true),
         PS_RESTRICTED(true),
diff --git a/src/java/com/android/internal/telephony/dataconnection/DataEnabledOverride.java b/src/java/com/android/internal/telephony/dataconnection/DataEnabledOverride.java
index f639b36..a8145df 100644
--- a/src/java/com/android/internal/telephony/dataconnection/DataEnabledOverride.java
+++ b/src/java/com/android/internal/telephony/dataconnection/DataEnabledOverride.java
@@ -18,10 +18,10 @@
 
 import android.annotation.IntDef;
 import android.annotation.NonNull;
+import android.telephony.Annotation.ApnType;
 import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyManager;
 import android.telephony.data.ApnSetting;
-import android.telephony.data.ApnSetting.ApnType;
 import android.text.TextUtils;
 import android.util.ArrayMap;
 import android.util.Log;
@@ -340,7 +340,10 @@
     }
 
     /**
-     * Set allowing mobile data during voice call.
+     * Set allowing mobile data during voice call. This is used for allowing data on the non-default
+     * data SIM. When a voice call is placed on the non-default data SIM on DSDS devices, users will
+     * not be able to use mobile data. By calling this API, data will be temporarily enabled on the
+     * non-default data SIM during the life cycle of the voice call.
      *
      * @param allow {@code true} if allowing using data during voice call, {@code false} if
      * disallowed.
diff --git a/src/java/com/android/internal/telephony/dataconnection/DataEnabledSettings.java b/src/java/com/android/internal/telephony/dataconnection/DataEnabledSettings.java
index 1b05487..10c9db6 100644
--- a/src/java/com/android/internal/telephony/dataconnection/DataEnabledSettings.java
+++ b/src/java/com/android/internal/telephony/dataconnection/DataEnabledSettings.java
@@ -27,9 +27,10 @@
 import android.os.RegistrantList;
 import android.os.SystemProperties;
 import android.provider.Settings;
+import android.sysprop.TelephonyProperties;
+import android.telephony.Annotation.CallState;
 import android.telephony.CarrierConfigManager;
 import android.telephony.PhoneStateListener;
-import android.telephony.Rlog;
 import android.telephony.SubscriptionInfo;
 import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyManager;
@@ -41,6 +42,7 @@
 import com.android.internal.telephony.MultiSimSettingController;
 import com.android.internal.telephony.Phone;
 import com.android.internal.telephony.SubscriptionController;
+import com.android.telephony.Rlog;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
@@ -157,7 +159,7 @@
 
     private final PhoneStateListener mPhoneStateListener = new PhoneStateListener() {
         @Override
-        public void onCallStateChanged(@TelephonyManager.CallState int state, String phoneNumber) {
+        public void onCallStateChanged(@CallState int state, String phoneNumber) {
             updateDataEnabledAndNotify(REASON_OVERRIDE_CONDITION_CHANGED);
         }
     };
@@ -235,8 +237,7 @@
         // User data should always be true for opportunistic subscription.
         if (isStandAloneOpportunistic(mPhone.getSubId(), mPhone.getContext())) return true;
 
-        boolean defaultVal = "true".equalsIgnoreCase(SystemProperties.get(
-                "ro.com.android.mobiledata", "true"));
+        boolean defaultVal = TelephonyProperties.mobile_data().orElse(true);
 
         return GlobalSettingsHelper.getBoolean(mPhone.getContext(),
                 Settings.Global.MOBILE_DATA, mPhone.getSubId(), defaultVal);
@@ -263,16 +264,23 @@
     }
 
     /**
-     * Set allowing mobile data during voice call.
+     * Set allowing mobile data during voice call. This is used for allowing data on the non-default
+     * data SIM. When a voice call is placed on the non-default data SIM on DSDS devices, users will
+     * not be able to use mobile data. By calling this API, data will be temporarily enabled on the
+     * non-default data SIM during the life cycle of the voice call.
      *
      * @param allow {@code true} if allowing using data during voice call, {@code false} if
      * disallowed
      *
-     * @return {@code false} if the setting is changed.
+     * @return {@code true} if operation is successful. otherwise {@code false}.
      */
     public synchronized boolean setAllowDataDuringVoiceCall(boolean allow) {
         localLog("setAllowDataDuringVoiceCall", allow);
+        if (allow == isDataAllowedInVoiceCall()) {
+            return true;
+        }
         mDataEnabledOverride.setDataAllowedInVoiceCall(allow);
+
         boolean changed = SubscriptionController.getInstance()
                 .setDataEnabledOverrideRules(mPhone.getSubId(), mDataEnabledOverride.getRules());
         if (changed) {
@@ -447,7 +455,7 @@
 
     private static boolean isStandAloneOpportunistic(int subId, Context context) {
         SubscriptionInfo info = SubscriptionController.getInstance().getActiveSubscriptionInfo(
-                subId, context.getOpPackageName());
+                subId, context.getOpPackageName(), context.getAttributionTag());
         return (info != null) && info.isOpportunistic() && info.getGroupUuid() == null;
     }
 
diff --git a/src/java/com/android/internal/telephony/dataconnection/DataServiceManager.java b/src/java/com/android/internal/telephony/dataconnection/DataServiceManager.java
index a5199d7..120a3e0 100644
--- a/src/java/com/android/internal/telephony/dataconnection/DataServiceManager.java
+++ b/src/java/com/android/internal/telephony/dataconnection/DataServiceManager.java
@@ -17,6 +17,7 @@
 package com.android.internal.telephony.dataconnection;
 
 import static android.text.format.DateUtils.MINUTE_IN_MILLIS;
+import static android.text.format.DateUtils.SECOND_IN_MILLIS;
 
 import android.annotation.NonNull;
 import android.app.AppOpsManager;
@@ -26,7 +27,6 @@
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.content.ServiceConnection;
-import android.content.pm.IPackageManager;
 import android.content.pm.PackageManager;
 import android.content.pm.ResolveInfo;
 import android.net.LinkProperties;
@@ -37,13 +37,13 @@
 import android.os.PersistableBundle;
 import android.os.RegistrantList;
 import android.os.RemoteException;
-import android.os.ServiceManager;
 import android.os.UserHandle;
+import android.permission.PermissionManager;
 import android.telephony.AccessNetworkConstants;
 import android.telephony.AccessNetworkConstants.TransportType;
 import android.telephony.AnomalyReporter;
 import android.telephony.CarrierConfigManager;
-import android.telephony.Rlog;
+import android.telephony.SubscriptionManager;
 import android.telephony.data.DataCallResponse;
 import android.telephony.data.DataProfile;
 import android.telephony.data.DataService;
@@ -53,6 +53,9 @@
 import android.text.TextUtils;
 
 import com.android.internal.telephony.Phone;
+import com.android.internal.telephony.PhoneConfigurationManager;
+import com.android.internal.telephony.util.TelephonyUtils;
+import com.android.telephony.Rlog;
 
 import java.util.HashSet;
 import java.util.List;
@@ -60,6 +63,7 @@
 import java.util.Set;
 import java.util.UUID;
 import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.CountDownLatch;
 
 /**
  * Data service manager manages handling data requests and responses on data services (e.g.
@@ -76,13 +80,15 @@
 
     private static final long REQUEST_UNRESPONDED_TIMEOUT = 10 * MINUTE_IN_MILLIS; // 10 mins
 
+    private static final long CHANGE_PERMISSION_TIMEOUT_MS = 15 * SECOND_IN_MILLIS; // 15 secs
+
     private final Phone mPhone;
 
     private final String mTag;
 
     private final CarrierConfigManager mCarrierConfigManager;
     private final AppOpsManager mAppOps;
-    private final IPackageManager mPackageManager;
+    private final PermissionManager mPermissionManager;
 
     private final int mTransportType;
 
@@ -129,14 +135,23 @@
 
     private void grantPermissionsToService(String packageName) {
         final String[] pkgToGrant = {packageName};
+        CountDownLatch latch = new CountDownLatch(1);
         try {
-            mPackageManager.grantDefaultPermissionsToEnabledTelephonyDataServices(
-                    pkgToGrant, mPhone.getContext().getUserId());
-            mAppOps.setMode(AppOpsManager.OP_MANAGE_IPSEC_TUNNELS, mPhone.getContext().getUserId(),
-                    pkgToGrant[0], AppOpsManager.MODE_ALLOWED);
-        } catch (RemoteException e) {
+            mPermissionManager.grantDefaultPermissionsToEnabledTelephonyDataServices(
+                    pkgToGrant, UserHandle.of(UserHandle.myUserId()), Runnable::run,
+                    isSuccess -> {
+                        if (isSuccess) {
+                            latch.countDown();
+                        } else {
+                            loge("Failed to grant permissions to service.");
+                        }
+                    });
+            TelephonyUtils.waitUntilReady(latch, CHANGE_PERMISSION_TIMEOUT_MS);
+            mAppOps.setMode(AppOpsManager.OPSTR_MANAGE_IPSEC_TUNNELS,
+                UserHandle.myUserId(), pkgToGrant[0], AppOpsManager.MODE_ALLOWED);
+        } catch (RuntimeException e) {
             loge("Binder to package manager died, permission grant for DataService failed.");
-            throw e.rethrowAsRuntimeException();
+            throw e;
         }
     }
 
@@ -151,19 +166,27 @@
             dataServices.remove(getDataServicePackageName(transportType));
         }
 
+        CountDownLatch latch = new CountDownLatch(1);
         try {
             String[] dataServicesArray = new String[dataServices.size()];
             dataServices.toArray(dataServicesArray);
-            mPackageManager.revokeDefaultPermissionsFromDisabledTelephonyDataServices(
-                    dataServicesArray, mPhone.getContext().getUserId());
+            mPermissionManager.revokeDefaultPermissionsFromDisabledTelephonyDataServices(
+                    dataServicesArray, UserHandle.of(UserHandle.myUserId()), Runnable::run,
+                    isSuccess -> {
+                        if (isSuccess) {
+                            latch.countDown();
+                        } else {
+                            loge("Failed to revoke permissions from data services.");
+                        }
+                    });
+            TelephonyUtils.waitUntilReady(latch, CHANGE_PERMISSION_TIMEOUT_MS);
             for (String pkg : dataServices) {
-                mAppOps.setMode(AppOpsManager.OP_MANAGE_IPSEC_TUNNELS,
-                        mPhone.getContext().getUserId(),
+                mAppOps.setMode(AppOpsManager.OPSTR_MANAGE_IPSEC_TUNNELS, UserHandle.myUserId(),
                         pkg, AppOpsManager.MODE_ERRORED);
             }
-        } catch (RemoteException e) {
+        } catch (RuntimeException e) {
             loge("Binder to package manager died; failed to revoke DataService permissions.");
-            throw e.rethrowAsRuntimeException();
+            throw e;
         }
     }
 
@@ -192,7 +215,6 @@
         public void onServiceDisconnected(ComponentName name) {
             if (DBG) log("onServiceDisconnected");
             removeMessages(EVENT_WATCHDOG_TIMEOUT);
-            mIDataService.asBinder().unlinkToDeath(mDeathRecipient, 0);
             mIDataService = null;
             mBound = false;
             mServiceBindingChangedRegistrants.notifyResult(false);
@@ -280,13 +302,26 @@
         mBound = false;
         mCarrierConfigManager = (CarrierConfigManager) phone.getContext().getSystemService(
                 Context.CARRIER_CONFIG_SERVICE);
-        mPackageManager = IPackageManager.Stub.asInterface(ServiceManager.getService("package"));
+        // NOTE: Do NOT use AppGlobals to retrieve the permission manager; AppGlobals
+        // caches the service instance, but we need to explicitly request a new service
+        // so it can be mocked out for tests
+        mPermissionManager =
+                (PermissionManager) phone.getContext().getSystemService(Context.PERMISSION_SERVICE);
         mAppOps = (AppOpsManager) phone.getContext().getSystemService(Context.APP_OPS_SERVICE);
 
         IntentFilter intentFilter = new IntentFilter();
         intentFilter.addAction(CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED);
-        phone.getContext().registerReceiverAsUser(mBroadcastReceiver, UserHandle.ALL,
-                intentFilter, null, null);
+        try {
+            Context contextAsUser = phone.getContext().createPackageContextAsUser(
+                phone.getContext().getPackageName(), 0, UserHandle.ALL);
+            contextAsUser.registerReceiver(mBroadcastReceiver, intentFilter,
+                null /* broadcastPermission */, null);
+        } catch (PackageManager.NameNotFoundException e) {
+            loge("Package name not found: " + e.getMessage());
+        }
+        PhoneConfigurationManager.registerForMultiSimConfigChange(
+                this, EVENT_BIND_DATA_SERVICE, null);
+
         sendEmptyMessage(EVENT_BIND_DATA_SERVICE);
     }
 
@@ -299,7 +334,7 @@
     public void handleMessage(Message msg) {
         switch (msg.what) {
             case EVENT_BIND_DATA_SERVICE:
-                bindDataService();
+                rebindDataService();
                 break;
             case EVENT_WATCHDOG_TIMEOUT:
                 handleRequestUnresponded((CellularDataServiceCallback) msg.obj);
@@ -320,31 +355,48 @@
                 message);
     }
 
-    private void bindDataService() {
-        String packageName = getDataServicePackageName();
-        if (TextUtils.isEmpty(packageName)) {
-            loge("Can't find the binding package");
-            return;
-        }
-
-        if (TextUtils.equals(packageName, mTargetBindingPackageName)) {
-            if (DBG) log("Service " + packageName + " already bound or being bound.");
-            return;
-        }
-
+    private void unbindDataService() {
         // Start by cleaning up all packages that *shouldn't* have permissions.
         revokePermissionsFromUnusedDataServices();
-
         if (mIDataService != null && mIDataService.asBinder().isBinderAlive()) {
+            log("unbinding service");
             // Remove the network availability updater and then unbind the service.
             try {
                 mIDataService.removeDataServiceProvider(mPhone.getPhoneId());
             } catch (RemoteException e) {
                 loge("Cannot remove data service provider. " + e);
             }
+        }
 
+        if (mServiceConnection != null) {
             mPhone.getContext().unbindService(mServiceConnection);
         }
+        mIDataService = null;
+        mServiceConnection = null;
+        mTargetBindingPackageName = null;
+        mBound = false;
+    }
+
+    private void bindDataService(String packageName) {
+        if (mPhone == null || !SubscriptionManager.isValidPhoneId(mPhone.getPhoneId())) {
+            loge("can't bindDataService with invalid phone or phoneId.");
+            return;
+        }
+
+        if (TextUtils.isEmpty(packageName)) {
+            loge("Can't find the binding package");
+            return;
+        }
+
+        Intent intent = null;
+        String className = getDataServiceClassName();
+        if (TextUtils.isEmpty(className)) {
+            intent = new Intent(DataService.SERVICE_INTERFACE);
+            intent.setPackage(packageName);
+        } else {
+            ComponentName cm = new ComponentName(packageName, className);
+            intent = new Intent(DataService.SERVICE_INTERFACE).setComponent(cm);
+        }
 
         // Then pre-emptively grant the permissions to the package we will bind.
         grantPermissionsToService(packageName);
@@ -352,9 +404,7 @@
         try {
             mServiceConnection = new CellularDataServiceConnection();
             if (!mPhone.getContext().bindService(
-                    new Intent(DataService.SERVICE_INTERFACE).setPackage(packageName),
-                    mServiceConnection,
-                    Context.BIND_AUTO_CREATE)) {
+                    intent, mServiceConnection, Context.BIND_AUTO_CREATE)) {
                 loge("Cannot bind to the data service.");
                 return;
             }
@@ -364,6 +414,19 @@
         }
     }
 
+    private void rebindDataService() {
+        String packageName = getDataServicePackageName();
+        // Do nothing if no need to rebind.
+        if (SubscriptionManager.isValidPhoneId(mPhone.getPhoneId())
+                && TextUtils.equals(packageName, mTargetBindingPackageName)) {
+            if (DBG) log("Service " + packageName + " already bound or being bound.");
+            return;
+        }
+
+        unbindDataService();
+        bindDataService(packageName);
+    }
+
     @NonNull
     private Set<String> getAllDataServicePackageNames() {
         // Cowardly using the public PackageManager interface here.
@@ -435,6 +498,55 @@
         return packageName;
     }
 
+    /**
+     * Get the data service class name for our current transport type.
+     *
+     * @return class name of the data service package for the the current transportType.
+     */
+    private String getDataServiceClassName() {
+        return getDataServiceClassName(mTransportType);
+    }
+
+
+    /**
+     * Get the data service class by transport type.
+     *
+     * @param transportType either WWAN or WLAN
+     * @return class name of the data service package for the specified transportType.
+     */
+    private String getDataServiceClassName(int transportType) {
+        String className;
+        int resourceId;
+        String carrierConfig;
+        switch (transportType) {
+            case AccessNetworkConstants.TRANSPORT_TYPE_WWAN:
+                resourceId = com.android.internal.R.string.config_wwan_data_service_class;
+                carrierConfig = CarrierConfigManager
+                        .KEY_CARRIER_DATA_SERVICE_WWAN_CLASS_OVERRIDE_STRING;
+                break;
+            case AccessNetworkConstants.TRANSPORT_TYPE_WLAN:
+                resourceId = com.android.internal.R.string.config_wlan_data_service_class;
+                carrierConfig = CarrierConfigManager
+                        .KEY_CARRIER_DATA_SERVICE_WLAN_CLASS_OVERRIDE_STRING;
+                break;
+            default:
+                throw new IllegalStateException("Transport type not WWAN or WLAN. type="
+                        + transportType);
+        }
+
+        // Read package name from resource overlay
+        className = mPhone.getContext().getResources().getString(resourceId);
+
+        PersistableBundle b = mCarrierConfigManager.getConfigForSubId(mPhone.getSubId());
+
+        if (b != null && !TextUtils.isEmpty(b.getString(carrierConfig))) {
+            // If carrier config overrides it, use the one from carrier config
+            className = b.getString(carrierConfig, className);
+        }
+
+        return className;
+    }
+
     private void sendCompleteMessage(Message msg, int code) {
         if (msg != null) {
             msg.arg1 = code;
@@ -680,5 +792,4 @@
     private void loge(String s) {
         Rlog.e(mTag, s);
     }
-
 }
diff --git a/src/java/com/android/internal/telephony/dataconnection/DcController.java b/src/java/com/android/internal/telephony/dataconnection/DcController.java
index 5901985..f2125d1 100644
--- a/src/java/com/android/internal/telephony/dataconnection/DcController.java
+++ b/src/java/com/android/internal/telephony/dataconnection/DcController.java
@@ -16,30 +16,36 @@
 
 package com.android.internal.telephony.dataconnection;
 
+import android.annotation.IntDef;
 import android.content.Context;
 import android.hardware.radio.V1_4.DataConnActiveStatus;
 import android.net.LinkAddress;
-import android.net.LinkProperties.CompareResult;
-import android.net.NetworkUtils;
 import android.os.AsyncResult;
-import android.os.Build;
 import android.os.Handler;
 import android.os.Message;
+import android.os.RegistrantList;
 import android.telephony.AccessNetworkConstants;
 import android.telephony.DataFailCause;
 import android.telephony.PhoneStateListener;
-import android.telephony.Rlog;
 import android.telephony.TelephonyManager;
 import android.telephony.data.DataCallResponse;
 
 import com.android.internal.telephony.DctConstants;
 import com.android.internal.telephony.Phone;
 import com.android.internal.telephony.dataconnection.DataConnection.UpdateLinkPropertyResult;
+import com.android.internal.telephony.util.HandlerExecutor;
+import com.android.internal.telephony.util.TelephonyUtils;
 import com.android.internal.util.State;
 import com.android.internal.util.StateMachine;
+import com.android.net.module.util.LinkPropertiesUtils;
+import com.android.net.module.util.LinkPropertiesUtils.CompareResult;
+import com.android.net.module.util.NetUtils;
+import com.android.telephony.Rlog;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.List;
@@ -53,6 +59,24 @@
     private static final boolean DBG = true;
     private static final boolean VDBG = false;
 
+    /** Physical link state unknown */
+    public static final int PHYSICAL_LINK_UNKNOWN = 0;
+
+    /** Physical link state inactive (i.e. RRC idle) */
+    public static final int PHYSICAL_LINK_NOT_ACTIVE = 1;
+
+    /** Physical link state active (i.e. RRC connected) */
+    public static final int PHYSICAL_LINK_ACTIVE = 2;
+
+    /** @hide */
+    @IntDef(prefix = { "PHYSICAL_LINK_" }, value = {
+            PHYSICAL_LINK_UNKNOWN,
+            PHYSICAL_LINK_NOT_ACTIVE,
+            PHYSICAL_LINK_ACTIVE
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface PhysicalLinkState{}
+
     private final Phone mPhone;
     private final DcTracker mDct;
     private final DataServiceManager mDataServiceManager;
@@ -75,6 +99,16 @@
     private volatile boolean mExecutingCarrierChange;
 
     /**
+     * Aggregated physical link state from all data connections. This reflects the device's RRC
+     * connection state.
+     * // TODO: Instead of tracking the RRC state here, we should make PhysicalChannelConfig work in
+     *          S.
+     */
+    private @PhysicalLinkState int mPhysicalLinkState = PHYSICAL_LINK_UNKNOWN;
+
+    private RegistrantList mPhysicalLinkStateChangedRegistrants = new RegistrantList();
+
+    /**
      * Constructor.
      *
      * @param name to be used for the Controller
@@ -95,7 +129,7 @@
         setInitialState(mDccDefaultState);
         log("X ctor");
 
-        mPhoneStateListener = new PhoneStateListener(handler.getLooper()) {
+        mPhoneStateListener = new PhoneStateListener(new HandlerExecutor(handler)) {
             @Override
             public void onCarrierNetworkChange(boolean active) {
                 mExecutingCarrierChange = active;
@@ -105,7 +139,7 @@
         mTelephonyManager = (TelephonyManager) phone.getContext()
                 .getSystemService(Context.TELEPHONY_SERVICE);
 
-        mDcTesterDeactivateAll = (Build.IS_DEBUGGABLE)
+        mDcTesterDeactivateAll = (TelephonyUtils.IS_DEBUGGABLE)
                 ? new DcTesterDeactivateAll(mPhone, DcController.this, getHandler())
                 : null;
 
@@ -321,15 +355,21 @@
                         if (result.oldLp.equals(result.newLp)) {
                             if (DBG) log("onDataStateChanged: no change");
                         } else {
-                            if (result.oldLp.isIdenticalInterfaceName(result.newLp)) {
-                                if (! result.oldLp.isIdenticalDnses(result.newLp) ||
-                                        ! result.oldLp.isIdenticalRoutes(result.newLp) ||
-                                        ! result.oldLp.isIdenticalHttpProxy(result.newLp) ||
-                                        ! result.oldLp.isIdenticalAddresses(result.newLp)) {
+                            if (LinkPropertiesUtils.isIdenticalInterfaceName(
+                                    result.oldLp, result.newLp)) {
+                                if (!LinkPropertiesUtils.isIdenticalDnses(
+                                        result.oldLp, result.newLp)
+                                        || !LinkPropertiesUtils.isIdenticalRoutes(
+                                                result.oldLp, result.newLp)
+                                        || !LinkPropertiesUtils.isIdenticalHttpProxy(
+                                                result.oldLp, result.newLp)
+                                        || !LinkPropertiesUtils.isIdenticalAddresses(
+                                                result.oldLp, result.newLp)) {
                                     // If the same address type was removed and
                                     // added we need to cleanup
                                     CompareResult<LinkAddress> car =
-                                        result.oldLp.compareAddresses(result.newLp);
+                                            LinkPropertiesUtils.compareAddresses(result.oldLp,
+                                                    result.newLp);
                                     if (DBG) {
                                         log("onDataStateChanged: oldLp=" + result.oldLp +
                                                 " newLp=" + result.newLp + " car=" + car);
@@ -337,7 +377,7 @@
                                     boolean needToClean = false;
                                     for (LinkAddress added : car.added) {
                                         for (LinkAddress removed : car.removed) {
-                                            if (NetworkUtils.addressTypeMatches(
+                                            if (NetUtils.addressTypeMatches(
                                                     removed.getAddress(),
                                                     added.getAddress())) {
                                                 needToClean = true;
@@ -384,24 +424,33 @@
                 }
             }
 
-            if (isAnyDataCallDormant && !isAnyDataCallActive) {
-                // There is no way to indicate link activity per APN right now. So
-                // Link Activity will be considered dormant only when all data calls
-                // are dormant.
-                // If a single data call is in dormant state and none of the data
-                // calls are active broadcast overall link state as dormant.
-                if (DBG) {
-                    log("onDataStateChanged: Data Activity updated to DORMANT. stopNetStatePoll");
+            if (mDataServiceManager.getTransportType()
+                    == AccessNetworkConstants.TRANSPORT_TYPE_WWAN) {
+                int physicalLinkState = isAnyDataCallActive
+                        ? PHYSICAL_LINK_ACTIVE : PHYSICAL_LINK_NOT_ACTIVE;
+                if (mPhysicalLinkState != physicalLinkState) {
+                    mPhysicalLinkState = physicalLinkState;
+                    mPhysicalLinkStateChangedRegistrants.notifyResult(mPhysicalLinkState);
                 }
-                mDct.sendStopNetStatPoll(DctConstants.Activity.DORMANT);
-            } else {
-                if (DBG) {
-                    log("onDataStateChanged: Data Activity updated to NONE. " +
-                            "isAnyDataCallActive = " + isAnyDataCallActive +
-                            " isAnyDataCallDormant = " + isAnyDataCallDormant);
-                }
-                if (isAnyDataCallActive) {
-                    mDct.sendStartNetStatPoll(DctConstants.Activity.NONE);
+                if (isAnyDataCallDormant && !isAnyDataCallActive) {
+                    // There is no way to indicate link activity per APN right now. So
+                    // Link Activity will be considered dormant only when all data calls
+                    // are dormant.
+                    // If a single data call is in dormant state and none of the data
+                    // calls are active broadcast overall link state as dormant.
+                    if (DBG) {
+                        log("onDataStateChanged: Data activity DORMANT. stopNetStatePoll");
+                    }
+                    mDct.sendStopNetStatPoll(DctConstants.Activity.DORMANT);
+                } else {
+                    if (DBG) {
+                        log("onDataStateChanged: Data Activity updated to NONE. "
+                                + "isAnyDataCallActive = " + isAnyDataCallActive
+                                + " isAnyDataCallDormant = " + isAnyDataCallDormant);
+                    }
+                    if (isAnyDataCallActive) {
+                        mDct.sendStartNetStatPoll(DctConstants.Activity.NONE);
+                    }
                 }
             }
 
@@ -426,6 +475,25 @@
     }
 
     /**
+     * Register for physical link state (i.e. RRC state) changed event.
+     *
+     * @param h The handler
+     * @param what The event
+     */
+    public void registerForPhysicalLinkStateChanged(Handler h, int what) {
+        mPhysicalLinkStateChangedRegistrants.addUnique(h, what, null);
+    }
+
+    /**
+     * Unregister from physical link state (i.e. RRC state) changed event.
+     *
+     * @param h The previously registered handler
+     */
+    public void unregisterForPhysicalLinkStateChanged(Handler h) {
+        mPhysicalLinkStateChangedRegistrants.remove(h);
+    }
+
+    /**
      * lr is short name for logAndAddLogRec
      * @param s
      */
diff --git a/src/java/com/android/internal/telephony/dataconnection/DcFailBringUp.java b/src/java/com/android/internal/telephony/dataconnection/DcFailBringUp.java
index 20e13ff..420340d 100644
--- a/src/java/com/android/internal/telephony/dataconnection/DcFailBringUp.java
+++ b/src/java/com/android/internal/telephony/dataconnection/DcFailBringUp.java
@@ -17,8 +17,10 @@
 package com.android.internal.telephony.dataconnection;
 
 import android.content.Intent;
+import android.telephony.Annotation.DataFailureCause;
 import android.telephony.DataFailCause;
-import android.telephony.Rlog;
+
+import com.android.telephony.Rlog;
 
 /**
  * A package visible class for supporting testing failing bringUp commands. This
@@ -41,7 +43,7 @@
     // failCause with its --ei option name and default value
     static final String FAIL_CAUSE = "fail_cause";
     static final int DEFAULT_FAIL_CAUSE = DataFailCause.ERROR_UNSPECIFIED;
-    @DataFailCause.FailCause
+    @DataFailureCause
     int mFailCause;
 
     // suggestedRetryTime with its --ei option name and default value
@@ -62,7 +64,7 @@
         }
     }
 
-    public void saveParameters(int counter, @DataFailCause.FailCause int failCause,
+    public void saveParameters(int counter, @DataFailureCause int failCause,
                                int suggestedRetryTime) {
         mCounter = counter;
         mFailCause = DataFailCause.getFailCause(failCause);
diff --git a/src/java/com/android/internal/telephony/dataconnection/DcNetworkAgent.java b/src/java/com/android/internal/telephony/dataconnection/DcNetworkAgent.java
index d9edd28..7fa1f97 100644
--- a/src/java/com/android/internal/telephony/dataconnection/DcNetworkAgent.java
+++ b/src/java/com/android/internal/telephony/dataconnection/DcNetworkAgent.java
@@ -17,28 +17,38 @@
 package com.android.internal.telephony.dataconnection;
 
 import android.annotation.NonNull;
+import android.net.KeepalivePacketData;
 import android.net.LinkProperties;
 import android.net.NattKeepalivePacketData;
 import android.net.NetworkAgent;
+import android.net.NetworkAgentConfig;
 import android.net.NetworkCapabilities;
 import android.net.NetworkInfo;
-import android.net.NetworkMisc;
+import android.net.NetworkProvider;
 import android.net.SocketKeepalive;
+import android.net.Uri;
 import android.os.Message;
 import android.telephony.AccessNetworkConstants;
 import android.telephony.AccessNetworkConstants.TransportType;
-import android.telephony.Rlog;
+import android.telephony.AnomalyReporter;
+import android.telephony.TelephonyManager;
+import android.text.TextUtils;
 import android.util.LocalLog;
 import android.util.SparseArray;
 
 import com.android.internal.telephony.DctConstants;
 import com.android.internal.telephony.Phone;
 import com.android.internal.telephony.RILConstants;
+import com.android.internal.telephony.metrics.TelephonyMetrics;
 import com.android.internal.util.IndentingPrintWriter;
+import com.android.telephony.Rlog;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
-import java.util.concurrent.atomic.AtomicInteger;
+import java.time.Duration;
+import java.util.HashSet;
+import java.util.Set;
+import java.util.UUID;
 
 /**
  * This class represents a network agent which is communication channel between
@@ -51,7 +61,7 @@
  * different {@link DataConnection}. Thus each method in this class needs to be synchronized.
  */
 public class DcNetworkAgent extends NetworkAgent {
-    private String mTag;
+    private final String mTag;
 
     private Phone mPhone;
 
@@ -65,42 +75,52 @@
 
     private final LocalLog mNetCapsLocalLog = new LocalLog(50);
 
-    private static AtomicInteger sSerialNumber = new AtomicInteger(0);
+    private NetworkInfo mNetworkInfo;
 
-    private DcNetworkAgent(DataConnection dc, String tag, Phone phone, NetworkInfo ni,
-                           int score, NetworkMisc misc, int factorySerialNumber,
-                           int transportType) {
-        super(dc.getHandler().getLooper(), phone.getContext(), tag, ni,
-                dc.getNetworkCapabilities(), dc.getLinkProperties(), score, misc,
-                factorySerialNumber);
-        mTag = tag;
+    // For debugging duplicate interface issue. Remove before R released.
+    private static Set<String> sInterfaceNames = new HashSet<>();
+
+    DcNetworkAgent(DataConnection dc, Phone phone, NetworkInfo ni, int score,
+            NetworkAgentConfig config, NetworkProvider networkProvider, int transportType) {
+        super(phone.getContext(), dc.getHandler().getLooper(), "DcNetworkAgent",
+                dc.getNetworkCapabilities(), dc.getLinkProperties(), score, config,
+                networkProvider);
+        register();
+        mTag = "DcNetworkAgent" + "-" + getNetwork().netId;
         mPhone = phone;
         mNetworkCapabilities = dc.getNetworkCapabilities();
         mTransportType = transportType;
         mDataConnection = dc;
-        logd(tag + " created for data connection " + dc.getName());
+        mNetworkInfo = new NetworkInfo(ni);
+        setLegacySubtype(ni.getSubtype(), ni.getSubtypeName());
+        setLegacyExtraInfo(ni.getExtraInfo());
+        // TODO: Remove before R is released.
+        if (dc.getLinkProperties() != null
+                && !TextUtils.isEmpty(dc.getLinkProperties().getInterfaceName())) {
+            checkDuplicateInterface(dc.getLinkProperties().getInterfaceName());
+        }
+        logd(mTag + " created for data connection " + dc.getName());
+    }
+
+    // This is a temp code to catch the duplicate network interface issue.
+    // TODO: Remove before R is released.
+    private void checkDuplicateInterface(String interfaceName) {
+        if (sInterfaceNames.contains(interfaceName)) {
+            String message = "Duplicate interface " + interfaceName + " is detected.";
+            log(message);
+            // Using fixed UUID to avoid duplicate bugreport notification
+            AnomalyReporter.reportAnomaly(
+                    UUID.fromString("02f3d3f6-4613-4415-b6cb-8d92c8a938a6"),
+                    message);
+        }
+        sInterfaceNames.add(interfaceName);
     }
 
     /**
-     * Constructor
-     *
-     * @param dc The data connection owns this network agent.
-     * @param phone The phone object.
-     * @param ni Network info.
-     * @param score Score of the data connection.
-     * @param misc The miscellaneous information of the data connection.
-     * @param factorySerialNumber Serial number of telephony network factory.
-     * @param transportType The transport of the data connection.
-     * @return The network agent
+     * @return The tag
      */
-    public static DcNetworkAgent createDcNetworkAgent(DataConnection dc, Phone phone,
-                                                      NetworkInfo ni, int score, NetworkMisc misc,
-                                                      int factorySerialNumber, int transportType) {
-        // Use serial number only. Do not use transport type because it can be transferred to
-        // a different transport.
-        String tag = "DcNetworkAgent-" + sSerialNumber.incrementAndGet();
-        return new DcNetworkAgent(dc, tag, phone, ni, score, misc, factorySerialNumber,
-                transportType);
+    String getTag() {
+        return mTag;
     }
 
     /**
@@ -140,22 +160,22 @@
     }
 
     @Override
-    protected synchronized void unwanted() {
+    public synchronized void onNetworkUnwanted() {
         if (mDataConnection == null) {
-            loge("Unwanted found called on no-owner DcNetworkAgent!");
+            loge("onNetworkUnwanted found called on no-owner DcNetworkAgent!");
             return;
         }
 
-        logd("unwanted called. Now tear down the data connection "
+        logd("onNetworkUnwanted called. Now tear down the data connection "
                 + mDataConnection.getName());
         mDataConnection.tearDownAll(Phone.REASON_RELEASED_BY_CONNECTIVITY_SERVICE,
                 DcTracker.RELEASE_TYPE_DETACH, null);
     }
 
     @Override
-    protected synchronized void pollLceData() {
+    public synchronized void onBandwidthUpdateRequested() {
         if (mDataConnection == null) {
-            loge("pollLceData called on no-owner DcNetworkAgent!");
+            loge("onBandwidthUpdateRequested called on no-owner DcNetworkAgent!");
             return;
         }
 
@@ -167,21 +187,33 @@
     }
 
     @Override
-    protected synchronized void networkStatus(int status, String redirectUrl) {
+    public synchronized void onValidationStatus(int status, Uri redirectUri) {
         if (mDataConnection == null) {
-            loge("networkStatus called on no-owner DcNetworkAgent!");
+            loge("onValidationStatus called on no-owner DcNetworkAgent!");
             return;
         }
 
-        logd("validation status: " + status + " with redirection URL: " + redirectUrl);
+        logd("validation status: " + status + " with redirection URL: " + redirectUri);
         DcTracker dct = mPhone.getDcTracker(mTransportType);
         if (dct != null) {
             Message msg = dct.obtainMessage(DctConstants.EVENT_NETWORK_STATUS_CHANGED,
-                    status, 0, redirectUrl);
+                    status, mDataConnection.getCid(), redirectUri.toString());
             msg.sendToTarget();
         }
     }
 
+    private synchronized boolean isOwned(DataConnection dc, String reason) {
+        if (mDataConnection == null) {
+            loge(reason + " called on no-owner DcNetworkAgent!");
+            return false;
+        } else if (mDataConnection != dc) {
+            loge(reason + ": This agent belongs to "
+                    + mDataConnection.getName() + ", ignored the request from " + dc.getName());
+            return false;
+        }
+        return true;
+    }
+
     /**
      * Set the network capabilities.
      *
@@ -190,14 +222,7 @@
      */
     public synchronized void sendNetworkCapabilities(NetworkCapabilities networkCapabilities,
                                                      DataConnection dc) {
-        if (mDataConnection == null) {
-            loge("sendNetworkCapabilities called on no-owner DcNetworkAgent!");
-            return;
-        } else if (mDataConnection != dc) {
-            loge("sendNetworkCapabilities: This agent belongs to "
-                    + mDataConnection.getName() + ", ignored the request from " + dc.getName());
-            return;
-        }
+        if (!isOwned(dc, "sendNetworkCapabilities")) return;
 
         if (!networkCapabilities.equals(mNetworkCapabilities)) {
             String logStr = "Changed from " + mNetworkCapabilities + " to "
@@ -206,6 +231,17 @@
                     + ", dc=" + mDataConnection.getName();
             logd(logStr);
             mNetCapsLocalLog.log(logStr);
+            if (networkCapabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)) {
+                // only log metrics for DataConnection with NET_CAPABILITY_INTERNET
+                if (mNetworkCapabilities == null
+                        || networkCapabilities.hasCapability(
+                                NetworkCapabilities.NET_CAPABILITY_TEMPORARILY_NOT_METERED)
+                        != mNetworkCapabilities.hasCapability(
+                                NetworkCapabilities.NET_CAPABILITY_TEMPORARILY_NOT_METERED)) {
+                    TelephonyMetrics.getInstance().writeNetworkCapabilitiesChangedEvent(
+                            mPhone.getPhoneId(), networkCapabilities);
+                }
+            }
             mNetworkCapabilities = networkCapabilities;
         }
         sendNetworkCapabilities(networkCapabilities);
@@ -219,14 +255,7 @@
      */
     public synchronized void sendLinkProperties(LinkProperties linkProperties,
                                                 DataConnection dc) {
-        if (mDataConnection == null) {
-            loge("sendLinkProperties called on no-owner DcNetworkAgent!");
-            return;
-        } else if (mDataConnection != dc) {
-            loge("sendLinkProperties: This agent belongs to "
-                    + mDataConnection.getName() + ", ignored the request from " + dc.getName());
-            return;
-        }
+        if (!isOwned(dc, "sendLinkProperties")) return;
         sendLinkProperties(linkProperties);
     }
 
@@ -237,59 +266,84 @@
      * @param dc The data connection that invokes this method.
      */
     public synchronized void sendNetworkScore(int score, DataConnection dc) {
-        if (mDataConnection == null) {
-            loge("sendNetworkScore called on no-owner DcNetworkAgent!");
-            return;
-        } else if (mDataConnection != dc) {
-            loge("sendNetworkScore: This agent belongs to "
-                    + mDataConnection.getName() + ", ignored the request from " + dc.getName());
-            return;
-        }
+        if (!isOwned(dc, "sendNetworkScore")) return;
         sendNetworkScore(score);
     }
 
     /**
+     * Unregister the network agent from connectivity service.
+     *
+     * @param dc The data connection that invokes this method.
+     */
+    public synchronized void unregister(DataConnection dc) {
+        if (!isOwned(dc, "unregister")) return;
+
+        if (dc.getLinkProperties() != null
+                && !TextUtils.isEmpty(dc.getLinkProperties().getInterfaceName())) {
+            sInterfaceNames.remove(dc.getLinkProperties().getInterfaceName());
+        }
+        logd("Unregister from connectivity service");
+        super.unregister();
+    }
+
+    /**
      * Set the network info.
      *
      * @param networkInfo The network info.
      * @param dc The data connection that invokes this method.
      */
     public synchronized void sendNetworkInfo(NetworkInfo networkInfo, DataConnection dc) {
-        if (mDataConnection == null) {
-            loge("sendNetworkInfo called on no-owner DcNetworkAgent!");
-            return;
-        } else if (mDataConnection != dc) {
-            loge("sendNetworkInfo: This agent belongs to "
-                    + mDataConnection.getName() + ", ignored the request from " + dc.getName());
-            return;
+        if (!isOwned(dc, "sendNetworkInfo")) return;
+        final NetworkInfo.State oldState = mNetworkInfo.getState();
+        final NetworkInfo.State state = networkInfo.getState();
+        if (mNetworkInfo.getExtraInfo() != networkInfo.getExtraInfo()) {
+            setLegacyExtraInfo(networkInfo.getExtraInfo());
         }
-        sendNetworkInfo(networkInfo);
+        int subType = networkInfo.getSubtype();
+        if (mNetworkInfo.getSubtype() != subType) {
+            setLegacySubtype(subType, TelephonyManager.getNetworkTypeName(subType));
+        }
+        if ((oldState == NetworkInfo.State.SUSPENDED || oldState == NetworkInfo.State.CONNECTED)
+                && state == NetworkInfo.State.DISCONNECTED) {
+            unregister(dc);
+        }
+        mNetworkInfo = new NetworkInfo(networkInfo);
+    }
+
+    /**
+     * Get the latest sent network info.
+     *
+     * @return network info
+     */
+    public synchronized NetworkInfo getNetworkInfo() {
+        return mNetworkInfo;
     }
 
     @Override
-    protected synchronized void startSocketKeepalive(Message msg) {
+    public synchronized void onStartSocketKeepalive(int slot, @NonNull Duration interval,
+            @NonNull KeepalivePacketData packet) {
         if (mDataConnection == null) {
-            loge("startSocketKeepalive called on no-owner DcNetworkAgent!");
+            loge("onStartSocketKeepalive called on no-owner DcNetworkAgent!");
             return;
         }
 
-        if (msg.obj instanceof NattKeepalivePacketData) {
+        if (packet instanceof NattKeepalivePacketData) {
             mDataConnection.obtainMessage(DataConnection.EVENT_KEEPALIVE_START_REQUEST,
-                    msg.arg1, msg.arg2, msg.obj).sendToTarget();
+                    slot, (int) interval.getSeconds(), packet).sendToTarget();
         } else {
-            onSocketKeepaliveEvent(msg.arg1, SocketKeepalive.ERROR_UNSUPPORTED);
+            sendSocketKeepaliveEvent(slot, SocketKeepalive.ERROR_UNSUPPORTED);
         }
     }
 
     @Override
-    protected synchronized void stopSocketKeepalive(Message msg) {
+    public synchronized void onStopSocketKeepalive(int slot) {
         if (mDataConnection == null) {
-            loge("stopSocketKeepalive called on no-owner DcNetworkAgent!");
+            loge("onStopSocketKeepalive called on no-owner DcNetworkAgent!");
             return;
         }
 
-        mDataConnection.obtainMessage(DataConnection.EVENT_KEEPALIVE_STOP_REQUEST,
-                msg.arg1, msg.arg2, msg.obj).sendToTarget();
+        mDataConnection.obtainMessage(DataConnection.EVENT_KEEPALIVE_STOP_REQUEST, slot)
+                .sendToTarget();
     }
 
     @Override
@@ -374,11 +428,11 @@
         void handleKeepaliveStarted(final int slot, KeepaliveStatus ks) {
             switch (ks.statusCode) {
                 case KeepaliveStatus.STATUS_INACTIVE:
-                    DcNetworkAgent.this.onSocketKeepaliveEvent(slot,
+                    DcNetworkAgent.this.sendSocketKeepaliveEvent(slot,
                             keepaliveStatusErrorToPacketKeepaliveError(ks.errorCode));
                     break;
                 case KeepaliveStatus.STATUS_ACTIVE:
-                    DcNetworkAgent.this.onSocketKeepaliveEvent(
+                    DcNetworkAgent.this.sendSocketKeepaliveEvent(
                             slot, SocketKeepalive.SUCCESS);
                     // fall through to add record
                 case KeepaliveStatus.STATUS_PENDING:
@@ -409,13 +463,13 @@
             switch (kr.currentStatus) {
                 case KeepaliveStatus.STATUS_INACTIVE:
                     logd("Inactive Keepalive received status!");
-                    DcNetworkAgent.this.onSocketKeepaliveEvent(
+                    DcNetworkAgent.this.sendSocketKeepaliveEvent(
                             kr.slotId, SocketKeepalive.ERROR_HARDWARE_ERROR);
                     break;
                 case KeepaliveStatus.STATUS_PENDING:
                     switch (ks.statusCode) {
                         case KeepaliveStatus.STATUS_INACTIVE:
-                            DcNetworkAgent.this.onSocketKeepaliveEvent(kr.slotId,
+                            DcNetworkAgent.this.sendSocketKeepaliveEvent(kr.slotId,
                                     keepaliveStatusErrorToPacketKeepaliveError(ks.errorCode));
                             kr.currentStatus = KeepaliveStatus.STATUS_INACTIVE;
                             mKeepalives.remove(ks.sessionHandle);
@@ -423,7 +477,7 @@
                         case KeepaliveStatus.STATUS_ACTIVE:
                             logd("Pending Keepalive received active status!");
                             kr.currentStatus = KeepaliveStatus.STATUS_ACTIVE;
-                            DcNetworkAgent.this.onSocketKeepaliveEvent(
+                            DcNetworkAgent.this.sendSocketKeepaliveEvent(
                                     kr.slotId, SocketKeepalive.SUCCESS);
                             break;
                         case KeepaliveStatus.STATUS_PENDING:
@@ -437,7 +491,7 @@
                     switch (ks.statusCode) {
                         case KeepaliveStatus.STATUS_INACTIVE:
                             logd("Keepalive received stopped status!");
-                            DcNetworkAgent.this.onSocketKeepaliveEvent(
+                            DcNetworkAgent.this.sendSocketKeepaliveEvent(
                                     kr.slotId, SocketKeepalive.SUCCESS);
 
                             kr.currentStatus = KeepaliveStatus.STATUS_INACTIVE;
diff --git a/src/java/com/android/internal/telephony/dataconnection/DcRequest.java b/src/java/com/android/internal/telephony/dataconnection/DcRequest.java
index bf2a6af..da775e8 100644
--- a/src/java/com/android/internal/telephony/dataconnection/DcRequest.java
+++ b/src/java/com/android/internal/telephony/dataconnection/DcRequest.java
@@ -15,25 +15,70 @@
  */
 package com.android.internal.telephony.dataconnection;
 
-import android.content.Context;
-import android.net.NetworkConfig;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.net.NetworkRequest;
-import android.telephony.data.ApnSetting.ApnType;
+import android.net.NetworkSpecifier;
+import android.net.TelephonyNetworkSpecifier;
+import android.telephony.Annotation.ApnType;
 
-import java.util.HashMap;
+import com.android.telephony.Rlog;
 
+/**
+ * Wraps cellular network requests to configured apn types.
+ */
 public class DcRequest implements Comparable<DcRequest> {
     private static final String LOG_TAG = "DcRequest";
 
+    @NonNull
     public final NetworkRequest networkRequest;
     public final int priority;
     public final @ApnType int apnType;
 
-    public DcRequest(NetworkRequest nr, Context context) {
-        initApnPriorities(context);
+    private DcRequest(@NonNull final NetworkRequest nr, @ApnType final int type,
+            int apnPriority) {
         networkRequest = nr;
-        apnType = ApnContext.getApnTypeFromNetworkRequest(networkRequest);
-        priority = priorityForApnType(apnType);
+        priority = apnPriority;
+        apnType = type;
+    }
+
+    /**
+     * Create a DcRequest based off of the network request.  If the network request is not cellular,
+     * then null is returned and a warning is generated.
+     * @param networkRequest sets the type of dc request
+     * @param apnConfigTypeRepository apn config types to match on the network request
+     * @return corresponding DcRequest
+     *
+     */
+    @Nullable
+    public static DcRequest create(@NonNull final NetworkRequest networkRequest,
+            @NonNull final ApnConfigTypeRepository apnConfigTypeRepository) {
+        final int apnType = ApnContext.getApnTypeFromNetworkRequest(networkRequest);
+        final ApnConfigType apnConfigType = apnConfigTypeRepository.getByType(apnType);
+        if (apnConfigType == null) {
+            Rlog.d(LOG_TAG, "Non cellular request ignored: " + networkRequest.toString());
+            checkForAnomalousNetworkRequest(networkRequest);
+            return null;
+        } else {
+            Rlog.d(LOG_TAG, "Cellular request confirmed: " + networkRequest.toString());
+            return new DcRequest(networkRequest, apnType, apnConfigType.getPriority());
+        }
+    }
+
+    private static void checkForAnomalousNetworkRequest(NetworkRequest networkRequest) {
+        NetworkSpecifier specifier = networkRequest.getNetworkSpecifier();
+        if (specifier != null) {
+            if (specifier instanceof TelephonyNetworkSpecifier) {
+                reportAnomalousNetworkRequest(networkRequest);
+            }
+        }
+    }
+
+    private static void reportAnomalousNetworkRequest(NetworkRequest networkRequest) {
+        //TODO: Report anomaly if this happens
+        Rlog.w(LOG_TAG, "A TelephonyNetworkSpecifier for a non-cellular request is invalid: "
+                + networkRequest.toString());
+
     }
 
     public String toString() {
@@ -54,26 +99,4 @@
     public int compareTo(DcRequest o) {
         return o.priority - priority;
     }
-
-    private static final HashMap<Integer, Integer> sApnPriorityMap =
-            new HashMap<Integer, Integer>();
-
-    private void initApnPriorities(Context context) {
-        synchronized (sApnPriorityMap) {
-            if (sApnPriorityMap.isEmpty()) {
-                String[] networkConfigStrings = context.getResources().getStringArray(
-                        com.android.internal.R.array.networkAttributes);
-                for (String networkConfigString : networkConfigStrings) {
-                    NetworkConfig networkConfig = new NetworkConfig(networkConfigString);
-                    final int apnType = ApnContext.getApnTypeFromNetworkType(networkConfig.type);
-                    sApnPriorityMap.put(apnType, networkConfig.priority);
-                }
-            }
-        }
-    }
-
-    private int priorityForApnType(int apnType) {
-        Integer priority = sApnPriorityMap.get(apnType);
-        return (priority != null ? priority.intValue() : 0);
-    }
 }
diff --git a/src/java/com/android/internal/telephony/dataconnection/DcTesterDeactivateAll.java b/src/java/com/android/internal/telephony/dataconnection/DcTesterDeactivateAll.java
index cda4836..f4b26b6 100644
--- a/src/java/com/android/internal/telephony/dataconnection/DcTesterDeactivateAll.java
+++ b/src/java/com/android/internal/telephony/dataconnection/DcTesterDeactivateAll.java
@@ -20,11 +20,11 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
-import android.os.Build;
 import android.os.Handler;
-import android.telephony.Rlog;
 
 import com.android.internal.telephony.Phone;
+import com.android.internal.telephony.util.TelephonyUtils;
+import com.android.telephony.Rlog;
 
 /**
  * To bring down all DC's send the following intent:
@@ -69,7 +69,7 @@
         mPhone = phone;
         mDcc = dcc;
 
-        if (Build.IS_DEBUGGABLE) {
+        if (TelephonyUtils.IS_DEBUGGABLE) {
             IntentFilter filter = new IntentFilter();
 
             filter.addAction(sActionDcTesterDeactivateAll);
@@ -83,7 +83,7 @@
     }
 
     void dispose() {
-        if (Build.IS_DEBUGGABLE) {
+        if (TelephonyUtils.IS_DEBUGGABLE) {
             mPhone.getContext().unregisterReceiver(sIntentReceiver);
         }
     }
diff --git a/src/java/com/android/internal/telephony/dataconnection/DcTesterFailBringUpAll.java b/src/java/com/android/internal/telephony/dataconnection/DcTesterFailBringUpAll.java
index 4013454..ba07e12 100644
--- a/src/java/com/android/internal/telephony/dataconnection/DcTesterFailBringUpAll.java
+++ b/src/java/com/android/internal/telephony/dataconnection/DcTesterFailBringUpAll.java
@@ -20,12 +20,12 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
-import android.os.Build;
 import android.os.Handler;
 import android.telephony.DataFailCause;
-import android.telephony.Rlog;
 
 import com.android.internal.telephony.Phone;
+import com.android.internal.telephony.util.TelephonyUtils;
+import com.android.telephony.Rlog;
 
 /**
  * A package level call that causes all DataConnection bringUp calls to fail a specific
@@ -77,7 +77,7 @@
 
     DcTesterFailBringUpAll(Phone phone, Handler handler) {
         mPhone = phone;
-        if (Build.IS_DEBUGGABLE) {
+        if (TelephonyUtils.IS_DEBUGGABLE) {
             IntentFilter filter = new IntentFilter();
 
             filter.addAction(mActionFailBringUp);
@@ -94,7 +94,7 @@
     }
 
     void dispose() {
-        if (Build.IS_DEBUGGABLE) {
+        if (TelephonyUtils.IS_DEBUGGABLE) {
             mPhone.getContext().unregisterReceiver(mIntentReceiver);
         }
     }
diff --git a/src/java/com/android/internal/telephony/dataconnection/DcTracker.java b/src/java/com/android/internal/telephony/dataconnection/DcTracker.java
index de4ba2c..7b1560f 100644
--- a/src/java/com/android/internal/telephony/dataconnection/DcTracker.java
+++ b/src/java/com/android/internal/telephony/dataconnection/DcTracker.java
@@ -19,6 +19,9 @@
 import static android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE;
 import static android.telephony.TelephonyManager.NETWORK_TYPE_LTE;
 import static android.telephony.TelephonyManager.NETWORK_TYPE_NR;
+import static android.telephony.data.ApnSetting.PROTOCOL_IPV4V6;
+import static android.telephony.data.ApnSetting.TYPE_DEFAULT;
+import static android.telephony.data.ApnSetting.TYPE_IA;
 
 import static com.android.internal.telephony.RILConstants.DATA_PROFILE_DEFAULT;
 import static com.android.internal.telephony.RILConstants.DATA_PROFILE_INVALID;
@@ -41,25 +44,21 @@
 import android.database.ContentObserver;
 import android.database.Cursor;
 import android.net.ConnectivityManager;
-import android.net.INetworkPolicyListener;
 import android.net.LinkProperties;
 import android.net.NetworkAgent;
 import android.net.NetworkCapabilities;
-import android.net.NetworkConfig;
 import android.net.NetworkPolicyManager;
 import android.net.NetworkRequest;
 import android.net.ProxyInfo;
 import android.net.TrafficStats;
 import android.net.Uri;
 import android.os.AsyncResult;
-import android.os.Build;
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.HandlerThread;
 import android.os.Message;
 import android.os.PersistableBundle;
 import android.os.RegistrantList;
-import android.os.ServiceManager;
 import android.os.SystemClock;
 import android.os.SystemProperties;
 import android.preference.PreferenceManager;
@@ -68,23 +67,25 @@
 import android.provider.Telephony;
 import android.telephony.AccessNetworkConstants;
 import android.telephony.AccessNetworkConstants.TransportType;
+import android.telephony.Annotation.ApnType;
+import android.telephony.Annotation.DataFailureCause;
+import android.telephony.Annotation.NetworkType;
 import android.telephony.CarrierConfigManager;
 import android.telephony.CellLocation;
 import android.telephony.DataFailCause;
-import android.telephony.DataFailCause.FailCause;
 import android.telephony.NetworkRegistrationInfo;
 import android.telephony.PcoData;
-import android.telephony.Rlog;
+import android.telephony.PreciseDataConnectionState;
 import android.telephony.ServiceState;
 import android.telephony.ServiceState.RilRadioTechnology;
 import android.telephony.SubscriptionManager;
-import android.telephony.SubscriptionManager.OnSubscriptionsChangedListener;
 import android.telephony.SubscriptionPlan;
+import android.telephony.TelephonyDisplayInfo;
+import android.telephony.TelephonyFrameworkInitializer;
 import android.telephony.TelephonyManager;
-import android.telephony.TelephonyManager.NetworkType;
+import android.telephony.TelephonyManager.SimState;
 import android.telephony.cdma.CdmaCellLocation;
 import android.telephony.data.ApnSetting;
-import android.telephony.data.ApnSetting.ApnType;
 import android.telephony.data.DataProfile;
 import android.telephony.gsm.GsmCellLocation;
 import android.text.TextUtils;
@@ -105,15 +106,15 @@
 import com.android.internal.telephony.PhoneSwitcher;
 import com.android.internal.telephony.RILConstants;
 import com.android.internal.telephony.SettingsObserver;
-import com.android.internal.telephony.TelephonyIntents;
+import com.android.internal.telephony.SubscriptionInfoUpdater;
 import com.android.internal.telephony.dataconnection.DataConnectionReasons.DataAllowedReasonType;
 import com.android.internal.telephony.dataconnection.DataConnectionReasons.DataDisallowedReasonType;
 import com.android.internal.telephony.dataconnection.DataEnabledSettings.DataEnabledChangedReason;
 import com.android.internal.telephony.metrics.TelephonyMetrics;
-import com.android.internal.telephony.uicc.IccRecords;
-import com.android.internal.telephony.uicc.UiccController;
-import com.android.internal.util.ArrayUtils;
+import com.android.internal.telephony.util.ArrayUtils;
+import com.android.internal.telephony.util.TelephonyUtils;
 import com.android.internal.util.AsyncChannel;
+import com.android.telephony.Rlog;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
@@ -121,45 +122,27 @@
 import java.lang.annotation.RetentionPolicy;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Collection;
 import java.util.Comparator;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.Map.Entry;
-import java.util.PriorityQueue;
 import java.util.Set;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.concurrent.atomic.AtomicInteger;
-import java.util.concurrent.atomic.AtomicReference;
+import java.util.stream.Collectors;
+
 /**
  * {@hide}
  */
 public class DcTracker extends Handler {
-    private static final boolean DBG = true;
+    protected static final boolean DBG = true;
     private static final boolean VDBG = false; // STOPSHIP if true
     private static final boolean VDBG_STALL = false; // STOPSHIP if true
     private static final boolean RADIO_TESTS = false;
 
-    /**
-     * These constants exist here because ConnectivityManager.TYPE_xxx constants are deprecated and
-     * new ones will not be added (for instance NETWORK_TYPE_MCX below).
-     * For backward compatibility, the values here need to be the same as
-     * ConnectivityManager.TYPE_xxx because networkAttributes overlay uses those values.
-     */
-    private static final int NETWORK_TYPE_DEFAULT = ConnectivityManager.TYPE_MOBILE;
-    private static final int NETWORK_TYPE_MMS = ConnectivityManager.TYPE_MOBILE_MMS;
-    private static final int NETWORK_TYPE_SUPL = ConnectivityManager.TYPE_MOBILE_SUPL;
-    private static final int NETWORK_TYPE_DUN = ConnectivityManager.TYPE_MOBILE_DUN;
-    private static final int NETWORK_TYPE_HIPRI = ConnectivityManager.TYPE_MOBILE_HIPRI;
-    private static final int NETWORK_TYPE_FOTA = ConnectivityManager.TYPE_MOBILE_FOTA;
-    private static final int NETWORK_TYPE_IMS = ConnectivityManager.TYPE_MOBILE_IMS;
-    private static final int NETWORK_TYPE_CBS = ConnectivityManager.TYPE_MOBILE_CBS;
-    private static final int NETWORK_TYPE_IA = ConnectivityManager.TYPE_MOBILE_IA;
-    private static final int NETWORK_TYPE_EMERGENCY = ConnectivityManager.TYPE_MOBILE_EMERGENCY;
-    private static final int NETWORK_TYPE_MCX = 1001;  // far away from ConnectivityManager.TYPE_xxx
-                                                       // constants as MCX isn't defined there.
-
     @IntDef(value = {
             REQUEST_TYPE_NORMAL,
             REQUEST_TYPE_HANDOVER,
@@ -226,6 +209,7 @@
             "extra_handover_failure_fallback";
 
     private final String mLogTag;
+    private final String mLogTagSuffix;
 
     public AtomicBoolean isCleanupRequired = new AtomicBoolean(false);
 
@@ -256,15 +240,7 @@
     private static final int DATA_STALL_ALARM_AGGRESSIVE_DELAY_IN_MS_DEFAULT = 1000 * 60;
 
     private static final boolean DATA_STALL_SUSPECTED = true;
-    private static final boolean DATA_STALL_NOT_SUSPECTED = false;
-
-    private static final String INTENT_RECONNECT_ALARM =
-            "com.android.internal.telephony.data-reconnect";
-    private static final String INTENT_RECONNECT_ALARM_EXTRA_TYPE = "reconnect_alarm_extra_type";
-    private static final String INTENT_RECONNECT_ALARM_EXTRA_REASON =
-            "reconnect_alarm_extra_reason";
-    private static final String INTENT_RECONNECT_ALARM_EXTRA_TRANSPORT_TYPE =
-            "reconnect_alarm_extra_transport_type";
+    protected static final boolean DATA_STALL_NOT_SUSPECTED = false;
 
     private static final String INTENT_DATA_STALL_ALARM =
             "com.android.internal.telephony.data-stall";
@@ -273,18 +249,20 @@
     private static final String INTENT_DATA_STALL_ALARM_EXTRA_TRANSPORT_TYPE =
             "data_stall_alarm_extra_transport_type";
 
+    /** The higher index has higher priority. */
+    private static final DctConstants.State[] DATA_CONNECTION_STATE_PRIORITIES = {
+            DctConstants.State.IDLE,
+            DctConstants.State.DISCONNECTING,
+            DctConstants.State.CONNECTING,
+            DctConstants.State.CONNECTED,
+    };
+
     private DcTesterFailBringUpAll mDcTesterFailBringUpAll;
     private DcController mDcc;
 
     /** kept in sync with mApnContexts
      * Higher numbers are higher priority and sorted so highest priority is first */
-    private final PriorityQueue<ApnContext>mPrioritySortedApnContexts =
-            new PriorityQueue<ApnContext>(5,
-            new Comparator<ApnContext>() {
-                public int compare(ApnContext c1, ApnContext c2) {
-                    return c2.priority - c1.priority;
-                }
-            } );
+    private ArrayList<ApnContext> mPrioritySortedApnContexts = new ArrayList<>();
 
     /** all APN settings applicable to the current carrier */
     private ArrayList<ApnSetting> mAllApnSettings = new ArrayList<>();
@@ -336,24 +314,24 @@
     private final LocalLog mDataRoamingLeakageLog = new LocalLog(50);
     private final LocalLog mApnSettingsInitializationLog = new LocalLog(50);
 
-    /* Default for 5G connection reevaluation alarm durations */
-    private int mHysteresisTimeSec = 0;
+    /* 5G connection reevaluation watchdog alarm constants */
     private long mWatchdogTimeMs = 1000 * 60 * 60;
+    private boolean mWatchdog = false;
 
     /* Default for whether 5G frequencies are considered unmetered */
-    private boolean mAllUnmetered = false;
-    private boolean mMmwaveUnmetered = false;
-    private boolean mSub6Unmetered = false;
-
-    /* Used to check whether 5G timers are currently active and waiting to go off */
-    private boolean mHysteresis = false;
-    private boolean mWatchdog = false;
+    private boolean mNrNsaAllUnmetered = false;
+    private boolean mNrNsaMmwaveUnmetered = false;
+    private boolean mNrNsaSub6Unmetered = false;
+    private boolean mNrSaAllUnmetered = false;
+    private boolean mNrSaMmwaveUnmetered = false;
+    private boolean mNrSaSub6Unmetered = false;
+    private boolean mRoamingUnmetered = false;
 
     /* List of SubscriptionPlans, updated on SubscriptionManager.setSubscriptionPlans */
     private List<SubscriptionPlan> mSubscriptionPlans = null;
 
-    /* Used to check whether phone was recently connected to 5G. */
-    private boolean m5GWasConnected = false;
+    @SimState
+    private int mSimState = TelephonyManager.SIM_STATE_UNKNOWN;
 
     private final BroadcastReceiver mIntentReceiver = new BroadcastReceiver () {
         @Override
@@ -367,57 +345,40 @@
                 stopNetStatPoll();
                 startNetStatPoll();
                 restartDataStallAlarm();
-                reevaluateUnmeteredConnections();
             } else if (action.equals(Intent.ACTION_SCREEN_OFF)) {
                 if (DBG) log("screen off");
                 mIsScreenOn = false;
                 stopNetStatPoll();
                 startNetStatPoll();
                 restartDataStallAlarm();
-                stopHysteresisAlarm();
-                stopWatchdogAlarm();
-                setDataConnectionUnmetered(false);
-            } else if (action.startsWith(INTENT_RECONNECT_ALARM)) {
-                onActionIntentReconnectAlarm(intent);
             } else if (action.equals(INTENT_DATA_STALL_ALARM)) {
                 onActionIntentDataStallAlarm(intent);
             } else if (action.equals(INTENT_PROVISIONING_APN_ALARM)) {
                 if (DBG) log("Provisioning apn alarm");
                 onActionIntentProvisioningApnAlarm(intent);
-            } else if (action.equals(CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED)) {
-                if (DBG) log("received carrier config change");
-                if (mIccRecords.get() != null && mIccRecords.get().getRecordsLoaded()) {
-                    setDefaultDataRoamingEnabled();
+            } else if (action.equals(TelephonyManager.ACTION_SIM_CARD_STATE_CHANGED)
+                    || action.equals(TelephonyManager.ACTION_SIM_APPLICATION_STATE_CHANGED)) {
+                if (mPhone.getPhoneId() == intent.getIntExtra(SubscriptionManager.EXTRA_SLOT_INDEX,
+                        SubscriptionManager.INVALID_SIM_SLOT_INDEX)) {
+                    int simState = intent.getIntExtra(TelephonyManager.EXTRA_SIM_STATE,
+                            TelephonyManager.SIM_STATE_UNKNOWN);
+                    sendMessage(obtainMessage(DctConstants.EVENT_SIM_STATE_UPDATED, simState, 0));
                 }
-                String[] bandwidths = CarrierConfigManager.getDefaultConfig().getStringArray(
-                        CarrierConfigManager.KEY_BANDWIDTH_STRING_ARRAY);
-                boolean useLte = false;
-                CarrierConfigManager configManager = (CarrierConfigManager) mPhone.getContext()
-                        .getSystemService(Context.CARRIER_CONFIG_SERVICE);
-                if (configManager != null) {
-                    PersistableBundle b = configManager.getConfigForSubId(mPhone.getSubId());
-                    if (b != null) {
-                        if (b.getStringArray(CarrierConfigManager.KEY_BANDWIDTH_STRING_ARRAY)
-                                != null) {
-                            bandwidths = b.getStringArray(
-                                    CarrierConfigManager.KEY_BANDWIDTH_STRING_ARRAY);
-                        }
-                        useLte = b.getBoolean(CarrierConfigManager
-                                .KEY_BANDWIDTH_NR_NSA_USE_LTE_VALUE_FOR_UPSTREAM_BOOL);
-                        mHysteresisTimeSec = b.getInt(
-                                CarrierConfigManager.KEY_5G_ICON_DISPLAY_GRACE_PERIOD_SEC_INT);
-                        mWatchdogTimeMs = b.getLong(
-                                CarrierConfigManager.KEY_5G_WATCHDOG_TIME_MS_LONG);
-                        mAllUnmetered = b.getBoolean(
-                                CarrierConfigManager.KEY_UNMETERED_NR_NSA_BOOL);
-                        mMmwaveUnmetered = b.getBoolean(
-                                CarrierConfigManager.KEY_UNMETERED_NR_NSA_MMWAVE_BOOL);
-                        mSub6Unmetered = b.getBoolean(
-                                CarrierConfigManager.KEY_UNMETERED_NR_NSA_SUB6_BOOL);
+            } else if (action.equals(CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED)) {
+                if (mPhone.getPhoneId() == intent.getIntExtra(CarrierConfigManager.EXTRA_SLOT_INDEX,
+                        SubscriptionManager.INVALID_SIM_SLOT_INDEX)) {
+                    if (intent.getBooleanExtra(
+                            CarrierConfigManager.EXTRA_REBROADCAST_ON_UNLOCK, false)) {
+                        // Ignore the rebroadcast one to prevent multiple carrier config changed
+                        // event during boot up.
+                        return;
+                    }
+                    int subId = intent.getIntExtra(SubscriptionManager.EXTRA_SUBSCRIPTION_INDEX,
+                            SubscriptionManager.INVALID_SUBSCRIPTION_ID);
+                    if (SubscriptionManager.isValidSubscriptionId(subId)) {
+                        sendEmptyMessage(DctConstants.EVENT_CARRIER_CONFIG_CHANGED);
                     }
                 }
-                sendMessage(obtainMessage(DctConstants.EVENT_UPDATE_CARRIER_CONFIGS,
-                        useLte ? 1 : 0, 0 /* unused */, bandwidths));
             } else {
                 if (DBG) log("onReceive: Unknown action=" + action);
             }
@@ -444,36 +405,9 @@
         }
     };
 
-    private SubscriptionManager mSubscriptionManager;
-    private final DctOnSubscriptionsChangedListener
-            mOnSubscriptionsChangedListener = new DctOnSubscriptionsChangedListener();
-
-    private class DctOnSubscriptionsChangedListener extends OnSubscriptionsChangedListener {
-        public final AtomicInteger mPreviousSubId =
-                new AtomicInteger(SubscriptionManager.INVALID_SUBSCRIPTION_ID);
-
-        /**
-         * Callback invoked when there is any change to any SubscriptionInfo. Typically
-         * this method invokes {@link SubscriptionManager#getActiveSubscriptionInfoList}
-         */
-        @Override
-        public void onSubscriptionsChanged() {
-            if (DBG) log("SubscriptionListener.onSubscriptionInfoChanged");
-            // Set the network type, in case the radio does not restore it.
-            int subId = mPhone.getSubId();
-            if (SubscriptionManager.isValidSubscriptionId(subId)) {
-                registerSettingsObserver();
-            }
-            if (SubscriptionManager.isValidSubscriptionId(subId) &&
-                    mPreviousSubId.getAndSet(subId) != subId) {
-                onRecordsLoadedOrSubIdChanged();
-            }
-        }
-    };
-
     private NetworkPolicyManager mNetworkPolicyManager;
-    private final INetworkPolicyListener mNetworkPolicyListener =
-            new NetworkPolicyManager.Listener() {
+    private final NetworkPolicyManager.SubscriptionCallback mSubscriptionCallback =
+            new NetworkPolicyManager.SubscriptionCallback() {
         @Override
         public void onSubscriptionOverride(int subId, int overrideMask, int overrideValue) {
             if (mPhone == null || mPhone.getSubId() != subId) return;
@@ -544,14 +478,6 @@
         }
 
         /**
-         * Get Tcp Tx/Rx packet count from TrafficStats
-         */
-        public void updateTcpTxRxSum() {
-            this.txPkts = TrafficStats.getMobileTcpTxPackets();
-            this.rxPkts = TrafficStats.getMobileTcpRxPackets();
-        }
-
-        /**
          * Get total Tx/Rx packet count from TrafficStats
          */
         public void updateTotalTxRxSum() {
@@ -560,28 +486,13 @@
         }
     }
 
-    private void onActionIntentReconnectAlarm(Intent intent) {
-        Message msg = obtainMessage(DctConstants.EVENT_DATA_RECONNECT);
-        msg.setData(intent.getExtras());
-        sendMessage(msg);
-    }
-
-    private void onDataReconnect(Bundle bundle) {
-        String reason = bundle.getString(INTENT_RECONNECT_ALARM_EXTRA_REASON);
-        String apnType = bundle.getString(INTENT_RECONNECT_ALARM_EXTRA_TYPE);
-
+    private void onDataReconnect(ApnContext apnContextforRetry, int subId) {
         int phoneSubId = mPhone.getSubId();
-        int currSubId = bundle.getInt(PhoneConstants.SUBSCRIPTION_KEY,
-                SubscriptionManager.INVALID_SUBSCRIPTION_ID);
+        String apnType = apnContextforRetry.getApnType();
+        String reason =  apnContextforRetry.getReason();
 
-        // Stop reconnect if not current subId is not correct.
-        // FIXME STOPSHIP - phoneSubId is coming up as -1 way after boot and failing this?
-        if (!SubscriptionManager.isValidSubscriptionId(currSubId) || (currSubId != phoneSubId)) {
-            return;
-        }
-
-        int transportType = bundle.getInt(INTENT_RECONNECT_ALARM_EXTRA_TRANSPORT_TYPE, 0);
-        if (transportType != mTransportType) {
+        if (!SubscriptionManager.isValidSubscriptionId(subId) || (subId != phoneSubId)) {
+            log("onDataReconnect: invalid subId");
             return;
         }
 
@@ -609,8 +520,6 @@
             }
             // TODO: IF already associated should we send the EVENT_TRY_SETUP_DATA???
             sendMessage(obtainMessage(DctConstants.EVENT_TRY_SETUP_DATA, apnContext));
-
-            apnContext.setReconnectIntent(null);
         }
     }
 
@@ -637,9 +546,7 @@
     private RegistrantList mAllDataDisconnectedRegistrants = new RegistrantList();
 
     // member variables
-    private final Phone mPhone;
-    private final UiccController mUiccController;
-    private final AtomicReference<IccRecords> mIccRecords = new AtomicReference<IccRecords>();
+    protected final Phone mPhone;
     private DctConstants.Activity mActivity = DctConstants.Activity.NONE;
     private DctConstants.State mState = DctConstants.State.IDLE;
     private final Handler mDataConnectionTracker;
@@ -663,16 +570,16 @@
     // True if data stall detection is enabled
     private volatile boolean mDataStallNoRxEnabled = true;
 
-    private volatile boolean mFailFast = false;
+    protected volatile boolean mFailFast = false;
 
     // True when in voice call
-    private boolean mInVoiceCall = false;
+    protected boolean mInVoiceCall = false;
 
     /** Intent sent when the reconnect alarm fires. */
     private PendingIntent mReconnectIntent = null;
 
     // When false we will not auto attach and manually attaching is required.
-    private boolean mAutoAttachOnCreationConfig = false;
+    protected boolean mAutoAttachOnCreationConfig = false;
     private AtomicBoolean mAutoAttachEnabled = new AtomicBoolean(false);
 
     // State of screen
@@ -691,19 +598,21 @@
     private HashMap<String, Integer> mApnToDataConnectionId = new HashMap<String, Integer>();
 
     /** Phone.APN_TYPE_* ===> ApnContext */
-    private final ConcurrentHashMap<String, ApnContext> mApnContexts =
+    protected ConcurrentHashMap<String, ApnContext> mApnContexts =
             new ConcurrentHashMap<String, ApnContext>();
 
-    private final SparseArray<ApnContext> mApnContextsByType = new SparseArray<ApnContext>();
+    private SparseArray<ApnContext> mApnContextsByType = new SparseArray<ApnContext>();
 
     private int mDisconnectPendingCount = 0;
 
     private ArrayList<DataProfile> mLastDataProfileList = new ArrayList<>();
 
     /** RAT name ===> (downstream, upstream) bandwidth values from carrier config. */
-    private final ConcurrentHashMap<String, Pair<Integer, Integer>> mBandwidths =
+    private ConcurrentHashMap<String, Pair<Integer, Integer>> mBandwidths =
             new ConcurrentHashMap<>();
 
+    private boolean mConfigReady = false;
+
     /**
      * Handles changes to the APN db.
      */
@@ -725,11 +634,6 @@
 
     //***** Constants
 
-    // Used by puppetmaster/*/radio_stress.py
-    private static final String PUPPET_MASTER_RADIO_STRESS_TEST = "gsm.defaultpdpcontext.active";
-
-    private static final int POLL_PDP_MILLIS = 5 * 1000;
-
     private static final int PROVISIONING_SPINNER_TIMEOUT_MILLIS = 120 * 1000;
 
     static final Uri PREFERAPN_NO_UPDATE_URI_USING_SUBID =
@@ -752,6 +656,7 @@
     private final int mTransportType;
 
     private DataStallRecoveryHandler mDsRecoveryHandler;
+    private HandlerThread mHandlerThread;
 
     /**
      * Request network completion message map. Key is the APN type, value is the list of completion
@@ -769,19 +674,14 @@
                 .createForSubscriptionId(phone.getSubId());
         // The 'C' in tag indicates cellular, and 'I' indicates IWLAN. This is to distinguish
         // between two DcTrackers, one for each.
-        String tagSuffix = "-" + ((transportType == AccessNetworkConstants.TRANSPORT_TYPE_WWAN)
-                ? "C" : "I");
-        if (mTelephonyManager.getPhoneCount() > 1) {
-            tagSuffix += "-" + mPhone.getPhoneId();
-        }
-        mLogTag = "DCT" + tagSuffix;
+        mLogTagSuffix = "-" + ((transportType == AccessNetworkConstants.TRANSPORT_TYPE_WWAN)
+                ? "C" : "I") + "-" + mPhone.getPhoneId();
+        mLogTag = "DCT" + mLogTagSuffix;
 
         mTransportType = transportType;
-        mDataServiceManager = new DataServiceManager(phone, transportType, tagSuffix);
+        mDataServiceManager = new DataServiceManager(phone, transportType, mLogTagSuffix);
 
         mResolver = mPhone.getContext().getContentResolver();
-        mUiccController = UiccController.getInstance();
-        mUiccController.registerForIccChanged(this, DctConstants.EVENT_ICC_CHANGED, null);
         mAlarmManager =
                 (AlarmManager) mPhone.getContext().getSystemService(Context.ALARM_SERVICE);
 
@@ -793,6 +693,8 @@
         filter.addAction(INTENT_DATA_STALL_ALARM);
         filter.addAction(INTENT_PROVISIONING_APN_ALARM);
         filter.addAction(CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED);
+        filter.addAction(TelephonyManager.ACTION_SIM_CARD_STATE_CHANGED);
+        filter.addAction(TelephonyManager.ACTION_SIM_APPLICATION_STATE_CHANGED);
 
         mDataEnabledSettings = mPhone.getDataEnabledSettings();
 
@@ -806,34 +708,24 @@
         SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(mPhone.getContext());
         mAutoAttachEnabled.set(sp.getBoolean(Phone.DATA_DISABLED_ON_BOOT_KEY, false));
 
-        mSubscriptionManager = SubscriptionManager.from(mPhone.getContext());
-        mSubscriptionManager.addOnSubscriptionsChangedListener(mOnSubscriptionsChangedListener);
+        mNetworkPolicyManager = (NetworkPolicyManager) mPhone.getContext()
+                .getSystemService(Context.NETWORK_POLICY_SERVICE);
+        mNetworkPolicyManager.registerSubscriptionCallback(mSubscriptionCallback);
 
-        mNetworkPolicyManager = NetworkPolicyManager.from(mPhone.getContext());
-        mNetworkPolicyManager.registerListener(mNetworkPolicyListener);
-
-        HandlerThread dcHandlerThread = new HandlerThread("DcHandlerThread");
-        dcHandlerThread.start();
-        Handler dcHandler = new Handler(dcHandlerThread.getLooper());
-        mDcc = DcController.makeDcc(mPhone, this, mDataServiceManager, dcHandler, tagSuffix);
+        mHandlerThread = new HandlerThread("DcHandlerThread");
+        mHandlerThread.start();
+        Handler dcHandler = new Handler(mHandlerThread.getLooper());
+        mDcc = DcController.makeDcc(mPhone, this, mDataServiceManager, dcHandler, mLogTagSuffix);
         mDcTesterFailBringUpAll = new DcTesterFailBringUpAll(mPhone, dcHandler);
 
         mDataConnectionTracker = this;
         registerForAllEvents();
-        update();
         mApnObserver = new ApnChangeObserver();
         phone.getContext().getContentResolver().registerContentObserver(
                 Telephony.Carriers.CONTENT_URI, true, mApnObserver);
 
         initApnContexts();
 
-        for (ApnContext apnContext : mApnContexts.values()) {
-            // Register the reconnect and restart actions.
-            filter = new IntentFilter();
-            filter.addAction(INTENT_RECONNECT_ALARM + '.' + apnContext.getApnType());
-            mPhone.getContext().registerReceiver(mIntentReceiver, filter, null, mPhone);
-        }
-
         initEmergencyApnSetting();
         addEmergencyApnSetting();
 
@@ -846,10 +738,10 @@
     @VisibleForTesting
     public DcTracker() {
         mLogTag = "DCT";
+        mLogTagSuffix = null;
         mTelephonyManager = null;
         mAlarmManager = null;
         mPhone = null;
-        mUiccController = null;
         mDataConnectionTracker = null;
         mProvisionActionName = null;
         mSettingsObserver = new SettingsObserver(null, this);
@@ -873,8 +765,6 @@
                 DctConstants.EVENT_PS_RESTRICT_DISABLED, null);
         mPhone.getServiceStateTracker().registerForDataRegStateOrRatChanged(mTransportType, this,
                 DctConstants.EVENT_DATA_RAT_CHANGED, null);
-        // listens for PhysicalChannelConfig changes
-        mPhone.registerForServiceStateChanged(this, DctConstants.EVENT_SERVICE_STATE_CHANGED, null);
     }
 
     public void unregisterServiceStateTrackerEvents() {
@@ -884,9 +774,7 @@
         mPhone.getServiceStateTracker().unregisterForDataRoamingOff(this);
         mPhone.getServiceStateTracker().unregisterForPsRestrictedEnabled(this);
         mPhone.getServiceStateTracker().unregisterForPsRestrictedDisabled(this);
-        mPhone.getServiceStateTracker().unregisterForDataRegStateOrRatChanged(mTransportType,
-                this);
-        mPhone.unregisterForServiceStateChanged(this);
+        mPhone.getServiceStateTracker().unregisterForDataRegStateOrRatChanged(mTransportType, this);
     }
 
     private void registerForAllEvents() {
@@ -906,6 +794,8 @@
                 DctConstants.EVENT_VOICE_CALL_ENDED, null);
         mPhone.getCallTracker().registerForVoiceCallStarted(this,
                 DctConstants.EVENT_VOICE_CALL_STARTED, null);
+        mPhone.getDisplayInfoController().registerForTelephonyDisplayInfoChanged(this,
+                DctConstants.EVENT_TELEPHONY_DISPLAY_INFO_CHANGED, null);
         registerServiceStateTrackerEvents();
         mDataServiceManager.registerForServiceBindingChanged(this,
                 DctConstants.EVENT_DATA_SERVICE_BINDING_CHANGED, null);
@@ -927,12 +817,9 @@
 
         mIsDisposed = true;
         mPhone.getContext().unregisterReceiver(mIntentReceiver);
-        mUiccController.unregisterForIccChanged(this);
         mSettingsObserver.unobserve();
 
-        mSubscriptionManager
-                .removeOnSubscriptionsChangedListener(mOnSubscriptionsChangedListener);
-        mNetworkPolicyManager.unregisterListener(mNetworkPolicyListener);
+        mNetworkPolicyManager.unregisterSubscriptionCallback(mSubscriptionCallback);
         mDcc.dispose();
         mDcTesterFailBringUpAll.dispose();
 
@@ -945,6 +832,16 @@
         destroyDataConnections();
     }
 
+    /**
+     * Stop the internal handler thread
+     *
+     * TESTING ONLY
+     */
+    @VisibleForTesting
+    public void stopHandlerThread() {
+        mHandlerThread.quit();
+    }
+
     private void unregisterForAllEvents() {
          //Unregister for all events
         if (mTransportType == AccessNetworkConstants.TRANSPORT_TYPE_WWAN) {
@@ -953,16 +850,11 @@
             mPhone.mCi.unregisterForPcoData(this);
         }
 
-        IccRecords r = mIccRecords.get();
-        if (r != null) {
-            r.unregisterForRecordsLoaded(this);
-            mIccRecords.set(null);
-        }
         mPhone.getCallTracker().unregisterForVoiceCallEnded(this);
         mPhone.getCallTracker().unregisterForVoiceCallStarted(this);
+        mPhone.getDisplayInfoController().unregisterForTelephonyDisplayInfoChanged(this);
         unregisterServiceStateTrackerEvents();
         mDataServiceManager.unregisterForServiceBindingChanged(this);
-
         mDataEnabledSettings.unregisterForDataEnabledChanged(this);
         mDataEnabledSettings.unregisterForDataEnabledOverrideChanged(this);
     }
@@ -1018,7 +910,11 @@
 
     // Turn telephony radio on or off.
     private void setRadio(boolean on) {
-        final ITelephony phone = ITelephony.Stub.asInterface(ServiceManager.checkService("phone"));
+        final ITelephony phone = ITelephony.Stub.asInterface(
+                TelephonyFrameworkInitializer
+                        .getTelephonyServiceManager()
+                        .getTelephonyServiceRegisterer()
+                        .get());
         try {
             phone.setRadio(on);
         } catch (Exception e) {
@@ -1046,7 +942,9 @@
 
         private void enableMobileProvisioning() {
             final Message msg = obtainMessage(DctConstants.CMD_ENABLE_MOBILE_PROVISIONING);
-            msg.setData(Bundle.forPair(DctConstants.PROVISIONING_URL_KEY, mProvisionUrl));
+            Bundle bundle = new Bundle(1);
+            bundle.putString(DctConstants.PROVISIONING_URL_KEY, mProvisionUrl);
+            msg.setData(bundle);
             sendMessage(msg);
         }
 
@@ -1083,65 +981,77 @@
         if(DBG && mPhone != null) log("finalize");
     }
 
-    private ApnContext addApnContext(String type, NetworkConfig networkConfig) {
-        ApnContext apnContext = new ApnContext(mPhone, type, mLogTag, networkConfig, this);
-        mApnContexts.put(type, apnContext);
-        mApnContextsByType.put(ApnSetting.getApnTypesBitmaskFromString(type), apnContext);
-        mPrioritySortedApnContexts.add(apnContext);
-        return apnContext;
+    private void initApnContexts() {
+        PersistableBundle carrierConfig;
+        CarrierConfigManager configManager = (CarrierConfigManager) mPhone.getContext()
+                .getSystemService(Context.CARRIER_CONFIG_SERVICE);
+        if (configManager != null) {
+            carrierConfig = configManager.getConfigForSubId(mPhone.getSubId());
+        } else {
+            carrierConfig = null;
+        }
+        initApnContexts(carrierConfig);
     }
 
-    private void initApnContexts() {
-        log("initApnContexts: E");
-        // Load device network attributes from resources
-        String[] networkConfigStrings = mPhone.getContext().getResources().getStringArray(
-                com.android.internal.R.array.networkAttributes);
-        for (String networkConfigString : networkConfigStrings) {
-            NetworkConfig networkConfig = new NetworkConfig(networkConfigString);
-            ApnContext apnContext;
-
-            switch (networkConfig.type) {
-                case NETWORK_TYPE_DEFAULT:
-                    apnContext = addApnContext(PhoneConstants.APN_TYPE_DEFAULT, networkConfig);
-                    break;
-                case NETWORK_TYPE_MMS:
-                    apnContext = addApnContext(PhoneConstants.APN_TYPE_MMS, networkConfig);
-                    break;
-                case NETWORK_TYPE_SUPL:
-                    apnContext = addApnContext(PhoneConstants.APN_TYPE_SUPL, networkConfig);
-                    break;
-                case NETWORK_TYPE_DUN:
-                    apnContext = addApnContext(PhoneConstants.APN_TYPE_DUN, networkConfig);
-                    break;
-                case NETWORK_TYPE_HIPRI:
-                    apnContext = addApnContext(PhoneConstants.APN_TYPE_HIPRI, networkConfig);
-                    break;
-                case NETWORK_TYPE_FOTA:
-                    apnContext = addApnContext(PhoneConstants.APN_TYPE_FOTA, networkConfig);
-                    break;
-                case NETWORK_TYPE_IMS:
-                    apnContext = addApnContext(PhoneConstants.APN_TYPE_IMS, networkConfig);
-                    break;
-                case NETWORK_TYPE_CBS:
-                    apnContext = addApnContext(PhoneConstants.APN_TYPE_CBS, networkConfig);
-                    break;
-                case NETWORK_TYPE_IA:
-                    apnContext = addApnContext(PhoneConstants.APN_TYPE_IA, networkConfig);
-                    break;
-                case NETWORK_TYPE_EMERGENCY:
-                    apnContext = addApnContext(PhoneConstants.APN_TYPE_EMERGENCY, networkConfig);
-                    break;
-                case NETWORK_TYPE_MCX:
-                    apnContext = addApnContext(PhoneConstants.APN_TYPE_MCX, networkConfig);
-                    break;
-                default:
-                    log("initApnContexts: skipping unknown type=" + networkConfig.type);
-                    continue;
-            }
-            log("initApnContexts: apnContext=" + apnContext);
+    //Blows away any existing apncontexts that may exist, only use in ctor.
+    private void initApnContexts(PersistableBundle carrierConfig) {
+        if (!mTelephonyManager.isDataCapable()) {
+            log("initApnContexts: isDataCapable == false.  No Apn Contexts loaded");
+            return;
         }
 
-        if (VDBG) log("initApnContexts: X mApnContexts=" + mApnContexts);
+        log("initApnContexts: E");
+        // Load device network attributes from resources
+        final Collection<ApnConfigType> types =
+                new ApnConfigTypeRepository(carrierConfig).getTypes();
+
+        for (ApnConfigType apnConfigType : types) {
+            ApnContext apnContext = new ApnContext(mPhone, apnConfigType.getType(), mLogTag, this,
+                    apnConfigType.getPriority());
+            mPrioritySortedApnContexts.add(apnContext);
+            mApnContexts.put(apnContext.getApnType(), apnContext);
+            mApnContextsByType.put(ApnSetting.getApnTypesBitmaskFromString(apnContext.getApnType()),
+                    apnContext);
+
+            log("initApnContexts: apnContext=" + ApnSetting.getApnTypeString(
+                    apnConfigType.getType()));
+        }
+        mPrioritySortedApnContexts.sort((c1, c2) -> c2.getPriority() - c1.getPriority());
+        logSortedApnContexts();
+    }
+
+    private void sortApnContextByPriority() {
+        if (!mTelephonyManager.isDataCapable()) {
+            log("sortApnContextByPriority: isDataCapable == false.  No Apn Contexts loaded");
+            return;
+        }
+
+        PersistableBundle carrierConfig;
+        CarrierConfigManager configManager = (CarrierConfigManager) mPhone.getContext()
+                .getSystemService(Context.CARRIER_CONFIG_SERVICE);
+        if (configManager != null) {
+            carrierConfig = configManager.getConfigForSubId(mPhone.getSubId());
+        } else {
+            carrierConfig = null;
+        }
+
+        log("sortApnContextByPriority: E");
+        // Load device network attributes from resources
+        final Collection<ApnConfigType> types =
+                new ApnConfigTypeRepository(carrierConfig).getTypes();
+        for (ApnConfigType apnConfigType : types) {
+            if (mApnContextsByType.contains(apnConfigType.getType())) {
+                ApnContext apnContext = mApnContextsByType.get(apnConfigType.getType());
+                apnContext.setPriority(apnConfigType.getPriority());
+            }
+        }
+
+        //Doing sorted in a different list to keep thread safety
+        ArrayList<ApnContext> prioritySortedApnContexts =
+                new ArrayList<>(mPrioritySortedApnContexts);
+        prioritySortedApnContexts.sort((c1, c2) -> c2.getPriority() - c1.getPriority());
+        mPrioritySortedApnContexts = prioritySortedApnContexts;
+        logSortedApnContexts();
     }
 
     public LinkProperties getLinkProperties(String apnType) {
@@ -1186,17 +1096,23 @@
         return result.toArray(new String[0]);
     }
 
+    @VisibleForTesting
+    public Collection<ApnContext> getApnContexts() {
+        return mPrioritySortedApnContexts;
+    }
+
+    /** Return active ApnSetting of a specific apnType */
+    public ApnSetting getActiveApnSetting(String apnType) {
+        if (VDBG) log("get active ApnSetting for type:" + apnType);
+        ApnContext apnContext = mApnContexts.get(apnType);
+        return (apnContext != null) ? apnContext.getApnSetting() : null;
+    }
+
     // Return active apn of specific apn type
     public String getActiveApnString(String apnType) {
         if (VDBG) log( "get active apn string for type:" + apnType);
-        ApnContext apnContext = mApnContexts.get(apnType);
-        if (apnContext != null) {
-            ApnSetting apnSetting = apnContext.getApnSetting();
-            if (apnSetting != null) {
-                return apnSetting.getApnName();
-            }
-        }
-        return null;
+        ApnSetting setting = getActiveApnSetting(apnType);
+        return (setting != null) ? setting.getApnName() : null;
     }
 
     /**
@@ -1207,23 +1123,79 @@
      * Assumes there is less than one {@link ApnSetting} can support the given apn type.
      */
     public DctConstants.State getState(String apnType) {
+        DctConstants.State state = DctConstants.State.IDLE;
         final int apnTypeBitmask = ApnSetting.getApnTypesBitmaskFromString(apnType);
         for (DataConnection dc : mDataConnections.values()) {
             ApnSetting apnSetting = dc.getApnSetting();
             if (apnSetting != null && apnSetting.canHandleType(apnTypeBitmask)) {
                 if (dc.isActive()) {
-                    return DctConstants.State.CONNECTED;
+                    state = getBetterConnectionState(state, DctConstants.State.CONNECTED);
                 } else if (dc.isActivating()) {
-                    return DctConstants.State.CONNECTING;
+                    state = getBetterConnectionState(state, DctConstants.State.CONNECTING);
                 } else if (dc.isInactive()) {
-                    return DctConstants.State.IDLE;
+                    state = getBetterConnectionState(state, DctConstants.State.IDLE);
                 } else if (dc.isDisconnecting()) {
-                    return DctConstants.State.DISCONNECTING;
+                    state = getBetterConnectionState(state, DctConstants.State.DISCONNECTING);
                 }
             }
         }
+        return state;
+    }
 
-        return DctConstants.State.IDLE;
+    /** Convert the internal DctConstants enum state to the TelephonyManager DATA_*  state.
+     * @param state the DctConstants.State
+     * @return a corresponding TelephonyManager.DataState
+     */
+    @TelephonyManager.DataState
+    public static int convertDctStateToTelephonyDataState(DctConstants.State state) {
+        switch(state) {
+            case CONNECTING: // fall through
+            case RETRYING:
+                return TelephonyManager.DATA_CONNECTING;
+            case CONNECTED:
+                return TelephonyManager.DATA_CONNECTED;
+            case DISCONNECTING:
+                return TelephonyManager.DATA_DISCONNECTING;
+            case IDLE: // fall through
+            case FAILED: // fall through
+            default:
+                return TelephonyManager.DATA_DISCONNECTED;
+        }
+    }
+
+    /** Return the Precise Data Connection State information */
+    public @NonNull PreciseDataConnectionState getPreciseDataConnectionState(
+            String apnType, boolean isSuspended, int networkType) {
+
+        int telState = convertDctStateToTelephonyDataState(getState(apnType));
+        // Since suspended isn't actually reported by the DCT, do a fixup based on current
+        // voice call state and device + rat capability
+        if ((telState == TelephonyManager.DATA_CONNECTED
+                || telState == TelephonyManager.DATA_DISCONNECTING)
+                && isSuspended) {
+            telState = TelephonyManager.DATA_SUSPENDED;
+        }
+
+        ApnSetting apnSetting = getActiveApnSetting(apnType);
+        int apnTypesBitmask = ApnSetting.getApnTypesBitmaskFromString(apnType);
+
+        // TODO: should the data fail cause be populated?
+        return new PreciseDataConnectionState(
+                telState, networkType, apnTypesBitmask, apnType,
+                getLinkProperties(apnType),
+                DataFailCause.NONE, apnSetting);
+    }
+
+    /**
+     * Return a better connection state between {@code stateA} and {@code stateB}. Check
+     * {@link #DATA_CONNECTION_STATE_PRIORITIES} for the details.
+     * @return the better connection state between {@code stateA} and {@code stateB}.
+     */
+    private static DctConstants.State getBetterConnectionState(
+            DctConstants.State stateA, DctConstants.State stateB) {
+        int idxA = ArrayUtils.indexOf(DATA_CONNECTION_STATE_PRIORITIES, stateA);
+        int idxB = ArrayUtils.indexOf(DATA_CONNECTION_STATE_PRIORITIES, stateB);
+        return idxA >= idxB ? stateA : stateB;
     }
 
     // Return if apn type is a provisioning apn.
@@ -1294,7 +1266,7 @@
         if (DBG) log ("onDataConnectionDetached: stop polling and notify detached");
         stopNetStatPoll();
         stopDataStallAlarm();
-        mPhone.notifyDataConnection();
+        mPhone.notifyAllActiveDataConnections();
         mAttached.set(false);
     }
 
@@ -1305,7 +1277,7 @@
             if (DBG) log("onDataConnectionAttached: start polling notify attached");
             startNetStatPoll();
             startDataStallAlarm(DATA_STALL_NOT_SUSPECTED);
-            mPhone.notifyDataConnection();
+            mPhone.notifyAllActiveDataConnections();
         }
         if (mAutoAttachOnCreationConfig) {
             mAutoAttachEnabled.set(true);
@@ -1346,6 +1318,11 @@
 
         DataConnectionReasons reasons = new DataConnectionReasons();
 
+        int requestApnType = 0;
+        if (apnContext != null) {
+            requestApnType = apnContext.getApnTypeBitmask();
+        }
+
         // Step 1: Get all environment conditions.
         final boolean internalDataEnabled = mDataEnabledSettings.isInternalDataEnabled();
         boolean attachedState = mAttached.get();
@@ -1358,14 +1335,11 @@
             radioStateFromCarrier = true;
         }
 
-        boolean recordsLoaded = mIccRecords.get() != null && mIccRecords.get().getRecordsLoaded();
-
         boolean defaultDataSelected = SubscriptionManager.isValidSubscriptionId(
                 SubscriptionManager.getDefaultDataSubscriptionId());
 
         boolean isMeteredApnType = apnContext == null
-                || ApnSettingUtils.isMeteredApnType(ApnSetting.getApnTypesBitmaskFromString(
-                        apnContext.getApnType()) , mPhone);
+                || ApnSettingUtils.isMeteredApnType(requestApnType, mPhone);
 
         PhoneConstants.State phoneState = PhoneConstants.State.IDLE;
         // Note this is explicitly not using mPhone.getState.  See b/19090488.
@@ -1381,7 +1355,7 @@
 
         // Step 2: Special handling for emergency APN.
         if (apnContext != null
-                && apnContext.getApnType().equals(PhoneConstants.APN_TYPE_EMERGENCY)
+                && requestApnType == ApnSetting.TYPE_EMERGENCY
                 && apnContext.isConnectable()) {
             // If this is an emergency APN, as long as the APN is connectable, we
             // should allow it.
@@ -1399,22 +1373,22 @@
 
         // In legacy mode, if RAT is IWLAN then don't allow default/IA PDP at all.
         // Rest of APN types can be evaluated for remaining conditions.
-        if ((apnContext != null && (apnContext.getApnType().equals(PhoneConstants.APN_TYPE_DEFAULT)
-                || apnContext.getApnType().equals(PhoneConstants.APN_TYPE_IA)))
+        if ((apnContext != null && requestApnType == TYPE_DEFAULT
+                || requestApnType == TYPE_IA)
                 && mPhone.getTransportManager().isInLegacyMode()
                 && dataRat == ServiceState.RIL_RADIO_TECHNOLOGY_IWLAN) {
             reasons.add(DataDisallowedReasonType.ON_IWLAN);
         }
 
-        if (isEmergency()) {
+        if (shouldRestrictDataForEcbm() || mPhone.isInEmergencyCall()) {
             reasons.add(DataDisallowedReasonType.IN_ECBM);
         }
 
         if (!attachedState && !shouldAutoAttach() && requestType != REQUEST_TYPE_HANDOVER) {
             reasons.add(DataDisallowedReasonType.NOT_ATTACHED);
         }
-        if (!recordsLoaded) {
-            reasons.add(DataDisallowedReasonType.RECORD_NOT_LOADED);
+        if (mPhone.getSubId() == SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
+            reasons.add(DataDisallowedReasonType.SIM_NOT_READY);
         }
         if (phoneState != PhoneConstants.State.IDLE
                 && !mPhone.getServiceStateTracker().isConcurrentVoiceAndDataAllowed()) {
@@ -1451,7 +1425,7 @@
         }
 
         boolean isDataEnabled = apnContext == null ? mDataEnabledSettings.isDataEnabled()
-                : mDataEnabledSettings.isDataEnabled(apnContext.getApnTypeBitmask());
+                : mDataEnabledSettings.isDataEnabled(requestApnType);
 
         if (!isDataEnabled) {
             reasons.add(DataDisallowedReasonType.DATA_DISABLED);
@@ -1476,7 +1450,7 @@
             // Or if the data is on cellular, and the APN type is determined unmetered by the
             // configuration.
             } else if (mTransportType == AccessNetworkConstants.TRANSPORT_TYPE_WWAN
-                    && !isMeteredApnType) {
+                    && !isMeteredApnType && requestApnType != TYPE_DEFAULT) {
                 reasons.add(DataAllowedReasonType.UNMETERED_APN);
             }
 
@@ -1501,7 +1475,7 @@
     }
 
     // arg for setupDataOnAllConnectableApns
-    private enum RetryFailures {
+    protected enum RetryFailures {
         // retry failed networks always (the old default)
         ALWAYS,
         // retry only when a substantial change has occurred.  Either:
@@ -1510,7 +1484,7 @@
         ONLY_ON_CHANGE
     };
 
-    private void setupDataOnAllConnectableApns(String reason, RetryFailures retryFailures) {
+    protected void setupDataOnAllConnectableApns(String reason, RetryFailures retryFailures) {
         if (VDBG) log("setupDataOnAllConnectableApns: " + reason);
 
         if (DBG && !VDBG) {
@@ -1531,7 +1505,7 @@
         }
     }
 
-    private void setupDataOnConnectableApn(ApnContext apnContext, String reason,
+    protected void setupDataOnConnectableApn(ApnContext apnContext, String reason,
             RetryFailures retryFailures) {
         if (VDBG) log("setupDataOnAllConnectableApns: apnContext " + apnContext);
 
@@ -1552,10 +1526,11 @@
         }
     }
 
-    boolean isEmergency() {
-        final boolean result = mPhone.isInEcm() || mPhone.isInEmergencyCall();
-        log("isEmergency: result=" + result);
-        return result;
+    private boolean shouldRestrictDataForEcbm() {
+        boolean isInEcm = mPhone.isInEcm();
+        boolean isInImsEcm = mPhone.getImsPhone() != null && mPhone.getImsPhone().isInImsEcm();
+        log("shouldRestrictDataForEcbm: isInEcm=" + isInEcm + " isInImsEcm=" + isInImsEcm);
+        return isInEcm && !isInImsEcm;
     }
 
     private boolean trySetupData(ApnContext apnContext, @RequestNetworkType int requestType) {
@@ -1585,7 +1560,8 @@
                 apnContext.setState(DctConstants.State.IDLE);
             }
             int radioTech = getDataRat();
-            if (radioTech == ServiceState.RIL_RADIO_TECHNOLOGY_UNKNOWN) {
+            if (radioTech == ServiceState.RIL_RADIO_TECHNOLOGY_UNKNOWN && mPhone.getServiceState()
+                    .getState() == ServiceState.STATE_IN_SERVICE) {
                 radioTech = getVoiceRat();
             }
             log("service state=" + mPhone.getServiceState());
@@ -1595,7 +1571,10 @@
                 ArrayList<ApnSetting> waitingApns =
                         buildWaitingApns(apnContext.getApnType(), radioTech);
                 if (waitingApns.isEmpty()) {
-                    notifyNoData(DataFailCause.MISSING_UNKNOWN_APN, apnContext);
+                    ApnSetting apn = apnContext != null ? apnContext.getApnSetting() : null;
+                    mPhone.notifyDataConnectionFailed(apnContext.getApnType(),
+                            apn != null ? apn.getApnName() : null,
+                            DataFailCause.MISSING_UNKNOWN_APN);
                     String str = "trySetupData: X No APN found retValue=false";
                     if (DBG) log(str);
                     apnContext.requestLog(str);
@@ -1684,7 +1663,8 @@
                 continue;
             }
 
-            if (shouldCleanUpConnection(apnContext, disableMeteredOnly)) {
+            if (shouldCleanUpConnection(apnContext, disableMeteredOnly,
+                    reason.equals(Phone.REASON_SINGLE_PDN_ARBITRATION))) {
                 // TODO - only do cleanup if not disconnected
                 if (apnContext.isDisconnected() == false) didDisconnect = true;
                 apnContext.setReason(reason);
@@ -1710,14 +1690,18 @@
         return didDisconnect;
     }
 
-    boolean shouldCleanUpConnection(ApnContext apnContext, boolean disableMeteredOnly) {
+    boolean shouldCleanUpConnection(ApnContext apnContext, boolean disableMeteredOnly,
+            boolean singlePdn) {
         if (apnContext == null) return false;
 
+        // If APN setting is not null and the reason is single PDN arbitration, clean up connection.
+        ApnSetting apnSetting = apnContext.getApnSetting();
+        if (apnSetting != null && singlePdn) return true;
+
         // If meteredOnly is false, clean up all connections.
         if (!disableMeteredOnly) return true;
 
         // If meteredOnly is true, and apnSetting is null or it's un-metered, no need to clean up.
-        ApnSetting apnSetting = apnContext.getApnSetting();
         if (apnSetting == null || !ApnSettingUtils.isMetered(apnSetting, mPhone)) return false;
 
         boolean isRoaming = mPhone.getServiceState().getDataRoaming();
@@ -1820,7 +1804,7 @@
         // Make sure reconnection alarm is cleaned up if there is no ApnContext
         // associated to the connection.
         if (dataConnection != null) {
-            cancelReconnectAlarm(apnContext);
+            cancelReconnect(apnContext);
         }
         str = "cleanUpConnectionInternal: X detach=" + detach + " reason="
                 + apnContext.getReason();
@@ -1833,10 +1817,6 @@
      */
     @VisibleForTesting
     public @NonNull ArrayList<ApnSetting> fetchDunApns() {
-        if (SystemProperties.getBoolean("net.tethering.noprovisioning", false)) {
-            log("fetchDunApns: net.tethering.noprovisioning=true ret: empty list");
-            return new ArrayList<ApnSetting>(0);
-        }
         int bearer = getDataRat();
         ArrayList<ApnSetting> dunCandidates = new ArrayList<ApnSetting>();
         ArrayList<ApnSetting> retDunSettings = new ArrayList<ApnSetting>();
@@ -1923,25 +1903,7 @@
         return null;
     }
 
-    /**
-     * Cancels the alarm associated with apnContext.
-     *
-     * @param apnContext on which the alarm should be stopped.
-     */
-    private void cancelReconnectAlarm(ApnContext apnContext) {
-        if (apnContext == null) return;
-
-        PendingIntent intent = apnContext.getReconnectIntent();
-
-        if (intent != null) {
-                AlarmManager am =
-                    (AlarmManager) mPhone.getContext().getSystemService(Context.ALARM_SERVICE);
-                am.cancel(intent);
-                apnContext.setReconnectIntent(null);
-        }
-    }
-
-    boolean isPermanentFailure(@FailCause int dcFailCause) {
+    boolean isPermanentFailure(@DataFailureCause int dcFailCause) {
         return (DataFailCause.isPermanentFailure(mPhone.getContext(), dcFailCause,
                 mPhone.getSubId())
                 && (mAttached.get() == false || dcFailCause != DataFailCause.SIGNAL_LOST));
@@ -2007,7 +1969,7 @@
         // this type.
         if (!apnContext.getApnType().equals(PhoneConstants.APN_TYPE_DUN)
                 || ServiceState.isGsm(getDataRat())) {
-            dataConnection = checkForCompatibleConnectedApnContext(apnContext);
+            dataConnection = checkForCompatibleDataConnection(apnContext);
             if (dataConnection != null) {
                 // Get the apn setting used by the data connection
                 ApnSetting dataConnectionApnSetting = dataConnection.getApnSetting();
@@ -2026,7 +1988,10 @@
                     return false;
                 }
 
-                if (!apnContext.getApnType().equals(PhoneConstants.APN_TYPE_IMS)) {
+                // Should not start cleanUp if the setupData is for IMS APN
+                // or retry of same APN(State==RETRYING).
+                if (!apnContext.getApnType().equals(PhoneConstants.APN_TYPE_IMS)
+                        && (apnContext.getState() != DctConstants.State.RETRYING)) {
                     // 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
@@ -2069,14 +2034,27 @@
         Message msg = obtainMessage();
         msg.what = DctConstants.EVENT_DATA_SETUP_COMPLETE;
         msg.obj = new Pair<ApnContext, Integer>(apnContext, generation);
-        dataConnection.bringUp(apnContext, profileId, radioTech, msg, generation, requestType,
-                mPhone.getSubId());
 
-        if (DBG) log("setupData: initing!");
+        ApnSetting preferredApn = getPreferredApn();
+        boolean isPreferredApn = apnSetting.equals(preferredApn);
+        dataConnection.bringUp(apnContext, profileId, radioTech, msg, generation, requestType,
+                mPhone.getSubId(), isPreferredApn);
+
+        if (DBG) {
+            if (isPreferredApn) {
+                log("setupData: initing! isPreferredApn=" + isPreferredApn
+                        + ", apnSetting={" + apnSetting.toString() + "}");
+            } else {
+                String preferredApnStr = preferredApn == null ? "null" : preferredApn.toString();
+                log("setupData: initing! isPreferredApn=" + isPreferredApn
+                        + ", apnSetting={" + apnSetting + "}"
+                        + ", preferredApn={" + preferredApnStr + "}");
+            }
+        }
         return true;
     }
 
-    private void setInitialAttachApn() {
+    protected void setInitialAttachApn() {
         ApnSetting iaApnSetting = null;
         ApnSetting defaultApnSetting = null;
         ApnSetting firstNonEmergencyApnSetting = null;
@@ -2089,7 +2067,7 @@
             // Search for Initial APN setting and the first apn that can handle default
             for (ApnSetting apn : mAllApnSettings) {
                 if (firstNonEmergencyApnSetting == null
-                        && !apn.canHandleType(ApnSetting.TYPE_EMERGENCY)) {
+                        && !apn.isEmergencyApn()) {
                     firstNonEmergencyApnSetting = apn;
                     log("setInitialApn: firstNonEmergencyApnSetting="
                             + firstNonEmergencyApnSetting);
@@ -2108,6 +2086,12 @@
             }
         }
 
+        if ((iaApnSetting == null) && (defaultApnSetting == null) &&
+                !allowInitialAttachForOperator()) {
+            log("Abort Initial attach");
+            return;
+        }
+
         // The priority of apn candidates from highest to lowest is:
         //   1) APN_TYPE_IA (Initial Attach)
         //   2) mPreferredApn, i.e. the current preferred apn
@@ -2140,6 +2124,10 @@
         }
     }
 
+    protected boolean allowInitialAttachForOperator() {
+        return true;
+    }
+
     /**
      * Handles changes to the APN database.
      */
@@ -2211,8 +2199,8 @@
             }
         }
         boolean onlySingleDcAllowed = false;
-        if (Build.IS_DEBUGGABLE &&
-                SystemProperties.getBoolean("persist.telephony.test.singleDc", false)) {
+        if (TelephonyUtils.IS_DEBUGGABLE
+                && SystemProperties.getBoolean("persist.telephony.test.singleDc", false)) {
             onlySingleDcAllowed = true;
         }
         if (singleDcRats != null) {
@@ -2242,13 +2230,6 @@
          * desired power state has changed in the interim, we don't want to
          * override it with an unconditional power on.
          */
-
-        int reset = Integer.parseInt(SystemProperties.get("net.ppp.reset-by-timeout", "0"));
-        try {
-            SystemProperties.set("net.ppp.reset-by-timeout", String.valueOf(reset + 1));
-        } catch (RuntimeException ex) {
-            log("Failed to set net.ppp.reset-by-timeout");
-        }
     }
 
     /**
@@ -2269,73 +2250,124 @@
         return retry;
     }
 
-    private void startAlarmForReconnect(long delay, ApnContext apnContext) {
-        String apnType = apnContext.getApnType();
-
-        Intent intent = new Intent(INTENT_RECONNECT_ALARM + "." + apnType);
-        intent.putExtra(INTENT_RECONNECT_ALARM_EXTRA_REASON, apnContext.getReason());
-        intent.putExtra(INTENT_RECONNECT_ALARM_EXTRA_TYPE, apnType);
-        intent.putExtra(INTENT_RECONNECT_ALARM_EXTRA_TRANSPORT_TYPE, mTransportType);
-        SubscriptionManager.putPhoneIdAndSubIdExtra(intent, mPhone.getPhoneId());
-        intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
+    protected void startReconnect(long delay, ApnContext apnContext) {
+        Message msg = obtainMessage(DctConstants.EVENT_DATA_RECONNECT,
+                       mPhone.getSubId(), mTransportType, apnContext);
+        cancelReconnect(apnContext);
+        sendMessageDelayed(msg, delay);
 
         if (DBG) {
-            log("startAlarmForReconnect: delay=" + delay + " action=" + intent.getAction()
-                    + " apn=" + apnContext);
-        }
-
-        PendingIntent alarmIntent = PendingIntent.getBroadcast(mPhone.getContext(), 0,
-                intent, PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);
-        apnContext.setReconnectIntent(alarmIntent);
-
-        // Use the exact timer instead of the inexact one to provide better user experience.
-        // In some extreme cases, we saw the retry was delayed for few minutes.
-        // Note that if the stated trigger time is in the past, the alarm will be triggered
-        // immediately.
-        mAlarmManager.setExact(AlarmManager.ELAPSED_REALTIME_WAKEUP,
-                SystemClock.elapsedRealtime() + delay, alarmIntent);
-    }
-
-    private void notifyNoData(@FailCause int lastFailCauseCode,
-                              ApnContext apnContext) {
-        if (DBG) log( "notifyNoData: type=" + apnContext.getApnType());
-        if (isPermanentFailure(lastFailCauseCode)
-            && (!apnContext.getApnType().equals(PhoneConstants.APN_TYPE_DEFAULT))) {
-            mPhone.notifyDataConnectionFailed(apnContext.getApnType());
+            log("startReconnect: delay=" + delay + " apn="
+                    + apnContext + "reason: " + apnContext.getReason()
+                    + " subId: " + mPhone.getSubId());
         }
     }
 
-    private void onRecordsLoadedOrSubIdChanged() {
-        if (DBG) log("onRecordsLoadedOrSubIdChanged: createAllApnList");
+    /**
+     * Cancels the alarm associated with apnContext.
+     *
+     * @param apnContext on which the alarm should be stopped.
+     */
+    protected void cancelReconnect(ApnContext apnContext) {
+        if (apnContext == null) return;
+
+        if (DBG) {
+            log("cancelReconnect: apn=" + apnContext);
+        }
+        removeMessages(DctConstants.EVENT_DATA_RECONNECT, apnContext);
+    }
+
+    /**
+     * Read configuration. Note this must be called after carrier config is ready.
+     */
+    private void readConfiguration() {
+        log("readConfiguration");
         if (mTransportType == AccessNetworkConstants.TRANSPORT_TYPE_WWAN) {
             // Auto attach is for cellular only.
             mAutoAttachOnCreationConfig = mPhone.getContext().getResources()
                     .getBoolean(com.android.internal.R.bool.config_auto_attach_data_on_creation);
         }
 
-        createAllApnList();
-        setDataProfilesAsNeeded();
-        setInitialAttachApn();
-        mPhone.notifyDataConnection();
-        setupDataOnAllConnectableApns(Phone.REASON_SIM_LOADED, RetryFailures.ALWAYS);
+        mAutoAttachEnabled.set(false);
+        setDefaultDataRoamingEnabled();
+        read5GConfiguration();
+        registerSettingsObserver();
+        mConfigReady = true;
     }
 
-    private void onSimNotReady() {
-        if (DBG) log("onSimNotReady");
+    /**
+     * @return {@code true} if carrier config has been applied.
+     */
+    private boolean isCarrierConfigApplied() {
+        CarrierConfigManager configManager = (CarrierConfigManager) mPhone.getContext()
+                .getSystemService(Context.CARRIER_CONFIG_SERVICE);
+        if (configManager != null) {
+            PersistableBundle b = configManager.getConfigForSubId(mPhone.getSubId());
+            if (b != null) {
+                return CarrierConfigManager.isConfigForIdentifiedCarrier(b);
+            }
+        }
+        return false;
+    }
 
+    private void onCarrierConfigChanged() {
+        if (DBG) log("onCarrierConfigChanged");
+
+        if (!isCarrierConfigApplied()) {
+            log("onCarrierConfigChanged: Carrier config is not ready yet.");
+            return;
+        }
+
+        readConfiguration();
+
+        if (mSimState == TelephonyManager.SIM_STATE_LOADED) {
+            createAllApnList();
+            setDataProfilesAsNeeded();
+            setInitialAttachApn();
+            sortApnContextByPriority();
+            cleanUpConnectionsOnUpdatedApns(true, Phone.REASON_CARRIER_CHANGE);
+            setupDataOnAllConnectableApns(Phone.REASON_CARRIER_CHANGE, RetryFailures.ALWAYS);
+        } else {
+            log("onCarrierConfigChanged: SIM is not loaded yet.");
+        }
+    }
+
+    private void onSimAbsent() {
+        if (DBG) log("onSimAbsent");
+
+        mConfigReady = false;
         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
         mAutoAttachEnabled.set(false);
-        mOnSubscriptionsChangedListener.mPreviousSubId.set(
-                SubscriptionManager.INVALID_SUBSCRIPTION_ID);
         // In no-sim case, we should still send the emergency APN to the modem, if there is any.
         createAllApnList();
         setDataProfilesAsNeeded();
     }
 
-    private DataConnection checkForCompatibleConnectedApnContext(ApnContext apnContext) {
+    private void onSimStateUpdated(@SimState int simState) {
+        mSimState = simState;
+
+        if (DBG) {
+            log("onSimStateUpdated: state=" + SubscriptionInfoUpdater.simStateString(mSimState));
+        }
+
+        if (mSimState == TelephonyManager.SIM_STATE_ABSENT) {
+            onSimAbsent();
+        } else if (mSimState == TelephonyManager.SIM_STATE_LOADED) {
+            if (mConfigReady) {
+                createAllApnList();
+                setDataProfilesAsNeeded();
+                setInitialAttachApn();
+                setupDataOnAllConnectableApns(Phone.REASON_SIM_LOADED, RetryFailures.ALWAYS);
+            } else {
+                log("onSimStateUpdated: config not ready yet.");
+            }
+        }
+    }
+
+    private DataConnection checkForCompatibleDataConnection(ApnContext apnContext) {
         int apnType = apnContext.getApnTypeBitmask();
         ArrayList<ApnSetting> dunSettings = null;
 
@@ -2343,71 +2375,46 @@
             dunSettings = sortApnListByPreferred(fetchDunApns());
         }
         if (DBG) {
-            log("checkForCompatibleConnectedApnContext: apnContext=" + apnContext );
+            log("checkForCompatibleDataConnection: apnContext=" + apnContext);
         }
 
         DataConnection potentialDc = null;
-        ApnContext potentialApnCtx = null;
-        for (ApnContext curApnCtx : mApnContexts.values()) {
-            DataConnection curDc = curApnCtx.getDataConnection();
+        for (DataConnection curDc : mDataConnections.values()) {
             if (curDc != null) {
-                ApnSetting apnSetting = curApnCtx.getApnSetting();
+                ApnSetting apnSetting = curDc.getApnSetting();
                 log("apnSetting: " + apnSetting);
                 if (dunSettings != null && dunSettings.size() > 0) {
                     for (ApnSetting dunSetting : dunSettings) {
                         if (dunSetting.equals(apnSetting)) {
-                            switch (curApnCtx.getState()) {
-                                case CONNECTED:
-                                    if (DBG) {
-                                        log("checkForCompatibleConnectedApnContext:"
-                                                + " found dun conn=" + curDc
-                                                + " curApnCtx=" + curApnCtx);
-                                    }
-                                    return curDc;
-                                case CONNECTING:
-                                    potentialDc = curDc;
-                                    potentialApnCtx = curApnCtx;
-                                    break;
-                                default:
-                                    // Not connected, potential unchanged
-                                    break;
+                            if (curDc.isActive()) {
+                                if (DBG) {
+                                    log("checkForCompatibleDataConnection:"
+                                            + " found dun conn=" + curDc);
+                                }
+                                return curDc;
+                            } else if (curDc.isActivating()) {
+                                potentialDc = curDc;
                             }
                         }
                     }
                 } else if (apnSetting != null && apnSetting.canHandleType(apnType)) {
-                    switch (curApnCtx.getState()) {
-                        case CONNECTED:
-                            if (DBG) {
-                                log("checkForCompatibleConnectedApnContext:"
-                                        + " found canHandle conn=" + curDc
-                                        + " curApnCtx=" + curApnCtx);
-                            }
-                            return curDc;
-                        case CONNECTING:
-                            potentialDc = curDc;
-                            potentialApnCtx = curApnCtx;
-                            break;
-                        default:
-                            // Not connected, potential unchanged
-                            break;
+                    if (curDc.isActive()) {
+                        if (DBG) {
+                            log("checkForCompatibleDataConnection:"
+                                    + " found canHandle conn=" + curDc);
+                        }
+                        return curDc;
+                    } else if (curDc.isActivating()) {
+                        potentialDc = curDc;
                     }
                 }
-            } else {
-                if (VDBG) {
-                    log("checkForCompatibleConnectedApnContext: not conn curApnCtx=" + curApnCtx);
-                }
             }
         }
-        if (potentialDc != null) {
-            if (DBG) {
-                log("checkForCompatibleConnectedApnContext: found potential conn=" + potentialDc
-                        + " curApnCtx=" + potentialApnCtx);
-            }
-            return potentialDc;
-        }
 
-        if (DBG) log("checkForCompatibleConnectedApnContext: NO conn apnContext=" + apnContext);
-        return null;
+        if (DBG) {
+            log("checkForCompatibleDataConnection: potential dc=" + potentialDc);
+        }
+        return potentialDc;
     }
 
     private void addRequestNetworkCompleteMsg(Message onCompleteMsg,
@@ -2423,7 +2430,7 @@
     private void sendRequestNetworkCompleteMsg(Message message, boolean success,
                                                @TransportType int transport,
                                                @RequestNetworkType int requestType,
-                                               @FailCause int cause) {
+                                               @DataFailureCause int cause) {
         if (message == null) return;
 
         Bundle b = message.getData();
@@ -2511,11 +2518,16 @@
         }
         apnContext.setEnabled(true);
         apnContext.resetErrorCodeRetries();
-        if (trySetupData(apnContext, requestType)) {
-            addRequestNetworkCompleteMsg(onCompleteMsg, apnType);
+
+        if (mConfigReady || apnContext.getApnTypeBitmask() == ApnSetting.TYPE_EMERGENCY) {
+            if (trySetupData(apnContext, requestType)) {
+                addRequestNetworkCompleteMsg(onCompleteMsg, apnType);
+            } else {
+                sendRequestNetworkCompleteMsg(onCompleteMsg, false, mTransportType,
+                        requestType, DataFailCause.NONE);
+            }
         } else {
-            sendRequestNetworkCompleteMsg(onCompleteMsg, false, mTransportType,
-                    requestType, DataFailCause.NONE);
+            log("onEnableApn: config not ready yet.");
         }
     }
 
@@ -2668,7 +2680,7 @@
 
             setupDataOnAllConnectableApns(Phone.REASON_ROAMING_OFF, RetryFailures.ALWAYS);
         } else {
-            mPhone.notifyDataConnection();
+            mPhone.notifyAllActiveDataConnections();
         }
     }
 
@@ -2699,7 +2711,7 @@
             if (DBG) log("onDataRoamingOnOrSettingsChanged: setup data on roaming");
 
             setupDataOnAllConnectableApns(Phone.REASON_ROAMING_ON, RetryFailures.ALWAYS);
-            mPhone.notifyDataConnection();
+            mPhone.notifyAllActiveDataConnections();
         } else {
             // If the user does not turn on data roaming, when we transit from non-roaming to
             // roaming, we need to tear down the data connection otherwise the user might be
@@ -2731,7 +2743,7 @@
             // Assume data is connected on the simulator
             // FIXME  this can be improved
             // setState(DctConstants.State.CONNECTED);
-            mPhone.notifyDataConnection();
+            mPhone.notifyAllActiveDataConnections();
 
             log("onRadioAvailable: We're on the simulator; assuming data is connected");
         }
@@ -2787,10 +2799,8 @@
                     mProvisioningSpinner));
         }
 
-        // Notify data is connected except for handover case.
-        if (type != REQUEST_TYPE_HANDOVER) {
-            mPhone.notifyDataConnection(apnContext.getApnType());
-        }
+        mPhone.notifyDataConnection(apnContext.getApnType());
+
         startNetStatPoll();
         startDataStallAlarm(DATA_STALL_NOT_SUSPECTED);
     }
@@ -2799,7 +2809,7 @@
      * A SETUP (aka bringUp) has completed, possibly with an error. If
      * there is an error this method will call {@link #onDataSetupCompleteError}.
      */
-    private void onDataSetupComplete(ApnContext apnContext, boolean success, int cause,
+    protected void onDataSetupComplete(ApnContext apnContext, boolean success, int cause,
                                      @RequestNetworkType int requestType) {
         int apnType = ApnSetting.getApnTypesBitmaskFromString(apnContext.getApnType());
         List<Message> messageList = mRequestNetworkCompletionMsgs.get(apnType);
@@ -2846,7 +2856,8 @@
                         if (port == -1) {
                             port = 8080;
                         }
-                        ProxyInfo proxy = new ProxyInfo(apn.getProxyAddressAsString(), port, null);
+                        ProxyInfo proxy = ProxyInfo.buildDirectProxy(
+                                apn.getProxyAddressAsString(), port);
                         dataConnection.setLinkPropertiesHttpProxy(proxy);
                     } catch (NumberFormatException e) {
                         loge("onDataSetupComplete: NumberFormatException making ProxyProperties ("
@@ -2855,24 +2866,12 @@
                 }
 
                 // everything is setup
-                if (TextUtils.equals(apnContext.getApnType(), PhoneConstants.APN_TYPE_DEFAULT)) {
-                    try {
-                        SystemProperties.set(PUPPET_MASTER_RADIO_STRESS_TEST, "true");
-                    } catch (RuntimeException ex) {
-                        log("Failed to set PUPPET_MASTER_RADIO_STRESS_TEST to true");
-                    }
-                    if (mCanSetPreferApn && mPreferredApn == null) {
-                        if (DBG) log("onDataSetupComplete: PREFERRED APN is null");
-                        mPreferredApn = apn;
-                        if (mPreferredApn != null) {
-                            setPreferredApn(mPreferredApn.getId());
-                        }
-                    }
-                } else {
-                    try {
-                        SystemProperties.set(PUPPET_MASTER_RADIO_STRESS_TEST, "false");
-                    } catch (RuntimeException ex) {
-                        log("Failed to set PUPPET_MASTER_RADIO_STRESS_TEST to false");
+                if (TextUtils.equals(apnContext.getApnType(), PhoneConstants.APN_TYPE_DEFAULT)
+                        && mCanSetPreferApn && mPreferredApn == null) {
+                    if (DBG) log("onDataSetupComplete: PREFERRED APN is null");
+                    mPreferredApn = apn;
+                    if (mPreferredApn != null) {
+                        setPreferredApn(mPreferredApn.getId());
                     }
                 }
 
@@ -2882,7 +2881,8 @@
                 checkDataRoamingStatus(false);
 
                 boolean isProvApn = apnContext.isProvisioningApn();
-                final ConnectivityManager cm = ConnectivityManager.from(mPhone.getContext());
+                final ConnectivityManager cm = (ConnectivityManager) mPhone.getContext()
+                        .getSystemService(Context.CONNECTIVITY_SERVICE);
                 if (mProvisionBroadcastReceiver != null) {
                     mPhone.getContext().unregisterReceiver(mProvisionBroadcastReceiver);
                     mProvisionBroadcastReceiver = null;
@@ -2924,7 +2924,7 @@
                 if (DBG) {
                     log("onDataSetupComplete: SETUP complete type=" + apnContext.getApnType());
                 }
-                if (Build.IS_DEBUGGABLE) {
+                if (TelephonyUtils.IS_DEBUGGABLE) {
                     // adb shell setprop persist.radio.test.pco [pco_val]
                     String radioTestProperty = "persist.radio.test.pco";
                     int pcoVal = SystemProperties.getInt(radioTestProperty, -1);
@@ -2933,11 +2933,13 @@
                         final byte[] value = new byte[1];
                         value[0] = (byte) pcoVal;
                         final Intent intent =
-                                new Intent(TelephonyIntents.ACTION_CARRIER_SIGNAL_PCO_VALUE);
-                        intent.putExtra(TelephonyIntents.EXTRA_APN_TYPE_KEY, "default");
-                        intent.putExtra(TelephonyIntents.EXTRA_APN_PROTO_KEY, "IPV4V6");
-                        intent.putExtra(TelephonyIntents.EXTRA_PCO_ID_KEY, 0xFF00);
-                        intent.putExtra(TelephonyIntents.EXTRA_PCO_VALUE_KEY, value);
+                                new Intent(TelephonyManager.ACTION_CARRIER_SIGNAL_PCO_VALUE);
+                        intent.putExtra(TelephonyManager.EXTRA_APN_TYPE, "default");
+                        intent.putExtra(TelephonyManager.EXTRA_APN_TYPE_INT, TYPE_DEFAULT);
+                        intent.putExtra(TelephonyManager.EXTRA_APN_PROTOCOL, "IPV4V6");
+                        intent.putExtra(TelephonyManager.EXTRA_APN_PROTOCOL_INT, PROTOCOL_IPV4V6);
+                        intent.putExtra(TelephonyManager.EXTRA_PCO_ID, 0xFF00);
+                        intent.putExtra(TelephonyManager.EXTRA_PCO_VALUE, value);
                         mPhone.getCarrierSignalAgent().notifyCarrierSignalReceivers(intent);
                     }
                 }
@@ -2955,14 +2957,16 @@
                         cause, cid, mTelephonyManager.getNetworkType());
             }
             ApnSetting apn = apnContext.getApnSetting();
-            mPhone.notifyPreciseDataConnectionFailed(apnContext.getApnType(),
+            mPhone.notifyDataConnectionFailed(apnContext.getApnType(),
                     apn != null ? apn.getApnName() : null, cause);
 
             // Compose broadcast intent send to the specific carrier signaling receivers
-            Intent intent = new Intent(TelephonyIntents
+            Intent intent = new Intent(TelephonyManager
                     .ACTION_CARRIER_SIGNAL_REQUEST_NETWORK_FAILED);
-            intent.putExtra(TelephonyIntents.EXTRA_ERROR_CODE_KEY, cause);
-            intent.putExtra(TelephonyIntents.EXTRA_APN_TYPE_KEY, apnContext.getApnType());
+            intent.putExtra(TelephonyManager.EXTRA_ERROR_CODE, cause);
+            intent.putExtra(TelephonyManager.EXTRA_APN_TYPE, apnContext.getApnType());
+            intent.putExtra(TelephonyManager.EXTRA_APN_TYPE_INT,
+                    ApnSetting.getApnTypesBitmaskFromString(apnContext.getApnType()));
             mPhone.getCarrierSignalAgent().notifyCarrierSignalReceivers(intent);
 
             if (DataFailCause.isRadioRestartFailure(mPhone.getContext(), cause, mPhone.getSubId())
@@ -2987,7 +2991,7 @@
      * beginning if the list is empty. Between each SETUP request there will
      * be a delay defined by {@link #getApnDelay()}.
      */
-    private void onDataSetupCompleteError(ApnContext apnContext,
+    protected void onDataSetupCompleteError(ApnContext apnContext,
                                           @RequestNetworkType int requestType) {
         long delay = apnContext.getDelayForNextApn(mFailFast);
 
@@ -2999,7 +3003,7 @@
             // Wait a bit before trying the next APN, so that
             // we're not tying up the RIL command channel
 
-            startAlarmForReconnect(delay, apnContext);
+            startReconnect(delay, apnContext);
         } else {
             // If we are not going to retry any APN, set this APN context to failed state.
             // This would be the final state of a data connection.
@@ -3016,17 +3020,19 @@
      *
      * @param status One of {@code NetworkAgent.VALID_NETWORK} or
      * {@code NetworkAgent.INVALID_NETWORK}.
+     * @param cid context id {@code cid}
      * @param redirectUrl If the Internet probe was redirected, this
      * is the destination it was redirected to, otherwise {@code null}
      */
-    private void onNetworkStatusChanged(int status, String redirectUrl) {
+    private void onNetworkStatusChanged(int status, int cid, String redirectUrl) {
         if (!TextUtils.isEmpty(redirectUrl)) {
-            Intent intent = new Intent(TelephonyIntents.ACTION_CARRIER_SIGNAL_REDIRECTED);
-            intent.putExtra(TelephonyIntents.EXTRA_REDIRECTION_URL_KEY, redirectUrl);
+            Intent intent = new Intent(TelephonyManager.ACTION_CARRIER_SIGNAL_REDIRECTED);
+            intent.putExtra(TelephonyManager.EXTRA_REDIRECTION_URL, redirectUrl);
             mPhone.getCarrierSignalAgent().notifyCarrierSignalReceivers(intent);
             log("Notify carrier signal receivers with redirectUrl: " + redirectUrl);
         } else {
-            final boolean isValid = status == NetworkAgent.VALID_NETWORK;
+            final boolean isValid = status == NetworkAgent.VALIDATION_STATUS_VALID;
+            final DataConnection dc = getDataConnectionByContextId(cid);
             if (!mDsRecoveryHandler.isRecoveryOnBadNetworkEnabled()) {
                 if (DBG) log("Skip data stall recovery on network status change with in threshold");
                 return;
@@ -3035,7 +3041,9 @@
                 if (DBG) log("Skip data stall recovery on non WWAN");
                 return;
             }
-            mDsRecoveryHandler.processNetworkStatusChanged(isValid);
+            if (dc != null && dc.isValidationRequired()) {
+                mDsRecoveryHandler.processNetworkStatusChanged(isValid);
+            }
         }
     }
 
@@ -3079,11 +3087,6 @@
         }
         // If APN is still enabled, try to bring it back up automatically
         if (mAttached.get() && apnContext.isReady() && retryAfterDisconnected(apnContext)) {
-            try {
-                SystemProperties.set(PUPPET_MASTER_RADIO_STRESS_TEST, "false");
-            } catch (RuntimeException ex) {
-                log("Failed to set PUPPET_MASTER_RADIO_STRESS_TEST to false");
-            }
             // Wait a bit before trying the next APN, so that
             // we're not tying up the RIL command channel.
             // This also helps in any external dependency to turn off the context.
@@ -3092,7 +3095,7 @@
             if (delay > 0) {
                 // Data connection is in IDLE state, so when we reconnect later, we'll rebuild
                 // the waiting APN list, which will also reset/reconfigure the retry manager.
-                startAlarmForReconnect(delay, apnContext);
+                startReconnect(delay, apnContext);
             }
         } else {
             boolean restartRadioAfterProvisioning = mPhone.getContext().getResources().getBoolean(
@@ -3131,18 +3134,18 @@
             if (DBG) log("onVoiceCallStarted stop polling");
             stopNetStatPoll();
             stopDataStallAlarm();
-            mPhone.notifyDataConnection();
+            mPhone.notifyAllActiveDataConnections();
         }
     }
 
-    private void onVoiceCallEnded() {
+    protected void onVoiceCallEnded() {
         if (DBG) log("onVoiceCallEnded");
         mInVoiceCall = false;
         if (isConnected()) {
             if (!mPhone.getServiceStateTracker().isConcurrentVoiceAndDataAllowed()) {
                 startNetStatPoll();
                 startDataStallAlarm(DATA_STALL_NOT_SUSPECTED);
-                mPhone.notifyDataConnection();
+                mPhone.notifyAllActiveDataConnections();
             } else {
                 // clean slate after call end.
                 resetPollStats();
@@ -3152,7 +3155,7 @@
         setupDataOnAllConnectableApns(Phone.REASON_VOICE_CALL_ENDED, RetryFailures.ALWAYS);
     }
 
-    private boolean isConnected() {
+    protected boolean isConnected() {
         for (ApnContext apnContext : mApnContexts.values()) {
             if (apnContext.getState() == DctConstants.State.CONNECTED) {
                 // At least one context is connected, return true
@@ -3174,7 +3177,7 @@
         return true;
     }
 
-    private void setDataProfilesAsNeeded() {
+    protected void setDataProfilesAsNeeded() {
         if (DBG) log("setDataProfilesAsNeeded");
 
         ArrayList<DataProfile> dataProfileList = new ArrayList<>();
@@ -3201,10 +3204,9 @@
      * Based on the sim operator numeric, create a list for all possible
      * Data Connections and setup the preferredApn.
      */
-    private void createAllApnList() {
+    protected void createAllApnList() {
         mAllApnSettings.clear();
-        IccRecords r = mIccRecords.get();
-        String operator = (r != null) ? r.getOperatorNumeric() : "";
+        String operator = mPhone.getOperatorNumeric();
 
         // ORDER BY Telephony.Carriers._ID ("_id")
         Cursor cursor = mPhone.getContext().getContentResolver().query(
@@ -3235,8 +3237,8 @@
             mApnSettingsInitializationLog.log("no APN found for carrier, operator: "
                     + operator);
             mPreferredApn = null;
-            // TODO: What is the right behavior?
-            //notifyNoData(DataConnection.FailCause.MISSING_UNKNOWN_APN);
+            // Notify that there are no APN Settings,
+            mPhone.notifyDataConnectionFailed(null, null, DataFailCause.MISSING_UNKNOWN_APN);
         } else {
             mPreferredApn = getPreferredApn();
             if (mPreferredApn != null && !mPreferredApn.getOperatorNumeric().equals(operator)) {
@@ -3347,8 +3349,7 @@
             }
         }
 
-        IccRecords r = mIccRecords.get();
-        String operator = (r != null) ? r.getOperatorNumeric() : "";
+        String operator = mPhone.getOperatorNumeric();
 
         // This is a workaround for a bug (7305641) where we don't failover to other
         // suitable APNs if our preferred APN fails.  On prepaid ATT sims we need to
@@ -3357,8 +3358,8 @@
         // to say they don't want to use preferred at all.
         boolean usePreferred = true;
         try {
-            usePreferred = ! mPhone.getContext().getResources().getBoolean(com.android.
-                    internal.R.bool.config_dontPreferApn);
+            usePreferred = !mPhone.getContext().getResources().getBoolean(com.android
+                    .internal.R.bool.config_dontPreferApn);
         } catch (Resources.NotFoundException e) {
             if (DBG) log("buildWaitingApns: usePreferred NotFoundException set to true");
             usePreferred = true;
@@ -3370,8 +3371,7 @@
             log("buildWaitingApns: usePreferred=" + usePreferred
                     + " canSetPreferApn=" + mCanSetPreferApn
                     + " mPreferredApn=" + mPreferredApn
-                    + " operator=" + operator + " radioTech=" + radioTech
-                    + " IccRecords r=" + r);
+                    + " operator=" + operator + " radioTech=" + radioTech);
         }
 
         if (usePreferred && mCanSetPreferApn && mPreferredApn != null &&
@@ -3387,16 +3387,11 @@
                     apnList = sortApnListByPreferred(apnList);
                     if (DBG) log("buildWaitingApns: X added preferred apnList=" + apnList);
                     return apnList;
-                } else {
-                    if (DBG) log("buildWaitingApns: no preferred APN");
-                    setPreferredApn(-1);
-                    mPreferredApn = null;
                 }
-            } else {
-                if (DBG) log("buildWaitingApns: no preferred APN");
-                setPreferredApn(-1);
-                mPreferredApn = null;
             }
+            if (DBG) log("buildWaitingApns: no preferred APN");
+            setPreferredApn(-1);
+            mPreferredApn = null;
         }
 
         if (DBG) log("buildWaitingApns: mAllApnSettings=" + mAllApnSettings);
@@ -3488,7 +3483,9 @@
         }
     }
 
+    @Nullable
     ApnSetting getPreferredApn() {
+        //Only call this method from main thread
         if (mAllApnSettings == null || mAllApnSettings.isEmpty()) {
             log("getPreferredApn: mAllApnSettings is empty");
             return null;
@@ -3544,17 +3541,6 @@
         int generation;
         int requestType;
         switch (msg.what) {
-            case DctConstants.EVENT_RECORDS_LOADED:
-                // If onRecordsLoadedOrSubIdChanged() is not called here, it should be called on
-                // onSubscriptionsChanged() when a valid subId is available.
-                int subId = mPhone.getSubId();
-                if (SubscriptionManager.isValidSubscriptionId(subId)) {
-                    onRecordsLoadedOrSubIdChanged();
-                } else {
-                    log("Ignoring EVENT_RECORDS_LOADED as subId is not valid: " + subId);
-                }
-                break;
-
             case DctConstants.EVENT_DATA_CONNECTION_DETACHED:
                 onDataConnectionDetached();
                 break;
@@ -3606,7 +3592,7 @@
                         trySetupData(apnContext, REQUEST_TYPE_NORMAL);
                     } else {
                         loge("**** Default ApnContext not found ****");
-                        if (Build.IS_DEBUGGABLE) {
+                        if (TelephonyUtils.IS_DEBUGGABLE) {
                             throw new RuntimeException("Default ApnContext not found");
                         }
                     }
@@ -3684,8 +3670,9 @@
 
             case DctConstants.EVENT_NETWORK_STATUS_CHANGED:
                 int status = msg.arg1;
+                int cid = msg.arg2;
                 String url = (String) msg.obj;
-                onNetworkStatusChanged(status, url);
+                onNetworkStatusChanged(status, cid, url);
                 break;
 
             case DctConstants.EVENT_RADIO_AVAILABLE:
@@ -3855,10 +3842,6 @@
                         isProvApn ? DctConstants.ENABLED : DctConstants.DISABLED);
                 break;
             }
-            case DctConstants.EVENT_ICC_CHANGED: {
-                onUpdateIcc();
-                break;
-            }
             case DctConstants.EVENT_RESTART_RADIO: {
                 restartRadio();
                 break;
@@ -3876,7 +3859,8 @@
                 break;
             }
             case DctConstants.EVENT_DATA_RECONNECT:
-                onDataReconnect(msg.getData());
+                if (DBG) log("EVENT_DATA_RECONNECT: subId=" + msg.arg1);
+                onDataReconnect((ApnContext) msg.obj, msg.arg1);
                 break;
             case DctConstants.EVENT_DATA_SERVICE_BINDING_CHANGED:
                 onDataServiceBindingChanged((Boolean) ((AsyncResult) msg.obj).result);
@@ -3893,19 +3877,19 @@
             case DctConstants.EVENT_DATA_ENABLED_OVERRIDE_RULES_CHANGED:
                 onDataEnabledOverrideRulesChanged();
                 break;
-            case DctConstants.EVENT_SERVICE_STATE_CHANGED:
-                reevaluateUnmeteredConnections();
-                break;
-            case DctConstants.EVENT_5G_TIMER_HYSTERESIS:
-                reevaluateUnmeteredConnections();
-                mHysteresis = false;
-                break;
-            case DctConstants.EVENT_5G_TIMER_WATCHDOG:
+            case DctConstants.EVENT_NR_TIMER_WATCHDOG:
                 mWatchdog = false;
                 reevaluateUnmeteredConnections();
                 break;
-            case DctConstants.EVENT_UPDATE_CARRIER_CONFIGS:
-                updateLinkBandwidths((String[]) msg.obj, msg.arg1 == 1);
+            case DctConstants.EVENT_TELEPHONY_DISPLAY_INFO_CHANGED:
+                reevaluateUnmeteredConnections();
+                break;
+            case DctConstants.EVENT_CARRIER_CONFIG_CHANGED:
+                onCarrierConfigChanged();
+                break;
+            case DctConstants.EVENT_SIM_STATE_UPDATED:
+                int simState = msg.arg1;
+                onSimStateUpdated(simState);
                 break;
             default:
                 Rlog.e("DcTracker", "Unhandled event=" + msg);
@@ -3932,7 +3916,7 @@
 
     private int getCellLocationId() {
         int cid = -1;
-        CellLocation loc = mPhone.getCellLocation();
+        CellLocation loc = mPhone.getCellIdentity().asCellLocation();
 
         if (loc != null) {
             if (loc instanceof GsmCellLocation) {
@@ -3944,78 +3928,46 @@
         return cid;
     }
 
-    private IccRecords getUiccRecords(int appFamily) {
-        return mUiccController.getIccRecords(mPhone.getPhoneId(), appFamily);
-    }
-
-
-    private void onUpdateIcc() {
-        if (mUiccController == null ) {
-            return;
-        }
-
-        IccRecords newIccRecords = getUiccRecords(UiccController.APP_FAM_3GPP);
-
-        IccRecords r = mIccRecords.get();
-        if (r != newIccRecords) {
-            if (r != null) {
-                log("Removing stale icc objects.");
-                r.unregisterForRecordsLoaded(this);
-                mIccRecords.set(null);
-            }
-            if (newIccRecords != null) {
-                if (SubscriptionManager.isValidSubscriptionId(mPhone.getSubId())) {
-                    log("New records found.");
-                    mIccRecords.set(newIccRecords);
-                    newIccRecords.registerForRecordsLoaded(
-                            this, DctConstants.EVENT_RECORDS_LOADED, null);
-                }
-            } else {
-                onSimNotReady();
-            }
-        }
-    }
-
     /**
      * Update link bandwidth estimate default values from carrier config.
      * @param bandwidths String array of "RAT:upstream,downstream" for each RAT
      * @param useLte For NR NSA, whether to use LTE value for upstream or not
      */
     private void updateLinkBandwidths(String[] bandwidths, boolean useLte) {
-        synchronized (mBandwidths) {
-            mBandwidths.clear();
-            for (String config : bandwidths) {
-                int downstream = 14;
-                int upstream = 14;
-                String[] kv = config.split(":");
-                if (kv.length == 2) {
-                    String[] split = kv[1].split(",");
-                    if (split.length == 2) {
-                        try {
-                            downstream = Integer.parseInt(split[0]);
-                            upstream = Integer.parseInt(split[1]);
-                        } catch (NumberFormatException ignored) {
-                        }
-                    }
-                    mBandwidths.put(kv[0], new Pair<>(downstream, upstream));
-                }
-            }
-            if (useLte) {
-                Pair<Integer, Integer> ltePair = mBandwidths.get(DctConstants.RAT_NAME_LTE);
-                if (ltePair != null) {
-                    if (mBandwidths.containsKey(DctConstants.RAT_NAME_NR_NSA)) {
-                        mBandwidths.put(DctConstants.RAT_NAME_NR_NSA, new Pair<>(
-                                mBandwidths.get(DctConstants.RAT_NAME_NR_NSA).first,
-                                ltePair.second));
-                    }
-                    if (mBandwidths.containsKey(DctConstants.RAT_NAME_NR_NSA_MMWAVE)) {
-                        mBandwidths.put(DctConstants.RAT_NAME_NR_NSA_MMWAVE, new Pair<>(
-                                mBandwidths.get(DctConstants.RAT_NAME_NR_NSA_MMWAVE).first,
-                                ltePair.second));
+        ConcurrentHashMap<String, Pair<Integer, Integer>> temp = new ConcurrentHashMap<>();
+        for (String config : bandwidths) {
+            int downstream = 14;
+            int upstream = 14;
+            String[] kv = config.split(":");
+            if (kv.length == 2) {
+                String[] split = kv[1].split(",");
+                if (split.length == 2) {
+                    try {
+                        downstream = Integer.parseInt(split[0]);
+                        upstream = Integer.parseInt(split[1]);
+                    } catch (NumberFormatException ignored) {
                     }
                 }
+                temp.put(kv[0], new Pair<>(downstream, upstream));
             }
         }
+        if (useLte) {
+            Pair<Integer, Integer> ltePair = temp.get(DctConstants.RAT_NAME_LTE);
+            if (ltePair != null) {
+                if (temp.containsKey(DctConstants.RAT_NAME_NR_NSA)) {
+                    temp.put(DctConstants.RAT_NAME_NR_NSA, new Pair<>(
+                            temp.get(DctConstants.RAT_NAME_NR_NSA).first, ltePair.second));
+                }
+                if (temp.containsKey(DctConstants.RAT_NAME_NR_NSA_MMWAVE)) {
+                    temp.put(DctConstants.RAT_NAME_NR_NSA_MMWAVE, new Pair<>(
+                            temp.get(DctConstants.RAT_NAME_NR_NSA_MMWAVE).first, ltePair.second));
+                }
+            }
+        }
+        mBandwidths = temp;
+        for (DataConnection dc : mDataConnections.values()) {
+            dc.sendMessage(DataConnection.EVENT_CARRIER_CONFIG_LINK_BANDWIDTHS_CHANGED);
+        }
     }
 
     /**
@@ -4023,25 +3975,10 @@
      * @param ratName RAT name from ServiceState#rilRadioTechnologyToString.
      * @return pair of downstream/upstream values (kbps), or null if the config is not defined.
      */
-    public Pair<Integer, Integer> getLinkBandwidths(String ratName) {
+    public Pair<Integer, Integer> getLinkBandwidthsFromCarrierConfig(String ratName) {
         return mBandwidths.get(ratName);
     }
 
-    /**
-     * 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!");
-        onUpdateIcc();
-
-        mAutoAttachEnabled.set(false);
-
-        mPhone.updateCurrentCarrierInProvider();
-    }
-
     @VisibleForTesting
     public boolean shouldAutoAttach() {
         if (mAutoAttachEnabled.get()) return true;
@@ -4052,7 +3989,7 @@
         if (phoneSwitcher == null || serviceState == null) return false;
 
         // If voice is also not in service, don't auto attach.
-        if (serviceState.getVoiceRegState() != ServiceState.STATE_IN_SERVICE) return false;
+        if (serviceState.getState() != ServiceState.STATE_IN_SERVICE) return false;
 
         // If voice is on LTE or NR, don't auto attach as for LTE / NR data would be attached.
         if (serviceState.getVoiceNetworkType() == NETWORK_TYPE_LTE
@@ -4117,42 +4054,23 @@
     }
 
     private void reevaluateUnmeteredConnections() {
-        if (isNetworkTypeUnmetered(NETWORK_TYPE_NR) || isFrequencyRangeUnmetered()) {
-            if (DBG) log("NR NSA is unmetered");
-            if (mPhone.getServiceState().getNrState()
-                    == NetworkRegistrationInfo.NR_STATE_CONNECTED) {
-                if (!m5GWasConnected) { // 4G -> 5G
-                    stopHysteresisAlarm();
-                    setDataConnectionUnmetered(true);
-                }
-                if (!mWatchdog) {
-                    startWatchdogAlarm();
-                }
-                m5GWasConnected = true;
-            } else {
-                if (m5GWasConnected) { // 5G -> 4G
-                    if (!mHysteresis && !startHysteresisAlarm()) {
-                        // hysteresis is not active but carrier does not support hysteresis
-                        stopWatchdogAlarm();
-                        setDataConnectionUnmetered(isNetworkTypeUnmetered(
-                                mTelephonyManager.getNetworkType(mPhone.getSubId())));
-                    }
-                    m5GWasConnected = false;
-                } else { // 4G -> 4G
-                    if (!hasMessages(DctConstants.EVENT_5G_TIMER_HYSTERESIS)) {
-                        stopWatchdogAlarm();
-                        setDataConnectionUnmetered(isNetworkTypeUnmetered(
-                                mTelephonyManager.getNetworkType(mPhone.getSubId())));
-                    }
-                    // do nothing if waiting for hysteresis alarm to go off
-                }
+        log("reevaluateUnmeteredConnections");
+        int rat = mPhone.getDisplayInfoController().getTelephonyDisplayInfo().getNetworkType();
+        int override = mPhone.getDisplayInfoController().getTelephonyDisplayInfo()
+                .getOverrideNetworkType();
+        boolean nrPlanUnmetered = isNetworkTypeUnmetered(NETWORK_TYPE_NR) && (rat == NETWORK_TYPE_NR
+                || override == TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_NSA
+                || override == TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_NSA_MMWAVE);
+        if ((nrPlanUnmetered || isNrNsaFrequencyRangeUnmetered() || isNrSaFrequencyRangeUnmetered())
+                && !mPhone.getServiceState().getRoaming() || mRoamingUnmetered) {
+            if (DBG) log("NR is unmetered");
+            setDataConnectionUnmetered(true);
+            if (!mWatchdog) {
+                startWatchdogAlarm();
             }
         } else {
             stopWatchdogAlarm();
-            stopHysteresisAlarm();
-            setDataConnectionUnmetered(isNetworkTypeUnmetered(
-                    mTelephonyManager.getNetworkType(mPhone.getSubId())));
-            m5GWasConnected = false;
+            setDataConnectionUnmetered(isNetworkTypeUnmetered(rat));
         }
     }
 
@@ -4168,22 +4086,24 @@
             return false;
         }
 
-        long bitmask = ServiceState.getBitmaskForTech(networkType);
         boolean isGeneralUnmetered = true;
+        Set<Integer> allNetworkTypes = Arrays.stream(TelephonyManager.getAllNetworkTypes())
+                .boxed().collect(Collectors.toSet());
         for (SubscriptionPlan plan : mSubscriptionPlans) {
-            // check plan applies to given network type
-            if ((plan.getNetworkTypesBitMask() & bitmask) == bitmask) {
-                // check plan is general or specific
-                if (plan.getNetworkTypes() == null) {
-                    if (!isPlanUnmetered(plan)) {
-                        // metered takes precedence over unmetered for safety
-                        isGeneralUnmetered = false;
-                    }
-                } else {
-                    // ensure network type unknown returns general value
-                    if (networkType != TelephonyManager.NETWORK_TYPE_UNKNOWN) {
-                        // there is only 1 specific plan per network type, so return value if found
-                        return isPlanUnmetered(plan);
+            // check plan is general (applies to all network types) or specific
+            if (Arrays.stream(plan.getNetworkTypes()).boxed().collect(Collectors.toSet())
+                    .containsAll(allNetworkTypes)) {
+                if (!isPlanUnmetered(plan)) {
+                    // metered takes precedence over unmetered for safety
+                    isGeneralUnmetered = false;
+                }
+            } else {
+                // check plan applies to given network type
+                if (networkType != TelephonyManager.NETWORK_TYPE_UNKNOWN) {
+                    for (int planNetworkType : plan.getNetworkTypes()) {
+                        if (planNetworkType == networkType) {
+                            return isPlanUnmetered(plan);
+                        }
                     }
                 }
             }
@@ -4197,21 +4117,37 @@
                 || plan.getDataLimitBehavior() == SubscriptionPlan.LIMIT_BEHAVIOR_THROTTLED);
     }
 
-    private boolean isFrequencyRangeUnmetered() {
-        boolean nrConnected = mPhone.getServiceState().getNrState()
-                == NetworkRegistrationInfo.NR_STATE_CONNECTED;
-        if (mMmwaveUnmetered || mSub6Unmetered) {
+    private boolean isNrNsaFrequencyRangeUnmetered() {
+        int override = mPhone.getDisplayInfoController().getTelephonyDisplayInfo()
+                .getOverrideNetworkType();
+        if (mNrNsaMmwaveUnmetered || mNrNsaSub6Unmetered) {
+            return (mNrNsaMmwaveUnmetered
+                    && override == TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_NSA_MMWAVE)
+                    || (mNrNsaSub6Unmetered
+                    && override == TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_NSA);
+        } else {
+            return mNrNsaAllUnmetered
+                    && (override == TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_NSA_MMWAVE
+                    || override == TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_NSA);
+        }
+    }
+
+    private boolean isNrSaFrequencyRangeUnmetered() {
+        if (ServiceState.rilRadioTechnologyToNetworkType(getDataRat()) != NETWORK_TYPE_NR) {
+            return false;
+        }
+        if (mNrSaMmwaveUnmetered || mNrSaSub6Unmetered) {
             int frequencyRange = mPhone.getServiceState().getNrFrequencyRange();
             boolean mmwave = frequencyRange == ServiceState.FREQUENCY_RANGE_MMWAVE;
             // frequency range LOW, MID, or HIGH
             boolean sub6 = frequencyRange != ServiceState.FREQUENCY_RANGE_UNKNOWN && !mmwave;
-            return (mMmwaveUnmetered && mmwave || mSub6Unmetered && sub6) && nrConnected;
+            return mNrSaMmwaveUnmetered && mmwave || mNrSaSub6Unmetered && sub6;
         } else {
-            return mAllUnmetered && nrConnected;
+            return mNrSaAllUnmetered;
         }
     }
 
-    private void log(String s) {
+    protected void log(String s) {
         Rlog.d(mLogTag, s);
     }
 
@@ -4219,6 +4155,23 @@
         Rlog.e(mLogTag, s);
     }
 
+    private void logSortedApnContexts() {
+        if (VDBG) {
+            log("initApnContexts: X mApnContexts=" + mApnContexts);
+
+            StringBuilder sb = new StringBuilder();
+            sb.append("sorted apncontexts -> [");
+            for (ApnContext apnContext : mPrioritySortedApnContexts) {
+                sb.append(apnContext);
+                sb.append(", ");
+
+                log("sorted list");
+            }
+            sb.append("]");
+            log(sb.toString());
+        }
+    }
+
     public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
         pw.println("DcTracker:");
         pw.println(" RADIO_TESTS=" + RADIO_TESTS);
@@ -4227,6 +4180,8 @@
         pw.flush();
         pw.println(" mRequestedApnType=" + mRequestedApnType);
         pw.println(" mPhone=" + mPhone.getPhoneName());
+        pw.println(" mConfigReady=" + mConfigReady);
+        pw.println(" mSimState=" + SubscriptionInfoUpdater.simStateString(mSimState));
         pw.println(" mActivity=" + mActivity);
         pw.println(" mState=" + mState);
         pw.println(" mTxPkts=" + mTxPkts);
@@ -4364,7 +4319,7 @@
      * PLMN name,APN name are not mandatory parameters
      */
     private void initEmergencyApnSetting() {
-        // Operator Numeric is not available when sim records are not loaded.
+        // Operator Numeric is not available when SIM is not ready.
         // Query Telephony.db with APN type as EPDN request does not
         // require APN name, plmn and all operators support same APN config.
         // DB will contain only one entry for Emergency APN
@@ -4387,6 +4342,9 @@
         mEmergencyApn = new ApnSetting.Builder()
                 .setEntryName("Emergency")
                 .setProtocol(ApnSetting.PROTOCOL_IPV4V6)
+                .setRoamingProtocol(ApnSetting.PROTOCOL_IPV4V6)
+                .setNetworkTypeBitmask((int)(TelephonyManager.NETWORK_TYPE_BITMASK_LTE
+                | TelephonyManager.NETWORK_TYPE_BITMASK_IWLAN))
                 .setApnName("sos")
                 .setApnTypeBitmask(ApnSetting.TYPE_EMERGENCY)
                 .build();
@@ -4414,8 +4372,7 @@
         }
     }
 
-    private boolean containsAllApns(ArrayList<ApnSetting> oldApnList,
-                                    ArrayList<ApnSetting> newApnList) {
+    private boolean containsAllApns(List<ApnSetting> oldApnList, List<ApnSetting> newApnList) {
         for (ApnSetting newApnSetting : newApnList) {
             boolean canHandle = false;
             for (ApnSetting oldApnSetting : oldApnList) {
@@ -4443,20 +4400,26 @@
                 return;
             }
             for (ApnContext apnContext : mApnContexts.values()) {
-                ArrayList<ApnSetting> currentWaitingApns = apnContext.getWaitingApns();
-                ArrayList<ApnSetting> waitingApns = buildWaitingApns(
-                        apnContext.getApnType(), getDataRat());
-                if (VDBG) log("new waitingApns:" + waitingApns);
-                if ((currentWaitingApns != null)
-                        && ((waitingApns.size() != currentWaitingApns.size())
-                        // Check if the existing waiting APN list can cover the newly built APN
-                        // list. If yes, then we don't need to tear down the existing data call.
-                        // TODO: We probably need to rebuild APN list when roaming status changes.
-                        || !containsAllApns(currentWaitingApns, waitingApns))) {
-                    if (VDBG) log("new waiting apn is different for " + apnContext);
+                boolean cleanupRequired = true;
+                if (!apnContext.isDisconnected()) {
+                    ArrayList<ApnSetting> waitingApns = buildWaitingApns(
+                            apnContext.getApnType(), getDataRat());
                     apnContext.setWaitingApns(waitingApns);
-                    if (!apnContext.isDisconnected()) {
-                        if (VDBG) log("cleanUpConnectionsOnUpdatedApns for " + apnContext);
+                    for (ApnSetting apnSetting : waitingApns) {
+                        if (apnSetting.equals(apnContext.getApnSetting(),
+                                mPhone.getServiceState().getDataRoamingFromRegistration())) {
+                            cleanupRequired = false;
+                            break;
+                        }
+                    }
+
+                    if (cleanupRequired) {
+                        if (DBG) {
+                            log("cleanUpConnectionsOnUpdatedApns: APN type "
+                                    + apnContext.getApnType() + " clean up is required. The new "
+                                    + "waiting APN list " + waitingApns + " does not cover "
+                                    + apnContext.getApnSetting());
+                        }
                         apnContext.setReason(reason);
                         cleanUpConnectionInternal(true, RELEASE_TYPE_DETACH, apnContext);
                     }
@@ -4480,13 +4443,13 @@
     /**
      * Polling stuff
      */
-    private void resetPollStats() {
+    protected void resetPollStats() {
         mTxPkts = -1;
         mRxPkts = -1;
         mNetStatPollPeriod = POLL_NETSTAT_MILLIS;
     }
 
-    private void startNetStatPoll() {
+    protected void startNetStatPoll() {
         if (getOverallState() == DctConstants.State.CONNECTED
                 && mNetStatPollEnabled == false) {
             if (DBG) {
@@ -4552,7 +4515,7 @@
                 }
                 setupDataOnConnectableApn(apnContext, Phone.REASON_DATA_ENABLED_OVERRIDE,
                         RetryFailures.ALWAYS);
-            } else if (shouldCleanUpConnection(apnContext, true)) {
+            } else if (shouldCleanUpConnection(apnContext, true, false)) {
                 apnContext.setReason(Phone.REASON_DATA_ENABLED_OVERRIDE);
                 cleanUpConnectionInternal(true, RELEASE_TYPE_DETACH, apnContext);
             }
@@ -4646,11 +4609,15 @@
             for (ApnContext apnContext : apnContextList) {
                 String apnType = apnContext.getApnType();
 
-                final Intent intent = new Intent(TelephonyIntents.ACTION_CARRIER_SIGNAL_PCO_VALUE);
-                intent.putExtra(TelephonyIntents.EXTRA_APN_TYPE_KEY, apnType);
-                intent.putExtra(TelephonyIntents.EXTRA_APN_PROTO_KEY, pcoData.bearerProto);
-                intent.putExtra(TelephonyIntents.EXTRA_PCO_ID_KEY, pcoData.pcoId);
-                intent.putExtra(TelephonyIntents.EXTRA_PCO_VALUE_KEY, pcoData.contents);
+                final Intent intent = new Intent(TelephonyManager.ACTION_CARRIER_SIGNAL_PCO_VALUE);
+                intent.putExtra(TelephonyManager.EXTRA_APN_TYPE, apnType);
+                intent.putExtra(TelephonyManager.EXTRA_APN_TYPE_INT,
+                        ApnSetting.getApnTypesBitmaskFromString(apnType));
+                intent.putExtra(TelephonyManager.EXTRA_APN_PROTOCOL, pcoData.bearerProto);
+                intent.putExtra(TelephonyManager.EXTRA_APN_PROTOCOL_INT,
+                        ApnSetting.getProtocolIntFromString(pcoData.bearerProto));
+                intent.putExtra(TelephonyManager.EXTRA_PCO_ID, pcoData.pcoId);
+                intent.putExtra(TelephonyManager.EXTRA_PCO_VALUE, pcoData.contents);
                 mPhone.getCarrierSignalAgent().notifyCarrierSignalReceivers(intent);
             }
         }
@@ -4744,11 +4711,20 @@
                 return false;
             }
 
+            // Skip recovery if it can cause a call to drop
+            if (mInVoiceCall && getRecoveryAction() > RECOVERY_ACTION_CLEANUP) {
+                if (VDBG_STALL) log("skip data stall recovery as there is an active call");
+                return false;
+            }
+
             // Allow recovery if data is expected to work
             return mAttached.get() && isDataAllowed(null);
         }
 
         private void triggerRecovery() {
+            // Updating the recovery start time early to avoid race when
+            // the message is being processed in the Queue
+            mTimeLastRecoveryStartMs = SystemClock.elapsedRealtime();
             sendMessage(obtainMessage(DctConstants.EVENT_DO_RECOVERY));
         }
 
@@ -4798,7 +4774,6 @@
                             + recoveryAction);
                 }
                 mSentSinceLastRecv = 0;
-                mTimeLastRecoveryStartMs = SystemClock.elapsedRealtime();
             }
         }
 
@@ -4833,7 +4808,7 @@
         long sent, received;
 
         TxRxSum preTxRxSum = new TxRxSum(mDataStallTxRxSum);
-        mDataStallTxRxSum.updateTcpTxRxSum();
+        mDataStallTxRxSum.updateTotalTxRxSum();
 
         if (VDBG_STALL) {
             log("updateDataStallInfo: mDataStallTxRxSum=" + mDataStallTxRxSum +
@@ -4915,7 +4890,7 @@
         startDataStallAlarm(suspectedStall);
     }
 
-    private void startDataStallAlarm(boolean suspectedStall) {
+    protected void startDataStallAlarm(boolean suspectedStall) {
         int delayInMs;
 
         if (mDsRecoveryHandler.isNoRxDataStallDetectionEnabled()
@@ -4992,7 +4967,7 @@
         int delayInMs = Settings.Global.getInt(mResolver,
                                 Settings.Global.PROVISIONING_APN_ALARM_DELAY_IN_MS,
                                 PROVISIONING_APN_ALARM_DELAY_IN_MS_DEFAULT);
-        if (Build.IS_DEBUGGABLE) {
+        if (TelephonyUtils.IS_DEBUGGABLE) {
             // Allow debug code to use a system property to provide another value
             String delayInMsStrg = Integer.toString(delayInMs);
             delayInMsStrg = System.getProperty(DEBUG_PROV_APN_ALARM, delayInMsStrg);
@@ -5028,32 +5003,15 @@
     }
 
     /**
-     * 5G connection reevaluation alarms
+     * 5G connection reevaluation alarm
      */
-    private boolean startHysteresisAlarm() {
-        if (mHysteresisTimeSec > 0) {
-            // only create hysteresis alarm if CarrierConfig allows it
-            sendMessageDelayed(obtainMessage(DctConstants.EVENT_5G_TIMER_HYSTERESIS),
-                    mHysteresisTimeSec * 1000);
-            mHysteresis = true;
-            return true;
-        } else {
-            return false;
-        }
-    }
-
-    private void stopHysteresisAlarm() {
-        removeMessages(DctConstants.EVENT_5G_TIMER_HYSTERESIS);
-        mHysteresis = false;
-    }
-
     private void startWatchdogAlarm() {
-        sendMessageDelayed(obtainMessage(DctConstants.EVENT_5G_TIMER_WATCHDOG), mWatchdogTimeMs);
+        sendMessageDelayed(obtainMessage(DctConstants.EVENT_NR_TIMER_WATCHDOG), mWatchdogTimeMs);
         mWatchdog = true;
     }
 
     private void stopWatchdogAlarm() {
-        removeMessages(DctConstants.EVENT_5G_TIMER_WATCHDOG);
+        removeMessages(DctConstants.EVENT_NR_TIMER_WATCHDOG);
         mWatchdog = false;
     }
 
@@ -5081,8 +5039,8 @@
                 .setApn(apn.getApnName())
                 .setProtocolType(apn.getProtocol())
                 .setAuthType(apn.getAuthType())
-                .setUserName(apn.getUser())
-                .setPassword(apn.getPassword())
+                .setUserName(apn.getUser() == null ? "" : apn.getUser())
+                .setPassword(apn.getPassword() == null ? "" : apn.getPassword())
                 .setType(profileType)
                 .setMaxConnectionsTime(apn.getMaxConnsTime())
                 .setMaxConnections(apn.getMaxConns())
@@ -5099,9 +5057,16 @@
 
     private void onDataServiceBindingChanged(boolean bound) {
         if (bound) {
+            if (mDcc == null) {
+                mDcc = DcController.makeDcc(mPhone, this, mDataServiceManager,
+                        new Handler(mHandlerThread.getLooper()), mLogTagSuffix);
+            }
             mDcc.start();
         } else {
             mDcc.dispose();
+            // dispose sets the associated Handler object (StateMachine#mSmHandler) to null, so mDcc
+            // needs to be created again (simply calling start() on it after dispose will not work)
+            mDcc = null;
         }
         mDataServiceBound = bound;
     }
@@ -5124,7 +5089,7 @@
     }
 
     @RilRadioTechnology
-    private int getDataRat() {
+    protected int getDataRat() {
         ServiceState ss = mPhone.getServiceState();
         NetworkRegistrationInfo nrs = ss.getNetworkRegistrationInfo(
                 NetworkRegistrationInfo.DOMAIN_PS, mTransportType);
@@ -5144,4 +5109,56 @@
         }
         return ServiceState.RIL_RADIO_TECHNOLOGY_UNKNOWN;
     }
+
+    private void read5GConfiguration() {
+        if (DBG) log("read5GConfiguration");
+        String[] bandwidths = CarrierConfigManager.getDefaultConfig().getStringArray(
+                CarrierConfigManager.KEY_BANDWIDTH_STRING_ARRAY);
+        boolean useLte = false;
+        CarrierConfigManager configManager = (CarrierConfigManager) mPhone.getContext()
+                .getSystemService(Context.CARRIER_CONFIG_SERVICE);
+        if (configManager != null) {
+            PersistableBundle b = configManager.getConfigForSubId(mPhone.getSubId());
+            if (b != null) {
+                if (b.getStringArray(CarrierConfigManager.KEY_BANDWIDTH_STRING_ARRAY) != null) {
+                    bandwidths = b.getStringArray(CarrierConfigManager.KEY_BANDWIDTH_STRING_ARRAY);
+                }
+                useLte = b.getBoolean(CarrierConfigManager
+                        .KEY_BANDWIDTH_NR_NSA_USE_LTE_VALUE_FOR_UPSTREAM_BOOL);
+                mWatchdogTimeMs = b.getLong(CarrierConfigManager.KEY_5G_WATCHDOG_TIME_MS_LONG);
+                mNrNsaAllUnmetered = b.getBoolean(CarrierConfigManager.KEY_UNMETERED_NR_NSA_BOOL);
+                mNrNsaMmwaveUnmetered = b.getBoolean(
+                        CarrierConfigManager.KEY_UNMETERED_NR_NSA_MMWAVE_BOOL);
+                mNrNsaSub6Unmetered = b.getBoolean(
+                        CarrierConfigManager.KEY_UNMETERED_NR_NSA_SUB6_BOOL);
+                mNrSaAllUnmetered = b.getBoolean(CarrierConfigManager.KEY_UNMETERED_NR_SA_BOOL);
+                mNrSaMmwaveUnmetered = b.getBoolean(
+                        CarrierConfigManager.KEY_UNMETERED_NR_SA_MMWAVE_BOOL);
+                mNrSaSub6Unmetered = b.getBoolean(
+                        CarrierConfigManager.KEY_UNMETERED_NR_SA_SUB6_BOOL);
+                mRoamingUnmetered = b.getBoolean(
+                        CarrierConfigManager.KEY_UNMETERED_NR_NSA_WHEN_ROAMING_BOOL);
+            }
+        }
+        updateLinkBandwidths(bandwidths, useLte);
+    }
+
+    /**
+     * Register for physical link state (i.e. RRC state) changed event.
+     *
+     * @param h The handler
+     * @param what The event
+     */
+    public void registerForPhysicalLinkStateChanged(Handler h, int what) {
+        mDcc.registerForPhysicalLinkStateChanged(h, what);
+    }
+
+    /**
+     * Unregister from physical link state (i.e. RRC state) changed event.
+     *
+     * @param h The previously registered handler
+     */
+    public void unregisterForPhysicalLinkStateChanged(Handler h) {
+        mDcc.unregisterForPhysicalLinkStateChanged(h);
+    }
 }
diff --git a/src/java/com/android/internal/telephony/dataconnection/README.txt b/src/java/com/android/internal/telephony/dataconnection/README.txt
new file mode 100644
index 0000000..e613a00
--- /dev/null
+++ b/src/java/com/android/internal/telephony/dataconnection/README.txt
@@ -0,0 +1,71 @@
+This package contains classes used to manage a DataConnection.
+
+A criticial aspect of this class is that most objects in this
+package run on the same thread except DataConnectionTracker
+This makes processing efficient as it minimizes context
+switching and it eliminates issues with multi-threading.
+
+This can be done because all actions are either asynchronous
+or are known to be non-blocking and fast. At this time only
+DcTesterDeactivateAll takes specific advantage of this
+single threading knowledge by using Dcc#mDcListAll so be
+very careful when making changes that break this assumption.
+
+A related change was in DataConnectionAc I added code that
+checks to see if the caller is on a different thread. If
+it is then the AsyncChannel#sendMessageSynchronously is
+used. If the caller is on the same thread then a getter
+is used. This allows the DCAC to be used from any thread
+and was required to fix a bug when Dcc called
+PhoneBase#notifyDataConnection which calls DCT#getLinkProperties
+and DCT#getLinkCapabilities which call Dcc all on the same
+thread. Without this change there was a dead lock when
+sendMessageSynchronously blocks.
+
+
+== Testing ==
+
+The following are Intents that can be sent for testing pruproses on
+DEBUGGABLE builds (userdebug, eng)
+
+*) Causes bringUp and retry requests to fail for all DC's
+
+  adb shell am broadcast -a com.android.internal.telephony.dataconnection.action_fail_bringup --ei counter 2 --ei fail_cause -3
+
+*) Causes all DC's to get torn down, simulating a temporary network outage:
+
+  adb shell am broadcast -a com.android.internal.telephony.dataconnection.action_deactivate_all
+
+*) To simplify testing we also have detach and attach simulations below where {x} is gsm, cdma or sip
+
+  adb shell am broadcast -a com.android.internal.telephony.{x}.action_detached
+  adb shell am broadcast -a com.android.internal.telephony.{x}.action_attached
+
+
+== System properties for Testing ==
+
+On debuggable builds (userdebug, eng) you can change additional
+settings through system properties.  These properties can be set with
+"setprop" for the current boot, or added to local.prop to persist
+across boots.
+
+device# setprop key value
+
+device# echo "key=value" >> /data/local.prop
+device# chmod 644 /data/local.prop
+
+
+-- Retry configuration --
+
+You can replace the connection retry configuration.  For example, you
+could change it to perform 4 retries at 5 second intervals:
+
+device# setprop test.data_retry_config "5000,5000,5000"
+
+
+-- Roaming --
+
+You can force the telephony stack to always assume that it's roaming
+to verify higher-level framework functionality:
+
+device# setprop telephony.test.forceRoaming true
diff --git a/src/java/com/android/internal/telephony/dataconnection/TelephonyNetworkFactory.java b/src/java/com/android/internal/telephony/dataconnection/TelephonyNetworkFactory.java
index e49d8fb..eff4340 100644
--- a/src/java/com/android/internal/telephony/dataconnection/TelephonyNetworkFactory.java
+++ b/src/java/com/android/internal/telephony/dataconnection/TelephonyNetworkFactory.java
@@ -21,26 +21,27 @@
 import android.net.NetworkCapabilities;
 import android.net.NetworkFactory;
 import android.net.NetworkRequest;
-import android.net.StringNetworkSpecifier;
+import android.net.TelephonyNetworkSpecifier;
 import android.os.AsyncResult;
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.Looper;
 import android.os.Message;
 import android.telephony.AccessNetworkConstants;
-import android.telephony.Rlog;
+import android.telephony.Annotation.ApnType;
+import android.telephony.SubscriptionManager;
 import android.telephony.data.ApnSetting;
-import android.telephony.data.ApnSetting.ApnType;
 import android.util.LocalLog;
 
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.telephony.Phone;
 import com.android.internal.telephony.PhoneSwitcher;
 import com.android.internal.telephony.SubscriptionController;
-import com.android.internal.telephony.SubscriptionMonitor;
 import com.android.internal.telephony.dataconnection.DcTracker.ReleaseNetworkType;
 import com.android.internal.telephony.dataconnection.DcTracker.RequestNetworkType;
 import com.android.internal.telephony.dataconnection.TransportManager.HandoverParams;
 import com.android.internal.util.IndentingPrintWriter;
+import com.android.telephony.Rlog;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
@@ -59,8 +60,10 @@
 
     private static final int TELEPHONY_NETWORK_SCORE = 50;
 
-    private static final int EVENT_ACTIVE_PHONE_SWITCH              = 1;
-    private static final int EVENT_SUBSCRIPTION_CHANGED             = 2;
+    @VisibleForTesting
+    public static final int EVENT_ACTIVE_PHONE_SWITCH               = 1;
+    @VisibleForTesting
+    public static final int EVENT_SUBSCRIPTION_CHANGED              = 2;
     private static final int EVENT_NETWORK_REQUEST                  = 3;
     private static final int EVENT_NETWORK_RELEASE                  = 4;
     private static final int EVENT_DATA_HANDOVER_NEEDED             = 5;
@@ -68,7 +71,6 @@
 
     private final PhoneSwitcher mPhoneSwitcher;
     private final SubscriptionController mSubscriptionController;
-    private final SubscriptionMonitor mSubscriptionMonitor;
     private final LocalLog mLocalLog = new LocalLog(REQUEST_LOG_SIZE);
 
     // Key: network request. Value: the transport of DcTracker it applies to,
@@ -83,11 +85,11 @@
 
     private int mSubscriptionId;
 
-    private final Handler mInternalHandler;
+    @VisibleForTesting
+    public final Handler mInternalHandler;
 
 
-    public TelephonyNetworkFactory(SubscriptionMonitor subscriptionMonitor, Looper looper,
-                                   Phone phone) {
+    public TelephonyNetworkFactory(Looper looper, Phone phone) {
         super(looper, phone.getContext(), "TelephonyNetworkFactory[" + phone.getPhoneId()
                 + "]", null);
         mPhone = phone;
@@ -100,7 +102,6 @@
         setScoreFilter(TELEPHONY_NETWORK_SCORE);
 
         mPhoneSwitcher = PhoneSwitcher.getInstance();
-        mSubscriptionMonitor = subscriptionMonitor;
         LOG_TAG = "TelephonyNetworkFactory[" + mPhone.getPhoneId() + "]";
 
         mPhoneSwitcher.registerForActivePhoneSwitch(mInternalHandler, EVENT_ACTIVE_PHONE_SWITCH,
@@ -109,12 +110,20 @@
                 EVENT_DATA_HANDOVER_NEEDED);
 
         mSubscriptionId = INVALID_SUBSCRIPTION_ID;
-        mSubscriptionMonitor.registerForSubscriptionChanged(mPhone.getPhoneId(), mInternalHandler,
-                EVENT_SUBSCRIPTION_CHANGED, null);
+        SubscriptionManager.from(mPhone.getContext()).addOnSubscriptionsChangedListener(
+                mSubscriptionsChangedListener);
 
         register();
     }
 
+    private final SubscriptionManager.OnSubscriptionsChangedListener mSubscriptionsChangedListener =
+            new SubscriptionManager.OnSubscriptionsChangedListener() {
+                @Override
+                public void onSubscriptionsChanged() {
+                    mInternalHandler.sendEmptyMessage(EVENT_SUBSCRIPTION_CHANGED);
+                }
+            };
+
     private NetworkCapabilities makeNetworkFilter(SubscriptionController subscriptionController,
             int phoneId) {
         final int subscriptionId = subscriptionController.getSubIdUsingPhoneId(phoneId);
@@ -136,7 +145,8 @@
         nc.addCapability(NetworkCapabilities.NET_CAPABILITY_EIMS);
         nc.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED);
         nc.addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET);
-        nc.setNetworkSpecifier(new StringNetworkSpecifier(String.valueOf(subscriptionId)));
+        nc.setNetworkSpecifier(new TelephonyNetworkSpecifier.Builder()
+                .setSubscriptionId(subscriptionId).build());
         return nc;
     }
 
@@ -396,8 +406,12 @@
                     // If handover fails, we need to tear down the existing connection, so the
                     // new data connection can be re-established on the new transport. If we leave
                     // the existing data connection in current transport, then DCT and qualified
-                    // network service will be out of sync.
-                    : DcTracker.RELEASE_TYPE_NORMAL;
+                    // network service will be out of sync. Specifying release type to detach
+                    // the transport is moved to the other transport, but network request is still
+                    // there, connectivity service will not call unwanted to tear down the network.
+                    // We need explicitly tear down the data connection here so the new data
+                    // connection can be re-established on the other transport.
+                    : DcTracker.RELEASE_TYPE_DETACH;
             releaseNetworkInternal(networkRequest, releaseType, originTransport);
             mNetworkRequests.put(networkRequest, targetTransport);
         }
diff --git a/src/java/com/android/internal/telephony/dataconnection/TransportManager.java b/src/java/com/android/internal/telephony/dataconnection/TransportManager.java
index 5fb14af..f674e68 100644
--- a/src/java/com/android/internal/telephony/dataconnection/TransportManager.java
+++ b/src/java/com/android/internal/telephony/dataconnection/TransportManager.java
@@ -26,10 +26,9 @@
 import android.os.SystemProperties;
 import android.telephony.AccessNetworkConstants;
 import android.telephony.AccessNetworkConstants.AccessNetworkType;
+import android.telephony.Annotation.ApnType;
 import android.telephony.CarrierConfigManager;
-import android.telephony.Rlog;
 import android.telephony.data.ApnSetting;
-import android.telephony.data.ApnSetting.ApnType;
 import android.util.LocalLog;
 import android.util.SparseArray;
 import android.util.SparseIntArray;
@@ -38,8 +37,9 @@
 import com.android.internal.telephony.Phone;
 import com.android.internal.telephony.RIL;
 import com.android.internal.telephony.dataconnection.AccessNetworksManager.QualifiedNetworks;
-import com.android.internal.util.ArrayUtils;
+import com.android.internal.telephony.util.ArrayUtils;
 import com.android.internal.util.IndentingPrintWriter;
+import com.android.telephony.Rlog;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
@@ -60,7 +60,7 @@
  *
  * The device can operate in the following modes, which is stored in the system properties
  * ro.telephony.iwlan_operation_mode. If the system properties is missing, then it's tied to
- * IRadio version. For 1.4 or above, it's legacy mode. For 1.3 or below, it's
+ * IRadio version. For 1.4 or above, it's AP-assisted mdoe. For 1.3 or below, it's legacy mode.
  *
  * Legacy mode:
  *      Frameworks send all data requests to the default data service, which is the cellular data
@@ -75,13 +75,13 @@
  *      frameworks to bind.
  *
  *      Package name of data service:
- *          The resource overlay 'config_wwan_data_service_package' or,
+ *          The resource overlay 'config_wlan_data_service_package' or,
  *          the carrier config
  *          {@link CarrierConfigManager#KEY_CARRIER_DATA_SERVICE_WLAN_PACKAGE_OVERRIDE_STRING}.
  *          The carrier config takes precedence over the resource overlay if both exist.
  *
  *      Package name of network service
- *          The resource overlay 'config_wwan_network_service_package' or
+ *          The resource overlay 'config_wlan_network_service_package' or
  *          the carrier config
  *          {@link CarrierConfigManager#KEY_CARRIER_NETWORK_SERVICE_WLAN_PACKAGE_OVERRIDE_STRING}.
  *          The carrier config takes precedence over the resource overlay if both exist.
@@ -109,6 +109,8 @@
                 AccessNetworkConstants.TRANSPORT_TYPE_WWAN);
         ACCESS_NETWORK_TRANSPORT_TYPE_MAP.put(AccessNetworkType.CDMA2000,
                 AccessNetworkConstants.TRANSPORT_TYPE_WWAN);
+        ACCESS_NETWORK_TRANSPORT_TYPE_MAP.put(AccessNetworkType.NGRAN,
+                AccessNetworkConstants.TRANSPORT_TYPE_WWAN);
         ACCESS_NETWORK_TRANSPORT_TYPE_MAP.put(AccessNetworkType.IWLAN,
                 AccessNetworkConstants.TRANSPORT_TYPE_WLAN);
     }
@@ -129,8 +131,8 @@
     public @interface IwlanOperationMode {}
 
     /**
-     * IWLAN default mode. On device that has IRadio 1.3 or above, it means
-     * {@link #IWLAN_OPERATION_MODE_AP_ASSISTED}. On device that has IRadio 1.2 or below, it means
+     * IWLAN default mode. On device that has IRadio 1.4 or above, it means
+     * {@link #IWLAN_OPERATION_MODE_AP_ASSISTED}. On device that has IRadio 1.3 or below, it means
      * {@link #IWLAN_OPERATION_MODE_LEGACY}.
      */
     public static final String IWLAN_OPERATION_MODE_DEFAULT = "default";
@@ -297,7 +299,7 @@
     }
 
     private static boolean areNetworksValid(QualifiedNetworks networks) {
-        if (networks.qualifiedNetworks == null) {
+        if (networks.qualifiedNetworks == null || networks.qualifiedNetworks.length == 0) {
             return false;
         }
         for (int network : networks.qualifiedNetworks) {
diff --git a/src/java/com/android/internal/telephony/emergency/EmergencyNumberTracker.java b/src/java/com/android/internal/telephony/emergency/EmergencyNumberTracker.java
index a1a7aea..8cfde1f 100644
--- a/src/java/com/android/internal/telephony/emergency/EmergencyNumberTracker.java
+++ b/src/java/com/android/internal/telephony/emergency/EmergencyNumberTracker.java
@@ -21,13 +21,15 @@
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.os.AsyncResult;
+import android.os.Environment;
 import android.os.Handler;
 import android.os.Message;
+import android.os.ParcelFileDescriptor;
 import android.os.PersistableBundle;
 import android.os.SystemProperties;
 import android.telephony.CarrierConfigManager;
 import android.telephony.PhoneNumberUtils;
-import android.telephony.Rlog;
+import android.telephony.ServiceState;
 import android.telephony.TelephonyManager;
 import android.telephony.emergency.EmergencyNumber;
 import android.telephony.emergency.EmergencyNumber.EmergencyCallRouting;
@@ -35,9 +37,9 @@
 import android.text.TextUtils;
 import android.util.LocalLog;
 
-import com.android.i18n.phonenumbers.ShortNumberInfo;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.telephony.CommandsInterface;
+import com.android.internal.telephony.HalVersion;
 import com.android.internal.telephony.LocaleTracker;
 import com.android.internal.telephony.Phone;
 import com.android.internal.telephony.PhoneConstants;
@@ -48,16 +50,21 @@
 import com.android.internal.util.IndentingPrintWriter;
 import com.android.phone.ecc.nano.ProtobufEccData;
 import com.android.phone.ecc.nano.ProtobufEccData.EccInfo;
+import com.android.telephony.Rlog;
 
-import libcore.io.IoUtils;
+import com.google.i18n.phonenumbers.ShortNumberInfo;
 
 import java.io.BufferedInputStream;
 import java.io.ByteArrayOutputStream;
+import java.io.File;
 import java.io.FileDescriptor;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.PrintWriter;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collections;
 import java.util.List;
 import java.util.zip.GZIPInputStream;
@@ -69,6 +76,12 @@
 public class EmergencyNumberTracker extends Handler {
     private static final String TAG = EmergencyNumberTracker.class.getSimpleName();
 
+    private static final int INVALID_DATABASE_VERSION = -1;
+    private static final String EMERGENCY_NUMBER_DB_OTA_FILE_NAME = "emergency_number_db";
+    private static final String EMERGENCY_NUMBER_DB_OTA_FILE_PATH =
+            "misc/emergencynumberdb/" + EMERGENCY_NUMBER_DB_OTA_FILE_NAME;
+    private FileInputStream mEmergencyNumberDbOtaFileInputStream = null;
+
     /** @hide */
     public static boolean DBG = false;
     /** @hide */
@@ -81,6 +94,7 @@
     private final CommandsInterface mCi;
     private final Phone mPhone;
     private String mCountryIso;
+    private int mCurrentDatabaseVersion = INVALID_DATABASE_VERSION;
     /**
      * Indicates if the country iso is set by another subscription.
      * @hide
@@ -113,6 +127,10 @@
     private static final int EVENT_UPDATE_EMERGENCY_NUMBER_TEST_MODE = 3;
     /** Event indicating the update for the emergency number prefix from carrier config. */
     private static final int EVENT_UPDATE_EMERGENCY_NUMBER_PREFIX = 4;
+    /** Event indicating the update for the OTA emergency number database. */
+    private static final int EVENT_UPDATE_OTA_EMERGENCY_NUMBER_DB = 5;
+    /** Event indicating the override for the test OTA emergency number database. */
+    private static final int EVENT_OVERRIDE_OTA_EMERGENCY_NUMBER_DB_FILE_PATH = 6;
 
     private BroadcastReceiver mIntentReceiver = new BroadcastReceiver() {
         @Override
@@ -129,17 +147,19 @@
                             TelephonyManager.EXTRA_NETWORK_COUNTRY);
                     logd("ACTION_NETWORK_COUNTRY_CHANGED: PhoneId: " + phoneId + " CountryIso: "
                             + countryIso);
+
                     // Sometimes the country is updated as an empty string when the network signal
                     // is lost; though we may not call emergency when there is no signal, we want
                     // to keep the old country iso to provide country-related emergency numbers,
-                    // because they think they are still in that country. So we do need to update
-                    // country change in this case.
-                    if (TextUtils.isEmpty(countryIso)) {
+                    // because they think they are still in that country. We don't need to update
+                    // country change in this case. We will still need to update the empty string
+                    // if device is in APM.
+                    if (TextUtils.isEmpty(countryIso) && !isAirplaneModeEnabled()) {
                         return;
                     }
 
                     // Update country iso change for available Phones
-                    updateEmergencyCountryIsoAllPhones(countryIso);
+                    updateEmergencyCountryIsoAllPhones(countryIso == null ? "" : countryIso);
                 }
                 return;
             }
@@ -149,6 +169,14 @@
     public EmergencyNumberTracker(Phone phone, CommandsInterface ci) {
         mPhone = phone;
         mCi = ci;
+
+        try {
+            mEmergencyNumberDbOtaFileInputStream = new FileInputStream(
+                    new File(Environment.getDataDirectory(), EMERGENCY_NUMBER_DB_OTA_FILE_PATH));
+        } catch (FileNotFoundException ex) {
+            loge("Initialize ota emergency database file input failure: " + ex);
+        }
+
         if (mPhone != null) {
             CarrierConfigManager configMgr = (CarrierConfigManager)
                     mPhone.getContext().getSystemService(Context.CARRIER_CONFIG_SERVICE);
@@ -207,7 +235,7 @@
                 }
                 break;
             case EVENT_UPDATE_EMERGENCY_NUMBER_TEST_MODE:
-                if (msg.obj == null) {
+                if (msg.obj == null && msg.arg1 != RESET_EMERGENCY_NUMBER_TEST_MODE) {
                     loge("EVENT_UPDATE_EMERGENCY_NUMBER_TEST_MODE: Result from"
                             + " executeEmergencyNumberTestModeCommand is null.");
                 } else {
@@ -222,16 +250,43 @@
                 } else {
                     updateEmergencyNumberPrefixAndNotify((String[]) msg.obj);
                 }
-
+                break;
+            case EVENT_UPDATE_OTA_EMERGENCY_NUMBER_DB:
+                updateOtaEmergencyNumberListDatabaseAndNotify();
+                break;
+            case EVENT_OVERRIDE_OTA_EMERGENCY_NUMBER_DB_FILE_PATH:
+                if (msg.obj == null) {
+                    overrideOtaEmergencyNumberDbFilePath(null);
+                } else {
+                    overrideOtaEmergencyNumberDbFilePath((ParcelFileDescriptor) msg.obj);
+                }
+                break;
         }
     }
 
+    private boolean isAirplaneModeEnabled() {
+        ServiceStateTracker serviceStateTracker = mPhone.getServiceStateTracker();
+        if (serviceStateTracker != null) {
+            if (serviceStateTracker.getServiceState().getState()
+                    == ServiceState.STATE_POWER_OFF) {
+                return true;
+            }
+        }
+        return false;
+    }
+
     private void initializeDatabaseEmergencyNumberList() {
         // If country iso has been cached when listener is set, don't need to cache the initial
         // country iso and initial database.
         if (mCountryIso == null) {
-            updateEmergencyCountryIso(getInitialCountryIso().toLowerCase());
-            cacheEmergencyDatabaseByCountry(mCountryIso);
+            String countryForDatabaseCache = getInitialCountryIso().toLowerCase();
+            updateEmergencyCountryIso(countryForDatabaseCache);
+            // Use the last known country to cache the database in APM
+            if (TextUtils.isEmpty(countryForDatabaseCache)
+                    && isAirplaneModeEnabled()) {
+                countryForDatabaseCache = getCountryIsoForCachingDatabase();
+            }
+            cacheEmergencyDatabaseByCountry(countryForDatabaseCache);
         }
     }
 
@@ -273,7 +328,7 @@
                 if (b != null) {
                     String[] emergencyNumberPrefix = b.getStringArray(
                             CarrierConfigManager.KEY_EMERGENCY_NUMBER_PREFIX_STRING_ARRAY);
-                    if (!mEmergencyNumberPrefix.equals(emergencyNumberPrefix)) {
+                    if (!Arrays.equals(mEmergencyNumberPrefix, emergencyNumberPrefix)) {
                         this.obtainMessage(EVENT_UPDATE_EMERGENCY_NUMBER_PREFIX,
                                 emergencyNumberPrefix).sendToTarget();
                     }
@@ -311,6 +366,36 @@
         this.obtainMessage(EVENT_UPDATE_DB_COUNTRY_ISO_CHANGED, countryIso).sendToTarget();
     }
 
+    /**
+     * Update changed OTA Emergency Number database.
+     *
+     * @hide
+     */
+    public void updateOtaEmergencyNumberDatabase() {
+        this.obtainMessage(EVENT_UPDATE_OTA_EMERGENCY_NUMBER_DB).sendToTarget();
+    }
+
+    /**
+     * Override the OTA Emergency Number database file path.
+     *
+     * @hide
+     */
+    public void updateOtaEmergencyNumberDbFilePath(ParcelFileDescriptor otaParcelFileDescriptor) {
+        this.obtainMessage(
+                EVENT_OVERRIDE_OTA_EMERGENCY_NUMBER_DB_FILE_PATH,
+                        otaParcelFileDescriptor).sendToTarget();
+    }
+
+    /**
+     * Override the OTA Emergency Number database file path.
+     *
+     * @hide
+     */
+    public void resetOtaEmergencyNumberDbFilePath() {
+        this.obtainMessage(
+                EVENT_OVERRIDE_OTA_EMERGENCY_NUMBER_DB_FILE_PATH, null).sendToTarget();
+    }
+
     private EmergencyNumber convertEmergencyNumberFromEccInfo(EccInfo eccInfo, String countryIso) {
         String phoneNumber = eccInfo.phoneNumber.trim();
         if (phoneNumber.isEmpty()) {
@@ -350,28 +435,106 @@
     private void cacheEmergencyDatabaseByCountry(String countryIso) {
         BufferedInputStream inputStream = null;
         ProtobufEccData.AllInfo allEccMessages = null;
-        List<EmergencyNumber> updatedEmergencyNumberList = new ArrayList<>();
+        int assetsDatabaseVersion = INVALID_DATABASE_VERSION;
+
+        // Read the Asset emergency number database
+        List<EmergencyNumber> updatedAssetEmergencyNumberList = new ArrayList<>();
         try {
             inputStream = new BufferedInputStream(
                     mPhone.getContext().getAssets().open(EMERGENCY_NUMBER_DB_ASSETS_FILE));
             allEccMessages = ProtobufEccData.AllInfo.parseFrom(readInputStreamToByteArray(
                     new GZIPInputStream(inputStream)));
-            logd(countryIso + " emergency database is loaded. ");
+            assetsDatabaseVersion = allEccMessages.revision;
+            logd(countryIso + " asset emergency database is loaded. Ver: " + assetsDatabaseVersion
+                    + " Phone Id: " + mPhone.getPhoneId());
             for (ProtobufEccData.CountryInfo countryEccInfo : allEccMessages.countries) {
                 if (countryEccInfo.isoCode.equals(countryIso.toUpperCase())) {
                     for (ProtobufEccData.EccInfo eccInfo : countryEccInfo.eccs) {
-                        updatedEmergencyNumberList.add(convertEmergencyNumberFromEccInfo(
+                        updatedAssetEmergencyNumberList.add(convertEmergencyNumberFromEccInfo(
                                 eccInfo, countryIso));
                     }
                 }
             }
-            EmergencyNumber.mergeSameNumbersInEmergencyNumberList(updatedEmergencyNumberList);
-            mEmergencyNumberListFromDatabase = updatedEmergencyNumberList;
+            EmergencyNumber.mergeSameNumbersInEmergencyNumberList(updatedAssetEmergencyNumberList);
         } catch (IOException ex) {
-            loge("Cache emergency database failure: " + ex);
+            loge("Cache asset emergency database failure: " + ex);
         } finally {
-            IoUtils.closeQuietly(inputStream);
+            // close quietly by catching non-runtime exceptions.
+            if (inputStream != null) {
+                try {
+                    inputStream.close();
+                } catch (RuntimeException rethrown) {
+                    throw rethrown;
+                } catch (Exception ignored) {
+                }
+            }
         }
+
+        // Cache OTA emergency number database
+        int otaDatabaseVersion = cacheOtaEmergencyNumberDatabase();
+
+        // Use a valid database that has higher version.
+        if (otaDatabaseVersion == INVALID_DATABASE_VERSION
+                && assetsDatabaseVersion == INVALID_DATABASE_VERSION) {
+            loge("No database available. Phone Id: " + mPhone.getPhoneId());
+            return;
+        } else if (assetsDatabaseVersion > otaDatabaseVersion) {
+            logd("Using Asset Emergency database. Version: " + assetsDatabaseVersion);
+            mCurrentDatabaseVersion = assetsDatabaseVersion;
+            mEmergencyNumberListFromDatabase = updatedAssetEmergencyNumberList;
+        } else {
+            logd("Using Ota Emergency database. Version: " + otaDatabaseVersion);
+        }
+    }
+
+    private int cacheOtaEmergencyNumberDatabase() {
+        BufferedInputStream inputStream = null;
+        ProtobufEccData.AllInfo allEccMessages = null;
+        int otaDatabaseVersion = INVALID_DATABASE_VERSION;
+
+        // Read the OTA emergency number database
+        List<EmergencyNumber> updatedOtaEmergencyNumberList = new ArrayList<>();
+        try {
+            // If OTA File partition is not available, try to reload the default one.
+            if (mEmergencyNumberDbOtaFileInputStream == null) {
+                mEmergencyNumberDbOtaFileInputStream = new FileInputStream(
+                      new File(Environment.getDataDirectory(), EMERGENCY_NUMBER_DB_OTA_FILE_PATH));
+            }
+            inputStream = new BufferedInputStream(mEmergencyNumberDbOtaFileInputStream);
+            allEccMessages = ProtobufEccData.AllInfo.parseFrom(readInputStreamToByteArray(
+                    new GZIPInputStream(inputStream)));
+            logd(mCountryIso + " ota emergency database is loaded. Ver: " + otaDatabaseVersion);
+            otaDatabaseVersion = allEccMessages.revision;
+            for (ProtobufEccData.CountryInfo countryEccInfo : allEccMessages.countries) {
+                if (countryEccInfo.isoCode.equals(mCountryIso.toUpperCase())) {
+                    for (ProtobufEccData.EccInfo eccInfo : countryEccInfo.eccs) {
+                        updatedOtaEmergencyNumberList.add(convertEmergencyNumberFromEccInfo(
+                                eccInfo, mCountryIso));
+                    }
+                }
+            }
+            EmergencyNumber.mergeSameNumbersInEmergencyNumberList(updatedOtaEmergencyNumberList);
+        } catch (IOException ex) {
+            loge("Cache ota emergency database IOException: " + ex);
+        } finally {
+            // close quietly by catching non-runtime exceptions.
+            if (inputStream != null) {
+                try {
+                    inputStream.close();
+                } catch (RuntimeException rethrown) {
+                    throw rethrown;
+                } catch (Exception ignored) {
+                }
+            }
+        }
+
+        // Use a valid database that has higher version.
+        if (otaDatabaseVersion != INVALID_DATABASE_VERSION
+                && mCurrentDatabaseVersion < otaDatabaseVersion) {
+            mCurrentDatabaseVersion = otaDatabaseVersion;
+            mEmergencyNumberListFromDatabase = updatedOtaEmergencyNumberList;
+        }
+        return otaDatabaseVersion;
     }
 
     /**
@@ -419,6 +582,12 @@
         logd("updateEmergencyNumberListDatabaseAndNotify(): receiving countryIso: "
                 + countryIso);
         updateEmergencyCountryIso(countryIso.toLowerCase());
+        // Use cached country iso in APM to load emergency number database.
+        if (TextUtils.isEmpty(countryIso) && isAirplaneModeEnabled()) {
+            countryIso = getCountryIsoForCachingDatabase();
+            logd("updateEmergencyNumberListDatabaseAndNotify(): using cached APM country "
+                    + countryIso);
+        }
         cacheEmergencyDatabaseByCountry(countryIso);
         writeUpdatedEmergencyNumberListMetrics(mEmergencyNumberListFromDatabase);
         if (!DBG) {
@@ -434,9 +603,44 @@
         notifyEmergencyNumberList();
     }
 
+    private void overrideOtaEmergencyNumberDbFilePath(
+            ParcelFileDescriptor otaParcelableFileDescriptor) {
+        logd("overrideOtaEmergencyNumberDbFilePath:" + otaParcelableFileDescriptor);
+        try {
+            if (otaParcelableFileDescriptor == null) {
+                mEmergencyNumberDbOtaFileInputStream = new FileInputStream(
+                    new File(Environment.getDataDirectory(), EMERGENCY_NUMBER_DB_OTA_FILE_PATH));
+            } else {
+                mEmergencyNumberDbOtaFileInputStream = new FileInputStream(
+                    otaParcelableFileDescriptor.getFileDescriptor());
+            }
+        } catch (FileNotFoundException ex) {
+            loge("Override ota emergency database failure: " + ex);
+        }
+    }
+
+    private void updateOtaEmergencyNumberListDatabaseAndNotify() {
+        logd("updateOtaEmergencyNumberListDatabaseAndNotify():"
+                + " receiving Emegency Number database OTA update");
+        if (cacheOtaEmergencyNumberDatabase() != INVALID_DATABASE_VERSION) {
+            writeUpdatedEmergencyNumberListMetrics(mEmergencyNumberListFromDatabase);
+            if (!DBG) {
+                mEmergencyNumberListDatabaseLocalLog.log(
+                        "updateOtaEmergencyNumberListDatabaseAndNotify:"
+                            + mEmergencyNumberListFromDatabase);
+            }
+            updateEmergencyNumberList();
+            if (!DBG) {
+                mEmergencyNumberListLocalLog.log("updateOtaEmergencyNumberListDatabaseAndNotify:"
+                        + mEmergencyNumberList);
+            }
+            notifyEmergencyNumberList();
+        }
+    }
+
     private void updateEmergencyNumberPrefixAndNotify(String[] emergencyNumberPrefix) {
         logd("updateEmergencyNumberPrefixAndNotify(): receiving emergencyNumberPrefix: "
-                + emergencyNumberPrefix.toString());
+                + Arrays.toString(emergencyNumberPrefix));
         mEmergencyNumberPrefix = emergencyNumberPrefix;
         updateEmergencyNumberList();
         if (!DBG) {
@@ -497,7 +701,7 @@
         if (!mEmergencyNumberListFromRadio.isEmpty()) {
             return Collections.unmodifiableList(mEmergencyNumberList);
         } else {
-            return getEmergencyNumberListFromEccListAndTest();
+            return getEmergencyNumberListFromEccListDatabaseAndTest();
         }
     }
 
@@ -535,7 +739,7 @@
             return false;
         } else {
             return isEmergencyNumberFromEccList(number, exactMatch)
-                    || isEmergencyNumberForTest(number);
+                    || isEmergencyNumberFromDatabase(number) || isEmergencyNumberForTest(number);
         }
     }
 
@@ -600,6 +804,21 @@
         return mCountryIso;
     }
 
+    private String getCountryIsoForCachingDatabase() {
+        ServiceStateTracker sst = mPhone.getServiceStateTracker();
+        if (sst != null) {
+            LocaleTracker lt = sst.getLocaleTracker();
+            if (lt != null) {
+                return lt.getLastKnownCountryIso();
+            }
+        }
+        return "";
+    }
+
+    public int getEmergencyNumberDbVersion() {
+        return mCurrentDatabaseVersion;
+    }
+
     private synchronized void updateEmergencyCountryIso(String countryIso) {
         mCountryIso = countryIso;
     }
@@ -640,15 +859,18 @@
     private List<EmergencyNumber> getEmergencyNumberListWithPrefix(
             List<EmergencyNumber> emergencyNumberList) {
         List<EmergencyNumber> emergencyNumberListWithPrefix = new ArrayList<>();
-        for (EmergencyNumber num : emergencyNumberList) {
-            for (String prefix : mEmergencyNumberPrefix) {
-                // If an emergency number has started with the prefix, no need to apply the prefix.
-                if (!num.getNumber().startsWith(prefix)) {
-                    emergencyNumberListWithPrefix.add(new EmergencyNumber(
+        if (emergencyNumberList != null) {
+            for (EmergencyNumber num : emergencyNumberList) {
+                for (String prefix : mEmergencyNumberPrefix) {
+                    // If an emergency number has started with the prefix,
+                    // no need to apply the prefix.
+                    if (!num.getNumber().startsWith(prefix)) {
+                        emergencyNumberListWithPrefix.add(new EmergencyNumber(
                             prefix + num.getNumber(), num.getCountryIso(),
                             num.getMnc(), num.getEmergencyServiceCategoryBitmask(),
                             num.getEmergencyUrns(), num.getEmergencyNumberSourceBitmask(),
                             num.getEmergencyCallRouting()));
+                    }
                 }
             }
         }
@@ -665,6 +887,26 @@
         return false;
     }
 
+    private boolean isEmergencyNumberFromDatabase(String number) {
+        if (!mPhone.getHalVersion().greaterOrEqual(new HalVersion(1, 4))) {
+            return false;
+        }
+        number = PhoneNumberUtils.stripSeparators(number);
+        for (EmergencyNumber num : mEmergencyNumberListFromDatabase) {
+            if (num.getNumber().equals(number)) {
+                return true;
+            }
+        }
+        List<EmergencyNumber> emergencyNumberListFromDatabaseWithPrefix =
+                getEmergencyNumberListWithPrefix(mEmergencyNumberListFromDatabase);
+        for (EmergencyNumber num : emergencyNumberListFromDatabaseWithPrefix) {
+            if (num.getNumber().equals(number)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
     private EmergencyNumber getLabeledEmergencyNumberForEcclist(String number) {
         number = PhoneNumberUtils.stripSeparators(number);
         for (EmergencyNumber num : mEmergencyNumberListFromDatabase) {
@@ -856,9 +1098,17 @@
         notifyEmergencyNumberList();
     }
 
-    private List<EmergencyNumber> getEmergencyNumberListFromEccListAndTest() {
+    private List<EmergencyNumber> getEmergencyNumberListFromEccListDatabaseAndTest() {
         List<EmergencyNumber> mergedEmergencyNumberList = getEmergencyNumberListFromEccList();
+        if (mPhone.getHalVersion().greaterOrEqual(new HalVersion(1, 4))) {
+            loge("getEmergencyNumberListFromEccListDatabaseAndTest: radio indication is"
+                    + " unavailable in 1.4 HAL.");
+            mergedEmergencyNumberList.addAll(mEmergencyNumberListFromDatabase);
+            mergedEmergencyNumberList.addAll(getEmergencyNumberListWithPrefix(
+                    mEmergencyNumberListFromDatabase));
+        }
         mergedEmergencyNumberList.addAll(getEmergencyNumberListTestMode());
+        EmergencyNumber.mergeSameNumbersInEmergencyNumberList(mergedEmergencyNumberList);
         return mergedEmergencyNumberList;
     }
 
@@ -889,7 +1139,7 @@
         }
         for (EmergencyNumber num : updatedEmergencyNumberList) {
             TelephonyMetrics.getInstance().writeEmergencyNumberUpdateEvent(
-                    mPhone.getPhoneId(), num);
+                    mPhone.getPhoneId(), num, getEmergencyNumberDbVersion());
         }
     }
 
@@ -905,6 +1155,12 @@
         ipw.println(" Hal Version:" + mPhone.getHalVersion());
         ipw.println(" ========================================= ");
 
+        ipw.println(" Country Iso:" + getEmergencyCountryIso());
+        ipw.println(" ========================================= ");
+
+        ipw.println(" Database Version:" + getEmergencyNumberDbVersion());
+        ipw.println(" ========================================= ");
+
         ipw.println("mEmergencyNumberListDatabaseLocalLog:");
         ipw.increaseIndent();
         mEmergencyNumberListDatabaseLocalLog.dump(fd, pw, args);
diff --git a/src/java/com/android/internal/telephony/euicc/EuiccCardController.java b/src/java/com/android/internal/telephony/euicc/EuiccCardController.java
index 86826c3..3016ca1 100644
--- a/src/java/com/android/internal/telephony/euicc/EuiccCardController.java
+++ b/src/java/com/android/internal/telephony/euicc/EuiccCardController.java
@@ -27,10 +27,10 @@
 import android.os.Binder;
 import android.os.Handler;
 import android.os.RemoteException;
-import android.os.ServiceManager;
 import android.preference.PreferenceManager;
 import android.provider.Settings;
 import android.service.euicc.EuiccProfileInfo;
+import android.telephony.TelephonyFrameworkInitializer;
 import android.telephony.TelephonyManager;
 import android.telephony.euicc.EuiccCardManager;
 import android.telephony.euicc.EuiccNotification;
@@ -108,7 +108,10 @@
 
     private EuiccCardController(Context context) {
         this(context, new Handler(), EuiccController.get(), UiccController.getInstance());
-        ServiceManager.addService("euicc_card_controller", this);
+        TelephonyFrameworkInitializer
+                .getTelephonyServiceManager()
+                .getEuiccCardControllerServiceRegisterer()
+                .register(this);
     }
 
     @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
diff --git a/src/java/com/android/internal/telephony/euicc/EuiccConnector.java b/src/java/com/android/internal/telephony/euicc/EuiccConnector.java
index 24f56de..f135e62 100644
--- a/src/java/com/android/internal/telephony/euicc/EuiccConnector.java
+++ b/src/java/com/android/internal/telephony/euicc/EuiccConnector.java
@@ -15,6 +15,8 @@
  */
 package com.android.internal.telephony.euicc;
 
+import static android.telephony.euicc.EuiccCardManager.ResetOption;
+
 import static com.android.internal.annotations.VisibleForTesting.Visibility.PACKAGE;
 
 import android.Manifest;
@@ -44,6 +46,7 @@
 import android.service.euicc.IDownloadSubscriptionCallback;
 import android.service.euicc.IEraseSubscriptionsCallback;
 import android.service.euicc.IEuiccService;
+import android.service.euicc.IEuiccServiceDumpResultCallback;
 import android.service.euicc.IGetDefaultDownloadableSubscriptionListCallback;
 import android.service.euicc.IGetDownloadableSubscriptionMetadataCallback;
 import android.service.euicc.IGetEidCallback;
@@ -54,6 +57,7 @@
 import android.service.euicc.IRetainSubscriptionsForFactoryResetCallback;
 import android.service.euicc.ISwitchToSubscriptionCallback;
 import android.service.euicc.IUpdateSubscriptionNicknameCallback;
+import android.telephony.PackageChangeReceiver;
 import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyManager;
 import android.telephony.UiccCardInfo;
@@ -66,7 +70,7 @@
 import android.util.Log;
 
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.content.PackageMonitor;
+import com.android.internal.telephony.util.TelephonyUtils;
 import com.android.internal.util.IState;
 import com.android.internal.util.State;
 import com.android.internal.util.StateMachine;
@@ -143,6 +147,8 @@
     private static final int CMD_RETAIN_SUBSCRIPTIONS = 110;
     private static final int CMD_GET_OTA_STATUS = 111;
     private static final int CMD_START_OTA_IF_NECESSARY = 112;
+    private static final int CMD_ERASE_SUBSCRIPTIONS_WITH_OPTIONS = 113;
+    private static final int CMD_DUMP_EUICC_SERVICE = 114;
 
     private static boolean isEuiccCommand(int what) {
         return what >= CMD_GET_EID;
@@ -150,7 +156,7 @@
 
     /** Flags to use when querying PackageManager for Euicc component implementations. */
     private static final int EUICC_QUERY_FLAGS =
-            PackageManager.MATCH_SYSTEM_ONLY | PackageManager.MATCH_DEBUG_TRIAGED_MISSING
+            PackageManager.MATCH_SYSTEM_ONLY | PackageManager.MATCH_DIRECT_BOOT_AUTO
                     | PackageManager.GET_RESOLVED_FILTER;
 
     /**
@@ -302,7 +308,9 @@
         void onUpdateNicknameComplete(int result);
     }
 
-    /** Callback class for {@link #eraseSubscriptions}. */
+    /**
+     * Callback class for {@link #eraseSubscriptions} and {@link #eraseSubscriptionsWithOptions}.
+     */
     @VisibleForTesting(visibility = PACKAGE)
     public interface EraseCommandCallback extends BaseEuiccCommandCallback {
         /** Called when the erase has completed (though it may have failed). */
@@ -316,10 +324,19 @@
         void onRetainSubscriptionsComplete(int result);
     }
 
+    /** Callback class for {@link #dumpEuiccService(DumpEuiccCommandCallback)}   }*/
+    @VisibleForTesting(visibility = PACKAGE)
+    public interface DumpEuiccServiceCommandCallback extends BaseEuiccCommandCallback {
+        /** Called when the retain command has completed (though it may have failed). */
+        void onDumpEuiccServiceComplete(String logs);
+    }
+
     private Context mContext;
     private PackageManager mPm;
+    private TelephonyManager mTm;
+    private SubscriptionManager mSm;
 
-    private final PackageMonitor mPackageMonitor = new EuiccPackageMonitor();
+    private final PackageChangeReceiver mPackageMonitor = new EuiccPackageMonitor();
     private final BroadcastReceiver mUserUnlockedReceiver = new BroadcastReceiver() {
         @Override
         public void onReceive(Context context, Intent intent) {
@@ -361,6 +378,9 @@
     private void init(Context context) {
         mContext = context;
         mPm = context.getPackageManager();
+        mTm = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
+        mSm = (SubscriptionManager)
+                context.getSystemService(Context.TELEPHONY_SUBSCRIPTION_SERVICE);
 
         // Unavailable/Available both monitor for package changes and update mSelectedComponent but
         // do not need to adjust the binding.
@@ -382,11 +402,11 @@
         mSelectedComponent = findBestComponent();
         setInitialState(mSelectedComponent != null ? mAvailableState : mUnavailableState);
 
-        mPackageMonitor.register(mContext, null /* thread */, false /* externalStorage */);
+        start();
+
+        mPackageMonitor.register(mContext, null /* thread */, null /* user */);
         mContext.registerReceiver(
                 mUserUnlockedReceiver, new IntentFilter(Intent.ACTION_USER_UNLOCKED));
-
-        start();
     }
 
     @Override
@@ -491,18 +511,33 @@
         sendMessage(CMD_UPDATE_SUBSCRIPTION_NICKNAME, cardId, 0 /* arg2 */, request);
     }
 
-    /** Asynchronously erase all profiles on the eUICC. */
+    /** Asynchronously erase operational profiles on the eUICC. */
     @VisibleForTesting(visibility = PACKAGE)
     public void eraseSubscriptions(int cardId, EraseCommandCallback callback) {
         sendMessage(CMD_ERASE_SUBSCRIPTIONS, cardId, 0 /* arg2 */, callback);
     }
 
+    /** Asynchronously erase specific profiles on the eUICC. */
+    @VisibleForTesting(visibility = PACKAGE)
+    public void eraseSubscriptionsWithOptions(
+            int cardId, @ResetOption int options, EraseCommandCallback callback) {
+        sendMessage(CMD_ERASE_SUBSCRIPTIONS_WITH_OPTIONS, cardId, options, callback);
+    }
+
     /** Asynchronously ensure that all profiles will be retained on the next factory reset. */
     @VisibleForTesting(visibility = PACKAGE)
     public void retainSubscriptions(int cardId, RetainSubscriptionsCommandCallback callback) {
         sendMessage(CMD_RETAIN_SUBSCRIPTIONS, cardId, 0 /* arg2 */, callback);
     }
 
+    /** Asynchronously calls the currently bound EuiccService implementation to dump its states */
+    @VisibleForTesting(visibility = PACKAGE)
+    public void dumpEuiccService(DumpEuiccServiceCommandCallback callback) {
+        sendMessage(CMD_DUMP_EUICC_SERVICE, TelephonyManager.UNSUPPORTED_CARD_ID /* ignored */,
+                0 /* arg2 */,
+                callback);
+    }
+
     /**
      * State in which no EuiccService is available.
      *
@@ -523,6 +558,7 @@
                 } else if (getCurrentState() != mUnavailableState) {
                     transitionTo(mUnavailableState);
                 }
+                updateSubscriptionInfoListForAllAccessibleEuiccs();
                 return HANDLED;
             } else if (isEuiccCommand(message.what)) {
                 BaseEuiccCommandCallback callback = getCallback(message);
@@ -616,9 +652,9 @@
                     isSameComponent = mSelectedComponent != null;
                 } else {
                     isSameComponent = mSelectedComponent == null
-                            || Objects.equals(
-                                    bestComponent.getComponentName(),
-                                    mSelectedComponent.getComponentName());
+                            || Objects.equals(new ComponentName(bestComponent.packageName,
+                            bestComponent.name),
+                        new ComponentName(mSelectedComponent.packageName, mSelectedComponent.name));
                 }
                 boolean forceRebind = bestComponent != null
                         && Objects.equals(bestComponent.packageName, affectedPackage);
@@ -631,6 +667,7 @@
                         transitionTo(mBindingState);
                     }
                 }
+                updateSubscriptionInfoListForAllAccessibleEuiccs();
                 return HANDLED;
             } else if (message.what == CMD_CONNECT_TIMEOUT) {
                 transitionTo(mAvailableState);
@@ -838,6 +875,21 @@
                                     });
                             break;
                         }
+                        case CMD_ERASE_SUBSCRIPTIONS_WITH_OPTIONS: {
+                            mEuiccService.eraseSubscriptionsWithOptions(slotId,
+                                    message.arg2 /* options */,
+                                    new IEraseSubscriptionsCallback.Stub() {
+                                        @Override
+                                        public void onComplete(int result) {
+                                            sendMessage(CMD_COMMAND_COMPLETE, (Runnable) () -> {
+                                                ((EraseCommandCallback) callback)
+                                                        .onEraseComplete(result);
+                                                onCommandEnd(callback);
+                                            });
+                                        }
+                                    });
+                            break;
+                        }
                         case CMD_RETAIN_SUBSCRIPTIONS: {
                             mEuiccService.retainSubscriptionsForFactoryReset(slotId,
                                     new IRetainSubscriptionsForFactoryResetCallback.Stub() {
@@ -888,6 +940,20 @@
                                     });
                             break;
                         }
+                        case CMD_DUMP_EUICC_SERVICE: {
+                            mEuiccService.dump(new IEuiccServiceDumpResultCallback.Stub() {
+                                @Override
+                                public void onComplete(String logs)
+                                        throws RemoteException {
+                                    sendMessage(CMD_COMMAND_COMPLETE, (Runnable) () -> {
+                                        ((DumpEuiccServiceCommandCallback) callback)
+                                                .onDumpEuiccServiceComplete(logs);
+                                        onCommandEnd(callback);
+                                    });
+                                }
+                            });
+                            break;
+                        }
                         default: {
                             Log.wtf(TAG, "Unimplemented eUICC command: " + message.what);
                             callback.onEuiccServiceUnavailable();
@@ -929,9 +995,11 @@
             case CMD_GET_EUICC_PROFILE_INFO_LIST:
             case CMD_GET_EUICC_INFO:
             case CMD_ERASE_SUBSCRIPTIONS:
+            case CMD_ERASE_SUBSCRIPTIONS_WITH_OPTIONS:
             case CMD_RETAIN_SUBSCRIPTIONS:
             case CMD_GET_OTA_STATUS:
             case CMD_START_OTA_IF_NECESSARY:
+            case CMD_DUMP_EUICC_SERVICE:
                 return (BaseEuiccCommandCallback) message.obj;
             case CMD_GET_DOWNLOADABLE_SUBSCRIPTION_METADATA:
                 return ((GetMetadataRequest) message.obj).mCallback;
@@ -1006,7 +1074,8 @@
             return false;
         }
         Intent intent = new Intent(EuiccService.EUICC_SERVICE_INTERFACE);
-        intent.setComponent(mSelectedComponent.getComponentName());
+        intent.setComponent(new ComponentName(mSelectedComponent.packageName,
+            mSelectedComponent.name));
         // We bind this as a foreground service because it is operating directly on the SIM, and we
         // do not want it subjected to power-savings restrictions while doing so.
         return mContext.bindService(intent, this,
@@ -1030,7 +1099,7 @@
 
                 if (resolveInfo.filter.getPriority() > bestPriority) {
                     bestPriority = resolveInfo.filter.getPriority();
-                    bestComponent = resolveInfo.getComponentInfo();
+                    bestComponent = TelephonyUtils.getComponentInfo(resolveInfo);
                 }
             }
         }
@@ -1040,8 +1109,9 @@
 
     private static boolean isValidEuiccComponent(
             PackageManager packageManager, ResolveInfo resolveInfo) {
-        ComponentInfo componentInfo = resolveInfo.getComponentInfo();
-        String packageName = componentInfo.getComponentName().getPackageName();
+        ComponentInfo componentInfo = TelephonyUtils.getComponentInfo(resolveInfo);
+        String packageName = new ComponentName(componentInfo.packageName, componentInfo.name)
+            .getPackageName();
 
         // Verify that the app is privileged (via granting of a privileged permission).
         if (packageManager.checkPermission(
@@ -1086,19 +1156,19 @@
         sendMessage(CMD_SERVICE_DISCONNECTED);
     }
 
-    private class EuiccPackageMonitor extends PackageMonitor {
+    private class EuiccPackageMonitor extends PackageChangeReceiver {
         @Override
-        public void onPackageAdded(String packageName, int reason) {
+        public void onPackageAdded(String packageName) {
             sendPackageChange(packageName, true /* forceUnbindForThisPackage */);
         }
 
         @Override
-        public void onPackageRemoved(String packageName, int reason) {
+        public void onPackageRemoved(String packageName) {
             sendPackageChange(packageName, true /* forceUnbindForThisPackage */);
         }
 
         @Override
-        public void onPackageUpdateFinished(String packageName, int uid) {
+        public void onPackageUpdateFinished(String packageName) {
             sendPackageChange(packageName, true /* forceUnbindForThisPackage */);
         }
 
@@ -1108,13 +1178,12 @@
         }
 
         @Override
-        public boolean onHandleForceStop(Intent intent, String[] packages, int uid, boolean doit) {
+        public void onHandleForceStop(String[] packages, boolean doit) {
             if (doit) {
                 for (String packageName : packages) {
                     sendPackageChange(packageName, true /* forceUnbindForThisPackage */);
                 }
             }
-            return super.onHandleForceStop(intent, packages, uid, doit);
         }
 
         private void sendPackageChange(String packageName, boolean forceUnbindForThisPackage) {
@@ -1136,4 +1205,17 @@
         pw.println("mEuiccService=" + mEuiccService);
         pw.println("mActiveCommandCount=" + mActiveCommandCallbacks.size());
     }
+
+    private void updateSubscriptionInfoListForAllAccessibleEuiccs() {
+        if (mTm.getCardIdForDefaultEuicc() == TelephonyManager.UNSUPPORTED_CARD_ID) {
+            // Device does not support card ID
+            mSm.requestEmbeddedSubscriptionInfoListRefresh();
+        } else {
+            for (UiccCardInfo cardInfo : mTm.getUiccCardsInfo()) {
+                if (cardInfo.isEuicc()) {
+                    mSm.requestEmbeddedSubscriptionInfoListRefresh(cardInfo.getCardId());
+                }
+            }
+        }
+    }
 }
diff --git a/src/java/com/android/internal/telephony/euicc/EuiccController.java b/src/java/com/android/internal/telephony/euicc/EuiccController.java
index 3485a6c..400c326 100644
--- a/src/java/com/android/internal/telephony/euicc/EuiccController.java
+++ b/src/java/com/android/internal/telephony/euicc/EuiccController.java
@@ -17,6 +17,7 @@
 
 import android.Manifest;
 import android.Manifest.permission;
+import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.AppOpsManager;
 import android.app.PendingIntent;
@@ -28,7 +29,6 @@
 import android.content.pm.PackageManager;
 import android.os.Binder;
 import android.os.Bundle;
-import android.os.ServiceManager;
 import android.provider.Settings;
 import android.service.euicc.DownloadSubscriptionResult;
 import android.service.euicc.EuiccService;
@@ -37,16 +37,19 @@
 import android.service.euicc.GetEuiccProfileInfoListResult;
 import android.telephony.SubscriptionInfo;
 import android.telephony.SubscriptionManager;
+import android.telephony.TelephonyFrameworkInitializer;
 import android.telephony.TelephonyManager;
 import android.telephony.UiccAccessRule;
 import android.telephony.UiccCardInfo;
 import android.telephony.euicc.DownloadableSubscription;
+import android.telephony.euicc.EuiccCardManager.ResetOption;
 import android.telephony.euicc.EuiccInfo;
 import android.telephony.euicc.EuiccManager;
 import android.telephony.euicc.EuiccManager.OtaStatus;
 import android.text.TextUtils;
 import android.util.EventLog;
 import android.util.Log;
+import android.util.Pair;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.telephony.SubscriptionController;
@@ -54,8 +57,11 @@
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
+import java.util.Collections;
 import java.util.List;
+import java.util.Stack;
 import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicReference;
 
 /** Backing implementation of {@link android.telephony.euicc.EuiccManager}. */
@@ -66,6 +72,11 @@
     @VisibleForTesting
     static final String EXTRA_OPERATION = "operation";
 
+    /**
+     * Time out for {@link #dump(FileDescriptor, PrintWriter, String[])}
+     */
+    private static final int EUICC_DUMP_TIME_OUT_SECONDS = 5;
+
     // Aliases so line lengths stay short.
     private static final int OK = EuiccManager.EMBEDDED_SUBSCRIPTION_RESULT_OK;
     private static final int RESOLVABLE_ERROR =
@@ -89,6 +100,11 @@
     private final AppOpsManager mAppOpsManager;
     private final PackageManager mPackageManager;
 
+    // These values should be set or updated upon 1) system boot, 2) EuiccService/LPA is bound to
+    // the phone process, 3) values are updated remotely by server flags.
+    private List<String> mSupportedCountries;
+    private List<String> mUnsupportedCountries;
+
     /** Initialize the instance. Should only be called once. */
     public static EuiccController init(Context context) {
         synchronized (EuiccController.class) {
@@ -115,7 +131,8 @@
 
     private EuiccController(Context context) {
         this(context, new EuiccConnector(context));
-        ServiceManager.addService("econtroller", this);
+        TelephonyFrameworkInitializer
+                .getTelephonyServiceManager().getEuiccControllerService().register(this);
     }
 
     @VisibleForTesting
@@ -253,6 +270,101 @@
                 subscription, false /* forceDeactivateSim */, callingPackage, callbackIntent);
     }
 
+    /**
+     * Sets the supported or unsupported countries for eUICC.
+     *
+     * <p>If {@code isSupported} is true, the supported country list will be replaced by
+     * {@code countriesList}. Otherwise, unsupported country list will be replaced by
+     * {@code countriesList}. For how we determine whether a country is supported by checking
+     * supported and unsupported country list please check {@link EuiccManager#isSupportedCountry}.
+     *
+     * @param isSupported should be true if caller wants to set supported country list. If
+     * isSupported is false, un-supported country list will be updated.
+     * @param countriesList is a list of strings contains country ISO codes in uppercase.
+     */
+    @Override
+    public void setSupportedCountries(boolean isSupported, @NonNull List<String> countriesList) {
+        if (!callerCanWriteEmbeddedSubscriptions()) {
+            throw new SecurityException(
+                    "Must have WRITE_EMBEDDED_SUBSCRIPTIONS to set supported countries");
+        }
+        if (isSupported) {
+            mSupportedCountries = countriesList;
+        } else {
+            mUnsupportedCountries = countriesList;
+        }
+    }
+
+    /**
+     * Gets the supported or unsupported countries for eUICC.
+     *
+     * <p>If {@code isSupported} is true, the supported country list will be returned. Otherwise,
+     * unsupported country list will be returned.
+     *
+     * @param isSupported should be true if caller wants to get supported country list. If
+     * isSupported is false, unsupported country list will be returned.
+     * @return a list of strings contains country ISO codes in uppercase.
+     */
+    @Override
+    @NonNull
+    public List<String> getSupportedCountries(boolean isSupported) {
+        if (!callerCanWriteEmbeddedSubscriptions()) {
+            throw new SecurityException(
+                    "Must have WRITE_EMBEDDED_SUBSCRIPTIONS to get supported countries");
+        }
+        if (isSupported && mSupportedCountries != null) {
+            return mSupportedCountries;
+        } else if (!isSupported && mUnsupportedCountries != null) {
+            return mUnsupportedCountries;
+        }
+        return Collections.emptyList();
+    }
+
+    /**
+     * Returns whether the given country supports eUICC.
+     *
+     * <p>Supported country list has a higher prority than unsupported country list. If the
+     * supported country list is not empty, {@code countryIso} will be considered as supported when
+     * it exists in the supported country list. Otherwise {@code countryIso} is not supported. If
+     * the supported country list is empty, {@code countryIso} will be considered as supported if it
+     * does not exist in the unsupported country list. Otherwise {@code countryIso} is not
+     * supported. If both supported and unsupported country lists are empty, then all countries are
+     * consider be supported. For how to set supported and unsupported country list, please check
+     * {@link #setSupportedCountries}.
+     *
+     * @param countryIso should be the ISO-3166 country code is provided in uppercase 2 character
+     * format.
+     * @return whether the given country supports eUICC or not.
+     */
+    @Override
+    public boolean isSupportedCountry(@NonNull String countryIso) {
+        if (!callerCanWriteEmbeddedSubscriptions()) {
+            throw new SecurityException(
+                    "Must have WRITE_EMBEDDED_SUBSCRIPTIONS to check if the country is supported");
+        }
+        if (mSupportedCountries == null || mSupportedCountries.isEmpty()) {
+            Log.i(TAG, "Using blacklist unsupportedCountries=" + mUnsupportedCountries);
+            return !isEsimUnsupportedCountry(countryIso);
+        } else {
+            Log.i(TAG, "Using whitelist supportedCountries=" + mSupportedCountries);
+            return isEsimSupportedCountry(countryIso);
+        }
+    }
+
+    private boolean isEsimSupportedCountry(String countryIso) {
+        if (mSupportedCountries == null || TextUtils.isEmpty(countryIso)) {
+            return true;
+        }
+        return mSupportedCountries.contains(countryIso);
+    }
+
+    private boolean isEsimUnsupportedCountry(String countryIso) {
+        if (mUnsupportedCountries == null || TextUtils.isEmpty(countryIso)) {
+            return false;
+        }
+        return mUnsupportedCountries.contains(countryIso);
+    }
+
     void getDownloadableSubscriptionMetadata(int cardId, DownloadableSubscription subscription,
             boolean forceDeactivateSim, String callingPackage, PendingIntent callbackIntent) {
         if (!callerCanWriteEmbeddedSubscriptions()) {
@@ -311,9 +423,7 @@
                     break;
                 default:
                     resultCode = ERROR;
-                    extrasIntent.putExtra(
-                            EuiccManager.EXTRA_EMBEDDED_SUBSCRIPTION_DETAILED_CODE,
-                            result.getResult());
+                    addExtrasToResultIntent(extrasIntent, result.getResult());
                     break;
             }
 
@@ -339,6 +449,85 @@
                 false /* forceDeactivateSim */, resolvedBundle, callbackIntent);
     }
 
+    /**
+     * Given encoded error code described in
+     * {@link android.telephony.euicc.EuiccManager#OPERATION_SMDX_SUBJECT_REASON_CODE} decode it
+     * into SubjectCode[5.2.6.1] and ReasonCode[5.2.6.2] from GSMA (SGP.22 v2.2)
+     *
+     * @param resultCode from
+     *               {@link android.telephony.euicc.EuiccManager#OPERATION_SMDX_SUBJECT_REASON_CODE}
+     * @return a pair containing SubjectCode[5.2.6.1] and ReasonCode[5.2.6.2] from GSMA (SGP.22
+     * v2.2)
+     */
+    Pair<String, String> decodeSmdxSubjectAndReasonCode(int resultCode) {
+        final int numOfSections = 6;
+        final int bitsPerSection = 4;
+        final int sectionMask = 0xF;
+
+        final Stack<Integer> sections = new Stack<>();
+
+        // Extracting each section of digits backwards.
+        for (int i = 0; i < numOfSections; ++i) {
+            int sectionDigit = resultCode & sectionMask;
+            sections.push(sectionDigit);
+            resultCode = resultCode >>> bitsPerSection;
+        }
+
+        String subjectCode = sections.pop() + "." + sections.pop() + "." + sections.pop();
+        String reasonCode = sections.pop() + "." + sections.pop() + "." + sections.pop();
+
+        // drop the leading zeros, e.g 0.1 -> 1, 0.0.3 -> 3, 0.5.1 -> 5.1
+        subjectCode = subjectCode.replaceAll("^(0\\.)*", "");
+        reasonCode = reasonCode.replaceAll("^(0\\.)*", "");
+
+        return Pair.create(subjectCode, reasonCode);
+    }
+
+    /**
+     * Add more detailed information to the resulting intent.
+     * Fields added includes(key -> value):
+     * 1. {@link EuiccManager#EXTRA_EMBEDDED_SUBSCRIPTION_DETAILED_CODE} -> original error code
+     * 2. {@link EuiccManager#EXTRA_EMBEDDED_SUBSCRIPTION_OPERATION_CODE} ->
+     * EuiccManager.OperationCode such as {@link EuiccManager#OPERATION_DOWNLOAD}
+     * 3. if @link EuiccManager.OperationCode is not
+     * {@link EuiccManager#OPERATION_SMDX_SUBJECT_REASON_CODE}:
+     * {@link EuiccManager#EXTRA_EMBEDDED_SUBSCRIPTION_ERROR_CODE} -> @link
+     * EuiccManager.ErrorCode such as {@link EuiccManager#OPERATION_SMDX}
+     * 4. if EuiccManager.OperationCode is
+     * {@link EuiccManager#OPERATION_SMDX_SUBJECT_REASON_CODE}:
+     * a) {@link EuiccManager#EXTRA_EMBEDDED_SUBSCRIPTION_SMDX_SUBJECT_CODE} ->
+     * SubjectCode[5.2.6.1] from GSMA (SGP.22 v2.2)
+     * b) {@link EuiccManager#EXTRA_EMBEDDED_SUBSCRIPTION_SMDX_REASON_CODE} ->
+     * ReasonCode[5.2.6.2] from GSMA (SGP.22 v2.2
+     */
+    private void addExtrasToResultIntent(Intent intent, int resultCode) {
+        final int firstByteBitOffset = 24;
+        int errorCodeMask = 0xFFFFFF;
+        int operationCode = resultCode >>> firstByteBitOffset;
+
+        intent.putExtra(
+                EuiccManager.EXTRA_EMBEDDED_SUBSCRIPTION_DETAILED_CODE, resultCode);
+
+        intent.putExtra(EuiccManager.EXTRA_EMBEDDED_SUBSCRIPTION_OPERATION_CODE, operationCode);
+
+        // check to see if the operation code is EuiccManager#OPERATION_SMDX_SUBJECT_REASON_CODE
+        final boolean isSmdxSubjectReasonCode =
+                (operationCode == EuiccManager.OPERATION_SMDX_SUBJECT_REASON_CODE);
+
+        if (isSmdxSubjectReasonCode) {
+            final Pair<String, String> subjectReasonCode = decodeSmdxSubjectAndReasonCode(
+                    resultCode);
+            final String subjectCode = subjectReasonCode.first;
+            final String reasonCode = subjectReasonCode.second;
+            intent.putExtra(EuiccManager.EXTRA_EMBEDDED_SUBSCRIPTION_SMDX_SUBJECT_CODE,
+                    subjectCode);
+            intent.putExtra(EuiccManager.EXTRA_EMBEDDED_SUBSCRIPTION_SMDX_REASON_CODE, reasonCode);
+        } else {
+            final int errorCode = resultCode & errorCodeMask;
+            intent.putExtra(EuiccManager.EXTRA_EMBEDDED_SUBSCRIPTION_ERROR_CODE, errorCode);
+        }
+    }
+
     void downloadSubscription(int cardId, DownloadableSubscription subscription,
             boolean switchAfterDownload, String callingPackage, boolean forceDeactivateSim,
             Bundle resolvedBundle, PendingIntent callbackIntent) {
@@ -557,9 +746,8 @@
                                 break;
                             default:
                                 resultCode = ERROR;
-                                extrasIntent.putExtra(
-                                        EuiccManager.EXTRA_EMBEDDED_SUBSCRIPTION_DETAILED_CODE,
-                                        result.getResult());
+
+                                addExtrasToResultIntent(extrasIntent, result.getResult());
                                 break;
                         }
 
@@ -670,9 +858,7 @@
                     break;
                 default:
                     resultCode = ERROR;
-                    extrasIntent.putExtra(
-                            EuiccManager.EXTRA_EMBEDDED_SUBSCRIPTION_DETAILED_CODE,
-                            result.getResult());
+                    addExtrasToResultIntent(extrasIntent, result.getResult());
                     break;
             }
 
@@ -752,9 +938,7 @@
                                 return;
                             default:
                                 resultCode = ERROR;
-                                extrasIntent.putExtra(
-                                        EuiccManager.EXTRA_EMBEDDED_SUBSCRIPTION_DETAILED_CODE,
-                                        result);
+                                addExtrasToResultIntent(extrasIntent, result);
                                 break;
                         }
 
@@ -887,9 +1071,7 @@
                                 break;
                             default:
                                 resultCode = ERROR;
-                                extrasIntent.putExtra(
-                                        EuiccManager.EXTRA_EMBEDDED_SUBSCRIPTION_DETAILED_CODE,
-                                        result);
+                                addExtrasToResultIntent(extrasIntent, result);
                                 break;
                         }
 
@@ -943,9 +1125,7 @@
                                     return;
                                 default:
                                     resultCode = ERROR;
-                                    extrasIntent.putExtra(
-                                            EuiccManager.EXTRA_EMBEDDED_SUBSCRIPTION_DETAILED_CODE,
-                                            result);
+                                    addExtrasToResultIntent(extrasIntent, result);
                                     break;
                             }
 
@@ -970,7 +1150,48 @@
         }
         long token = Binder.clearCallingIdentity();
         try {
-            mConnector.eraseSubscriptions(cardId, new EuiccConnector.EraseCommandCallback() {
+            mConnector.eraseSubscriptions(
+                    cardId, new EuiccConnector.EraseCommandCallback() {
+                        @Override
+                        public void onEraseComplete(int result) {
+                            Intent extrasIntent = new Intent();
+                            final int resultCode;
+                            switch (result) {
+                                case EuiccService.RESULT_OK:
+                                    resultCode = OK;
+                                    refreshSubscriptionsAndSendResult(
+                                            callbackIntent, resultCode, extrasIntent);
+                                    return;
+                                default:
+                                    resultCode = ERROR;
+                                    addExtrasToResultIntent(extrasIntent, result);
+                                    break;
+                            }
+
+                            sendResult(callbackIntent, resultCode, extrasIntent);
+                        }
+
+                        @Override
+                        public void onEuiccServiceUnavailable() {
+                            sendResult(callbackIntent, ERROR, null /* extrasIntent */);
+                        }
+                    });
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+    }
+
+    @Override
+    public void eraseSubscriptionsWithOptions(
+            int cardId, @ResetOption int options, PendingIntent callbackIntent) {
+        if (!callerCanWriteEmbeddedSubscriptions()) {
+            throw new SecurityException(
+                    "Must have WRITE_EMBEDDED_SUBSCRIPTIONS to erase subscriptions");
+        }
+        long token = Binder.clearCallingIdentity();
+        try {
+            mConnector.eraseSubscriptionsWithOptions(
+                    cardId, options, new EuiccConnector.EraseCommandCallback() {
                 @Override
                 public void onEraseComplete(int result) {
                     Intent extrasIntent = new Intent();
@@ -983,9 +1204,7 @@
                             return;
                         default:
                             resultCode = ERROR;
-                            extrasIntent.putExtra(
-                                    EuiccManager.EXTRA_EMBEDDED_SUBSCRIPTION_DETAILED_CODE,
-                                    result);
+                                    addExtrasToResultIntent(extrasIntent, result);
                             break;
                     }
 
@@ -1020,9 +1239,7 @@
                                     break;
                                 default:
                                     resultCode = ERROR;
-                                    extrasIntent.putExtra(
-                                            EuiccManager.EXTRA_EMBEDDED_SUBSCRIPTION_DETAILED_CODE,
-                                            result);
+                                    addExtrasToResultIntent(extrasIntent, result);
                                     break;
                             }
 
@@ -1082,12 +1299,37 @@
     }
 
     @Override
-    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+    public void dump(FileDescriptor fd, final PrintWriter pw, String[] args) {
         mContext.enforceCallingOrSelfPermission(android.Manifest.permission.DUMP, "Requires DUMP");
         final long token = Binder.clearCallingIdentity();
+        pw.println("===== BEGIN EUICC CLINIC =====");
         try {
+            pw.println("===== EUICC CONNECTOR =====");
             mConnector.dump(fd, pw, args);
+            final CountDownLatch countDownLatch = new CountDownLatch(1);
+            mConnector.dumpEuiccService(new EuiccConnector.DumpEuiccServiceCommandCallback() {
+                @Override
+                public void onDumpEuiccServiceComplete(String logs) {
+                    pw.println("===== EUICC SERVICE =====");
+                    pw.println(logs);
+                    countDownLatch.countDown();
+                }
+
+                @Override
+                public void onEuiccServiceUnavailable() {
+                    pw.println("===== EUICC SERVICE UNAVAILABLE =====");
+                    countDownLatch.countDown();
+                }
+            });
+
+            // Wait up to 5 seconds
+            if (!countDownLatch.await(EUICC_DUMP_TIME_OUT_SECONDS, TimeUnit.SECONDS)) {
+                pw.println("===== EUICC SERVICE TIMEOUT =====");
+            }
+        } catch (InterruptedException e) {
+            pw.println("===== EUICC SERVICE INTERRUPTED =====");
         } finally {
+            pw.println("===== END EUICC CLINIC =====");
             Binder.restoreCallingIdentity(token);
         }
     }
@@ -1219,7 +1461,7 @@
     }
 
     private boolean supportMultiActiveSlots() {
-        return mTelephonyManager.getPhoneCount() > 1;
+        return mTelephonyManager.getSupportedModemCount() > 1;
     }
 
     // Checks whether the caller can manage the active embedded subscription on the SIM with the
diff --git a/src/java/com/android/internal/telephony/gsm/GsmCellBroadcastHandler.java b/src/java/com/android/internal/telephony/gsm/GsmCellBroadcastHandler.java
deleted file mode 100644
index 9367b5d..0000000
--- a/src/java/com/android/internal/telephony/gsm/GsmCellBroadcastHandler.java
+++ /dev/null
@@ -1,411 +0,0 @@
-/*
- * Copyright (C) 2013 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.gsm;
-
-import static com.android.internal.telephony.gsm.SmsCbConstants.MESSAGE_ID_CMAS_GEO_FENCING_TRIGGER;
-
-import android.content.BroadcastReceiver;
-import android.content.ContentResolver;
-import android.content.ContentUris;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.content.res.Resources;
-import android.database.Cursor;
-import android.net.Uri;
-import android.os.AsyncResult;
-import android.os.Message;
-import android.os.SystemClock;
-import android.provider.Telephony.CellBroadcasts;
-import android.telephony.CellLocation;
-import android.telephony.SmsCbLocation;
-import android.telephony.SmsCbMessage;
-import android.telephony.TelephonyManager;
-import android.telephony.gsm.GsmCellLocation;
-import android.text.format.DateUtils;
-
-import com.android.internal.telephony.CbGeoUtils.Geometry;
-import com.android.internal.telephony.CellBroadcastHandler;
-import com.android.internal.telephony.Phone;
-import com.android.internal.telephony.gsm.GsmSmsCbMessage.GeoFencingTriggerMessage;
-import com.android.internal.telephony.gsm.GsmSmsCbMessage.GeoFencingTriggerMessage.CellBroadcastIdentity;
-
-import java.text.DateFormat;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.Iterator;
-import java.util.List;
-
-/**
- * Handler for 3GPP format Cell Broadcasts. Parent class can also handle CDMA Cell Broadcasts.
- */
-public class GsmCellBroadcastHandler extends CellBroadcastHandler {
-    private static final boolean VDBG = false;  // log CB PDU data
-
-    /** Indicates that a message is not being broadcasted. */
-    private static final String MESSAGE_NOT_BROADCASTED = "0";
-
-    /** This map holds incomplete concatenated messages waiting for assembly. */
-    private final HashMap<SmsCbConcatInfo, byte[][]> mSmsCbPageMap =
-            new HashMap<SmsCbConcatInfo, byte[][]>(4);
-
-    private long mLastAirplaneModeTime = 0;
-
-    protected GsmCellBroadcastHandler(Context context, Phone phone) {
-        super("GsmCellBroadcastHandler", context, phone);
-        phone.mCi.setOnNewGsmBroadcastSms(getHandler(), EVENT_NEW_SMS_MESSAGE, null);
-
-        IntentFilter intentFilter = new IntentFilter();
-        intentFilter.addAction(Intent.ACTION_AIRPLANE_MODE_CHANGED);
-        mContext.registerReceiver(
-                new BroadcastReceiver() {
-                    @Override
-                    public void onReceive(Context context, Intent intent) {
-                        switch (intent.getAction()) {
-                            case Intent.ACTION_AIRPLANE_MODE_CHANGED:
-                                boolean airplaneModeOn = intent.getBooleanExtra("state", false);
-                                if (airplaneModeOn) {
-                                    mLastAirplaneModeTime = System.currentTimeMillis();
-                                }
-                                break;
-                        }
-
-                    }
-                }, intentFilter);
-    }
-
-    @Override
-    protected void onQuitting() {
-        mPhone.mCi.unSetOnNewGsmBroadcastSms(getHandler());
-        super.onQuitting();     // release wakelock
-    }
-
-    /**
-     * Create a new CellBroadcastHandler.
-     * @param context the context to use for dispatching Intents
-     * @return the new handler
-     */
-    public static GsmCellBroadcastHandler makeGsmCellBroadcastHandler(Context context,
-            Phone phone) {
-        GsmCellBroadcastHandler handler = new GsmCellBroadcastHandler(context, phone);
-        handler.start();
-        return handler;
-    }
-
-    /**
-     * Find the cell broadcast messages specify by the geo-fencing trigger message and perform a
-     * geo-fencing check for these messages.
-     * @param geoFencingTriggerMessage the trigger message
-     *
-     * @return {@code True} if geo-fencing is need for some cell broadcast message.
-     */
-    private boolean handleGeoFencingTriggerMessage(
-            GeoFencingTriggerMessage geoFencingTriggerMessage) {
-        final List<SmsCbMessage> cbMessages = new ArrayList<>();
-        final List<Uri> cbMessageUris = new ArrayList<>();
-
-        long lastReceivedTime = System.currentTimeMillis() - DateUtils.DAY_IN_MILLIS;
-        Resources res = mContext.getResources();
-        if (res.getBoolean(com.android.internal.R.bool.reset_geo_fencing_check_after_boot_or_apm)) {
-            // Check last airplane mode time
-            lastReceivedTime = Long.max(lastReceivedTime, mLastAirplaneModeTime);
-            // Check last boot up time
-            lastReceivedTime = Long.max(lastReceivedTime,
-                    System.currentTimeMillis() - SystemClock.elapsedRealtime());
-        }
-
-        // Find the cell broadcast message identify by the message identifier and serial number
-        // and is not broadcasted.
-        String where = CellBroadcasts.SERVICE_CATEGORY + "=? AND "
-                + CellBroadcasts.SERIAL_NUMBER + "=? AND "
-                + CellBroadcasts.MESSAGE_BROADCASTED + "=? AND "
-                + CellBroadcasts.RECEIVED_TIME + ">?";
-
-        ContentResolver resolver = mContext.getContentResolver();
-        for (CellBroadcastIdentity identity : geoFencingTriggerMessage.cbIdentifiers) {
-            try (Cursor cursor = resolver.query(CELL_BROADCAST_URI,
-                    CellBroadcasts.QUERY_COLUMNS_FWK,
-                    where,
-                    new String[] { Integer.toString(identity.messageIdentifier),
-                            Integer.toString(identity.serialNumber), MESSAGE_NOT_BROADCASTED,
-                            Long.toString(lastReceivedTime) },
-                    null /* sortOrder */)) {
-                if (cursor != null) {
-                    while (cursor.moveToNext()) {
-                        cbMessages.add(SmsCbMessage.createFromCursor(cursor));
-                        cbMessageUris.add(ContentUris.withAppendedId(CELL_BROADCAST_URI,
-                                cursor.getInt(cursor.getColumnIndex(CellBroadcasts._ID))));
-                    }
-                }
-            }
-        }
-
-        log("Found " + cbMessages.size() + " messages since "
-                + DateFormat.getDateTimeInstance().format(lastReceivedTime));
-
-        List<Geometry> commonBroadcastArea = new ArrayList<>();
-        if (geoFencingTriggerMessage.shouldShareBroadcastArea()) {
-            for (SmsCbMessage msg : cbMessages) {
-                if (msg.getGeometries() != null) {
-                    commonBroadcastArea.addAll(msg.getGeometries());
-                }
-            }
-        }
-
-        // ATIS doesn't specify the geo fencing maximum wait time for the cell broadcasts specified
-        // in geo fencing trigger message. We will pick the largest maximum wait time among these
-        // cell broadcasts.
-        int maximumWaitTimeSec = 0;
-        for (SmsCbMessage msg : cbMessages) {
-            maximumWaitTimeSec = Math.max(maximumWaitTimeSec, msg.getMaximumWaitingTime());
-        }
-
-        if (DBG) {
-            logd("Geo-fencing trigger message = " + geoFencingTriggerMessage);
-            for (SmsCbMessage msg : cbMessages) {
-                logd(msg.toString());
-            }
-        }
-
-        if (cbMessages.isEmpty()) {
-            if (DBG) logd("No CellBroadcast message need to be broadcasted");
-            return false;
-        }
-
-        requestLocationUpdate(location -> {
-            if (location == null) {
-                // If the location is not available, broadcast the messages directly.
-                broadcastMessage(cbMessages, cbMessageUris);
-            } else {
-                for (int i = 0; i < cbMessages.size(); i++) {
-                    List<Geometry> broadcastArea = !commonBroadcastArea.isEmpty()
-                            ? commonBroadcastArea : cbMessages.get(i).getGeometries();
-                    if (broadcastArea == null || broadcastArea.isEmpty()) {
-                        broadcastMessage(cbMessages.get(i), cbMessageUris.get(i));
-                    } else {
-                        performGeoFencing(cbMessages.get(i), cbMessageUris.get(i), broadcastArea,
-                                location);
-                    }
-                }
-            }
-        }, maximumWaitTimeSec);
-        return true;
-    }
-
-    /**
-     * Handle 3GPP-format Cell Broadcast messages sent from radio.
-     *
-     * @param message the message to process
-     * @return true if need to wait for geo-fencing or an ordered broadcast was sent.
-     */
-    @Override
-    protected boolean handleSmsMessage(Message message) {
-        if (message.obj instanceof AsyncResult) {
-            SmsCbHeader header = createSmsCbHeader((AsyncResult) message.obj);
-            if (header == null) return false;
-
-            AsyncResult ar = (AsyncResult) message.obj;
-            byte[] pdu = (byte[]) ar.result;
-            if (header.getServiceCategory() == MESSAGE_ID_CMAS_GEO_FENCING_TRIGGER) {
-                GeoFencingTriggerMessage triggerMessage =
-                        GsmSmsCbMessage.createGeoFencingTriggerMessage(pdu);
-                if (triggerMessage != null) {
-                    return handleGeoFencingTriggerMessage(triggerMessage);
-                }
-            } else {
-                SmsCbMessage cbMessage = handleGsmBroadcastSms(header, ar);
-                if (cbMessage != null) {
-                    handleBroadcastSms(cbMessage);
-                    return true;
-                }
-                if (VDBG) log("Not handled GSM broadcasts.");
-            }
-        }
-        return super.handleSmsMessage(message);
-    }
-
-    /**
-     * Handle 3GPP format SMS-CB message.
-     * @param header the cellbroadcast header.
-     * @param ar the AsyncResult containing the received PDUs
-     */
-    private SmsCbMessage handleGsmBroadcastSms(SmsCbHeader header, AsyncResult ar) {
-        try {
-            byte[] receivedPdu = (byte[]) ar.result;
-
-            if (VDBG) {
-                int pduLength = receivedPdu.length;
-                for (int i = 0; i < pduLength; i += 8) {
-                    StringBuilder sb = new StringBuilder("SMS CB pdu data: ");
-                    for (int j = i; j < i + 8 && j < pduLength; j++) {
-                        int b = receivedPdu[j] & 0xff;
-                        if (b < 0x10) {
-                            sb.append('0');
-                        }
-                        sb.append(Integer.toHexString(b)).append(' ');
-                    }
-                    log(sb.toString());
-                }
-            }
-
-            if (VDBG) log("header=" + header);
-            String plmn = TelephonyManager.from(mContext).getNetworkOperatorForPhone(
-                    mPhone.getPhoneId());
-            int lac = -1;
-            int cid = -1;
-            CellLocation cl = mPhone.getCellLocation();
-            // Check if cell location is GsmCellLocation.  This is required to support
-            // dual-mode devices such as CDMA/LTE devices that require support for
-            // both 3GPP and 3GPP2 format messages
-            if (cl instanceof GsmCellLocation) {
-                GsmCellLocation cellLocation = (GsmCellLocation)cl;
-                lac = cellLocation.getLac();
-                cid = cellLocation.getCid();
-            }
-
-            SmsCbLocation location;
-            switch (header.getGeographicalScope()) {
-                case SmsCbMessage.GEOGRAPHICAL_SCOPE_LA_WIDE:
-                    location = new SmsCbLocation(plmn, lac, -1);
-                    break;
-
-                case SmsCbMessage.GEOGRAPHICAL_SCOPE_CELL_WIDE:
-                case SmsCbMessage.GEOGRAPHICAL_SCOPE_CELL_WIDE_IMMEDIATE:
-                    location = new SmsCbLocation(plmn, lac, cid);
-                    break;
-
-                case SmsCbMessage.GEOGRAPHICAL_SCOPE_PLMN_WIDE:
-                default:
-                    location = new SmsCbLocation(plmn);
-                    break;
-            }
-
-            byte[][] pdus;
-            int pageCount = header.getNumberOfPages();
-            if (pageCount > 1) {
-                // Multi-page message
-                SmsCbConcatInfo concatInfo = new SmsCbConcatInfo(header, location);
-
-                // Try to find other pages of the same message
-                pdus = mSmsCbPageMap.get(concatInfo);
-
-                if (pdus == null) {
-                    // This is the first page of this message, make room for all
-                    // pages and keep until complete
-                    pdus = new byte[pageCount][];
-
-                    mSmsCbPageMap.put(concatInfo, pdus);
-                }
-
-                if (VDBG) log("pdus size=" + pdus.length);
-                // Page parameter is one-based
-                pdus[header.getPageIndex() - 1] = receivedPdu;
-
-                for (byte[] pdu : pdus) {
-                    if (pdu == null) {
-                        // Still missing pages, exit
-                        log("still missing pdu");
-                        return null;
-                    }
-                }
-
-                // Message complete, remove and dispatch
-                mSmsCbPageMap.remove(concatInfo);
-            } else {
-                // Single page message
-                pdus = new byte[1][];
-                pdus[0] = receivedPdu;
-            }
-
-            // Remove messages that are out of scope to prevent the map from
-            // growing indefinitely, containing incomplete messages that were
-            // never assembled
-            Iterator<SmsCbConcatInfo> iter = mSmsCbPageMap.keySet().iterator();
-
-            while (iter.hasNext()) {
-                SmsCbConcatInfo info = iter.next();
-
-                if (!info.matchesLocation(plmn, lac, cid)) {
-                    iter.remove();
-                }
-            }
-
-            return GsmSmsCbMessage.createSmsCbMessage(mContext, header, location, pdus);
-
-        } catch (RuntimeException e) {
-            loge("Error in decoding SMS CB pdu", e);
-            return null;
-        }
-    }
-
-    private SmsCbHeader createSmsCbHeader(AsyncResult ar) {
-        try {
-            return new SmsCbHeader((byte[]) ar.result);
-        } catch (Exception ex) {
-            loge("Can't create SmsCbHeader, ex = " + ex.toString());
-            return null;
-        }
-    }
-
-    /**
-     * Holds all info about a message page needed to assemble a complete concatenated message.
-     */
-    private static final class SmsCbConcatInfo {
-
-        private final SmsCbHeader mHeader;
-        private final SmsCbLocation mLocation;
-
-        SmsCbConcatInfo(SmsCbHeader header, SmsCbLocation location) {
-            mHeader = header;
-            mLocation = location;
-        }
-
-        @Override
-        public int hashCode() {
-            return (mHeader.getSerialNumber() * 31) + mLocation.hashCode();
-        }
-
-        @Override
-        public boolean equals(Object obj) {
-            if (obj instanceof SmsCbConcatInfo) {
-                SmsCbConcatInfo other = (SmsCbConcatInfo)obj;
-
-                // Two pages match if they have the same serial number (which includes the
-                // geographical scope and update number), and both pages belong to the same
-                // location (PLMN, plus LAC and CID if these are part of the geographical scope).
-                return mHeader.getSerialNumber() == other.mHeader.getSerialNumber()
-                        && mLocation.equals(other.mLocation);
-            }
-
-            return false;
-        }
-
-        /**
-         * Compare the location code for this message to the current location code. The match is
-         * relative to the geographical scope of the message, which determines whether the LAC
-         * and Cell ID are saved in mLocation or set to -1 to match all values.
-         *
-         * @param plmn the current PLMN
-         * @param lac the current Location Area (GSM) or Service Area (UMTS)
-         * @param cid the current Cell ID
-         * @return true if this message is valid for the current location; false otherwise
-         */
-        public boolean matchesLocation(String plmn, int lac, int cid) {
-            return mLocation.isInLocationArea(plmn, lac, cid);
-        }
-    }
-}
diff --git a/src/java/com/android/internal/telephony/gsm/GsmInboundSmsHandler.java b/src/java/com/android/internal/telephony/gsm/GsmInboundSmsHandler.java
index 9e24534..47f75ca 100644
--- a/src/java/com/android/internal/telephony/gsm/GsmInboundSmsHandler.java
+++ b/src/java/com/android/internal/telephony/gsm/GsmInboundSmsHandler.java
@@ -17,8 +17,14 @@
 package com.android.internal.telephony.gsm;
 
 import android.app.Activity;
+import android.compat.annotation.UnsupportedAppUsage;
+import android.content.BroadcastReceiver;
 import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.AsyncResult;
 import android.os.Message;
+import android.os.SystemProperties;
 import android.provider.Telephony.Sms.Intents;
 
 import com.android.internal.telephony.CommandsInterface;
@@ -37,18 +43,78 @@
  */
 public class GsmInboundSmsHandler extends InboundSmsHandler {
 
+    private static BroadcastReceiver sTestBroadcastReceiver;
     /** Handler for SMS-PP data download messages to UICC. */
     private final UsimDataDownloadHandler mDataDownloadHandler;
 
+    // When TEST_MODE is on we allow the test intent to trigger an SMS CB alert
+    private static final boolean TEST_MODE = SystemProperties.getInt("ro.debuggable", 0) == 1;
+    private static final String TEST_ACTION = "com.android.internal.telephony.gsm"
+            + ".TEST_TRIGGER_CELL_BROADCAST";
+
     /**
      * Create a new GSM inbound SMS handler.
      */
     private GsmInboundSmsHandler(Context context, SmsStorageMonitor storageMonitor,
             Phone phone) {
-        super("GsmInboundSmsHandler", context, storageMonitor, phone,
-                GsmCellBroadcastHandler.makeGsmCellBroadcastHandler(context, phone));
+        super("GsmInboundSmsHandler", context, storageMonitor, phone);
         phone.mCi.setOnNewGsmSms(getHandler(), EVENT_NEW_SMS, null);
         mDataDownloadHandler = new UsimDataDownloadHandler(phone.mCi, phone.getPhoneId());
+        mCellBroadcastServiceManager.enable();
+
+        if (TEST_MODE) {
+            if (sTestBroadcastReceiver == null) {
+                sTestBroadcastReceiver = new GsmCbTestBroadcastReceiver();
+                IntentFilter filter = new IntentFilter();
+                filter.addAction(TEST_ACTION);
+                context.registerReceiver(sTestBroadcastReceiver, filter);
+            }
+        }
+    }
+
+
+    /**
+     * A broadcast receiver used for testing emergency cell broadcasts. To trigger test GSM cell
+     * broadcasts with adb run e.g:
+     *
+     * adb shell am broadcast -a com.android.internal.telephony.gsm.TEST_TRIGGER_CELL_BROADCAST \
+     * --es pdu_string  0000110011010D0A5BAE57CE770C531790E85C716CBF3044573065B9306757309707767 \
+     * A751F30025F37304463FA308C306B5099304830664E0B30553044FF086C178C615E81FF09000000000000000 \
+     * 0000000000000
+     *
+     * adb shell am broadcast -a com.android.internal.telephony.gsm.TEST_TRIGGER_CELL_BROADCAST \
+     * --es pdu_string  0000110011010D0A5BAE57CE770C531790E85C716CBF3044573065B9306757309707767 \
+     * A751F30025F37304463FA308C306B5099304830664E0B30553044FF086C178C615E81FF09000000000000000 \
+     * 0000000000000 --ei phone_id 0
+     */
+    private class GsmCbTestBroadcastReceiver extends CbTestBroadcastReceiver {
+
+        GsmCbTestBroadcastReceiver() {
+            super(TEST_ACTION);
+        }
+
+        @Override
+        protected void handleTestAction(Intent intent) {
+            byte[] smsPdu = intent.getByteArrayExtra("pdu");
+            if (smsPdu == null) {
+                String pduString = intent.getStringExtra("pdu_string");
+                smsPdu = decodeHexString(pduString);
+            }
+            if (smsPdu == null) {
+                log("No pdu or pdu_string extra, ignoring CB test intent");
+                return;
+            }
+
+            // Return early if phone_id is explicilty included and does not match mPhone.
+            // If phone_id extra is not included, continue.
+            int phoneId = mPhone.getPhoneId();
+            if (intent.getIntExtra("phone_id", phoneId) != phoneId) {
+                return;
+            }
+            Message m = Message.obtain();
+            AsyncResult.forMessage(m, smsPdu, null);
+            mCellBroadcastServiceManager.sendGsmMessageToHandler(m);
+        }
     }
 
     /**
@@ -57,7 +123,6 @@
     @Override
     protected void onQuitting() {
         mPhone.mCi.unSetOnNewGsmSms(getHandler());
-        mCellBroadcastHandler.dispose();
 
         if (DBG) log("unregistered for 3GPP SMS");
         super.onQuitting();     // release wakelock
@@ -75,6 +140,7 @@
 
     /**
      * Return true if this handler is for 3GPP2 messages; false for 3GPP format.
+     *
      * @return false (3GPP)
      */
     @Override
@@ -88,7 +154,7 @@
      *
      * @param smsb the SmsMessageBase object from the RIL
      * @return a result code from {@link android.provider.Telephony.Sms.Intents},
-     *  or {@link Activity#RESULT_OK} for delayed acknowledgment to SMSC
+     * or {@link Activity#RESULT_OK} for delayed acknowledgment to SMSC
      */
     @Override
     protected int dispatchMessageRadioSpecific(SmsMessageBase smsb) {
@@ -158,10 +224,12 @@
 
     /**
      * Send an acknowledge message.
+     *
      * @param success indicates that last message was successfully received.
      * @param result result code indicating any error
      * @param response callback message sent when operation completes.
      */
+    @UnsupportedAppUsage
     @Override
     protected void acknowledgeLastIncomingSms(boolean success, int result, Message response) {
         mPhone.mCi.acknowledgeLastIncomingGsmSms(success, resultToCause(result), response);
@@ -169,6 +237,7 @@
 
     /**
      * Convert Android result code to 3GPP SMS failure cause.
+     *
      * @param rc the Android SMS intent result value
      * @return 0 for success, or a 3GPP SMS failure cause value
      */
diff --git a/src/java/com/android/internal/telephony/gsm/GsmMmiCode.java b/src/java/com/android/internal/telephony/gsm/GsmMmiCode.java
index 2a6c848..9714de0 100644
--- a/src/java/com/android/internal/telephony/gsm/GsmMmiCode.java
+++ b/src/java/com/android/internal/telephony/gsm/GsmMmiCode.java
@@ -27,6 +27,7 @@
 import static com.android.internal.telephony.CommandsInterface.SERVICE_CLASS_SMS;
 import static com.android.internal.telephony.CommandsInterface.SERVICE_CLASS_VOICE;
 
+import android.compat.annotation.UnsupportedAppUsage;
 import android.content.Context;
 import android.content.res.Resources;
 import android.os.AsyncResult;
@@ -36,7 +37,6 @@
 import android.os.ResultReceiver;
 import android.telephony.CarrierConfigManager;
 import android.telephony.PhoneNumberUtils;
-import android.telephony.Rlog;
 import android.text.BidiFormatter;
 import android.text.SpannableStringBuilder;
 import android.text.TextDirectionHeuristics;
@@ -53,7 +53,8 @@
 import com.android.internal.telephony.uicc.IccCardApplicationStatus.AppState;
 import com.android.internal.telephony.uicc.IccRecords;
 import com.android.internal.telephony.uicc.UiccCardApplication;
-import com.android.internal.util.ArrayUtils;
+import com.android.internal.telephony.util.ArrayUtils;
+import com.android.telephony.Rlog;
 
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
@@ -136,15 +137,25 @@
 
     //***** Instance Variables
 
+    @UnsupportedAppUsage
     GsmCdmaPhone mPhone;
+    @UnsupportedAppUsage
     Context mContext;
     UiccCardApplication mUiccApplication;
+    @UnsupportedAppUsage
     IccRecords mIccRecords;
 
     String mAction;              // One of ACTION_*
+    @UnsupportedAppUsage
     String mSc;                  // Service Code
-    String mSia, mSib, mSic;       // Service Info a,b,c
+    @UnsupportedAppUsage
+    String mSia;                 // Service Info a
+    @UnsupportedAppUsage
+    String mSib;                 // Service Info b
+    @UnsupportedAppUsage
+    String mSic;                 // Service Info c
     String mPoundString;         // Entire MMI string up to and including #
+    @UnsupportedAppUsage
     public String mDialingNumber;
     String mPwd;                 // For password registration
 
@@ -154,6 +165,9 @@
     private boolean mIsUssdRequest;
 
     private boolean mIsCallFwdReg;
+
+    private boolean mIsNetworkInitiatedUSSD;
+
     State mState = State.PENDING;
     CharSequence mMessage;
     private boolean mIsSsInfo = false;
@@ -165,6 +179,7 @@
 
     // See TS 22.030 6.5.2 "Structure of the MMI"
 
+    @UnsupportedAppUsage
     static Pattern sPatternSuppService = Pattern.compile(
         "((\\*|#|\\*#|\\*\\*|##)(\\d{2,3})(\\*([^*#]*)(\\*([^*#]*)(\\*([^*#]*)(\\*([^*#]*))?)?)?)?#)(.*)");
 /*       1  2                    3          4  5       6   7         8    9     10  11             12
@@ -205,6 +220,7 @@
      *
      * Please see flow chart in TS 22.030 6.5.3.2
      */
+    @UnsupportedAppUsage
     public static GsmMmiCode newFromDialString(String dialString, GsmCdmaPhone phone,
             UiccCardApplication app) {
         return newFromDialString(dialString, phone, app, null);
@@ -258,7 +274,7 @@
 
             ret = new GsmMmiCode(phone, app);
             ret.mPoundString = dialString;
-        } else if (isTwoDigitShortCode(phone.getContext(), dialString)) {
+        } else if (isTwoDigitShortCode(phone.getContext(), phone.getSubId(), dialString)) {
             //Is a country-specific exception to short codes as defined in TS 22.030, 6.5.3.2
             ret = null;
         } else if (isShortCode(dialString, phone)) {
@@ -302,6 +318,7 @@
 
         ret.mMessage = ussdMessage;
         ret.mIsUssdRequest = isUssdRequest;
+        ret.mIsNetworkInitiatedUSSD = true;
 
         // If it's a request, set to PENDING so that it's cancelable.
         if (isUssdRequest) {
@@ -367,12 +384,7 @@
                             isServiceClassVoiceorNone(ssData.serviceClass));
 
                     Rlog.d(LOG_TAG, "setVoiceCallForwardingFlag cffEnabled: " + cffEnabled);
-                    if (mIccRecords != null) {
-                        mPhone.setVoiceCallForwardingFlag(1, cffEnabled, null);
-                        Rlog.d(LOG_TAG, "setVoiceCallForwardingFlag done from SS Info.");
-                    } else {
-                        Rlog.e(LOG_TAG, "setVoiceCallForwardingFlag aborted. sim records is null.");
-                    }
+                    mPhone.setVoiceCallForwardingFlag(1, cffEnabled, null);
                 }
                 onSetComplete(null, new AsyncResult(null, ssData.cfInfo, ex));
                 break;
@@ -461,6 +473,7 @@
     /** make empty strings be null.
      *  Regexp returns empty strings for empty groups
      */
+    @UnsupportedAppUsage
     private static String
     makeEmptyNull (String s) {
         if (s != null && s.length() == 0) return null;
@@ -498,6 +511,7 @@
         }
     }
 
+    @UnsupportedAppUsage
     private static int
     siToServiceClass(String si) {
         if (si == null || si.length() == 0) {
@@ -547,6 +561,7 @@
         }
     }
 
+    @UnsupportedAppUsage
     static boolean
     isServiceCodeCallForwarding(String sc) {
         return sc != null &&
@@ -556,6 +571,7 @@
                 || sc.equals(SC_CF_All_Conditional));
     }
 
+    @UnsupportedAppUsage
     static boolean
     isServiceCodeCallBarring(String sc) {
         Resources resource = Resources.getSystem();
@@ -600,6 +616,7 @@
 
     //***** Constructor
 
+    @UnsupportedAppUsage
     public GsmMmiCode(GsmCdmaPhone phone, UiccCardApplication app) {
         // The telephony unit-test cases may create GsmMmiCode's
         // in secondary threads
@@ -669,6 +686,11 @@
         return mIsPendingUSSD;
     }
 
+    @Override
+    public boolean isNetworkInitiatedUssd() {
+        return mIsNetworkInitiatedUSSD;
+    }
+
     //***** Instance Methods
 
     /** Does this dial string contain a structured or unstructured MMI code? */
@@ -690,15 +712,16 @@
         return mPoundString;
     }
 
-    static private boolean
-    isTwoDigitShortCode(Context context, String dialString) {
+    /**
+     * Check if the dial string match the two digital number pattern which defined by Carrier.
+     */
+    public static boolean isTwoDigitShortCode(Context context, int subId, String dialString) {
         Rlog.d(LOG_TAG, "isTwoDigitShortCode");
 
         if (dialString == null || dialString.length() > 2) return false;
 
         if (sTwoDigitNumberPattern == null) {
-            sTwoDigitNumberPattern = context.getResources().getStringArray(
-                    com.android.internal.R.array.config_twoDigitNumberPattern);
+            sTwoDigitNumberPattern = getTwoDigitNumberPattern(context, subId);
         }
 
         for (String dialnumber : sTwoDigitNumberPattern) {
@@ -712,6 +735,27 @@
         return false;
     }
 
+    private static String[] getTwoDigitNumberPattern(Context context, int subId) {
+        Rlog.d(LOG_TAG, "Get two digit number pattern: subId=" + subId);
+        String[] twoDigitNumberPattern = null;
+        CarrierConfigManager configManager = (CarrierConfigManager)
+                context.getSystemService(Context.CARRIER_CONFIG_SERVICE);
+        if (configManager != null) {
+            PersistableBundle bundle = configManager.getConfigForSubId(subId);
+            if (bundle != null) {
+                Rlog.d(LOG_TAG, "Two Digit Number Pattern from carrir config");
+                twoDigitNumberPattern = bundle.getStringArray(CarrierConfigManager
+                        .KEY_MMI_TWO_DIGIT_NUMBER_PATTERN_STRING_ARRAY);
+            }
+        }
+
+        // Do NOT return null array
+        if (twoDigitNumberPattern == null) {
+            twoDigitNumberPattern = new String[0];
+        }
+        return twoDigitNumberPattern;
+    }
+
     /**
      * Helper function for newFromDialString. Returns true if dialString appears
      * to be a short code AND conditions are correct for it to be treated as
@@ -782,6 +826,7 @@
      *  In temporary mode, to invoke CLIR for a single call enter:
      *       " # 31 # [called number] SEND "
      */
+    @UnsupportedAppUsage
     public boolean
     isTemporaryModeCLIR() {
         return mSc != null && mSc.equals(SC_CLIR) && mDialingNumber != null
@@ -792,6 +837,7 @@
      * returns CommandsInterface.CLIR_*
      * See also isTemporaryModeCLIR()
      */
+    @UnsupportedAppUsage
     public int
     getCLIRMode() {
         if (mSc != null && mSc.equals(SC_CLIR)) {
@@ -829,22 +875,27 @@
         return false;
     }
 
+    @UnsupportedAppUsage
     boolean isActivate() {
         return mAction != null && mAction.equals(ACTION_ACTIVATE);
     }
 
+    @UnsupportedAppUsage
     boolean isDeactivate() {
         return mAction != null && mAction.equals(ACTION_DEACTIVATE);
     }
 
+    @UnsupportedAppUsage
     boolean isInterrogate() {
         return mAction != null && mAction.equals(ACTION_INTERROGATE);
     }
 
+    @UnsupportedAppUsage
     boolean isRegister() {
         return mAction != null && mAction.equals(ACTION_REGISTER);
     }
 
+    @UnsupportedAppUsage
     boolean isErasure() {
         return mAction != null && mAction.equals(ACTION_ERASURE);
     }
@@ -874,6 +925,7 @@
     }
 
     /** Process a MMI code or short code...anything that isn't a dialing number */
+    @UnsupportedAppUsage
     public void
     processCode() throws CallStateException {
         try {
@@ -894,10 +946,10 @@
                 }
             } else if (mSc != null && mSc.equals(SC_CLIR)) {
                 Rlog.d(LOG_TAG, "processCode: is CLIR");
-                if (isActivate()) {
+                if (isActivate() && !mPhone.isClirActivationAndDeactivationPrevented()) {
                     mPhone.mCi.setCLIR(CommandsInterface.CLIR_INVOCATION,
                         obtainMessage(EVENT_SET_COMPLETE, this));
-                } else if (isDeactivate()) {
+                } else if (isDeactivate() && !mPhone.isClirActivationAndDeactivationPrevented()) {
                     mPhone.mCi.setCLIR(CommandsInterface.CLIR_SUPPRESSION,
                         obtainMessage(EVENT_SET_COMPLETE, this));
                 } else if (isInterrogate()) {
@@ -1183,9 +1235,7 @@
                 */
                 if ((ar.exception == null) && (msg.arg1 == 1)) {
                     boolean cffEnabled = (msg.arg2 == 1);
-                    if (mIccRecords != null) {
-                        mPhone.setVoiceCallForwardingFlag(1, cffEnabled, mDialingNumber);
-                    }
+                    mPhone.setVoiceCallForwardingFlag(1, cffEnabled, mDialingNumber);
                 }
 
                 onSetComplete(msg, ar);
@@ -1264,6 +1314,7 @@
         return mContext.getText(com.android.internal.R.string.mmiError);
     }
 
+    @UnsupportedAppUsage
     private CharSequence getScString() {
         if (mSc != null) {
             if (isServiceCodeCallBarring(mSc)) {
@@ -1558,9 +1609,7 @@
                 (info.serviceClass & serviceClassMask)
                         == CommandsInterface.SERVICE_CLASS_VOICE) {
             boolean cffEnabled = (info.status == 1);
-            if (mIccRecords != null) {
-                mPhone.setVoiceCallForwardingFlag(1, cffEnabled, info.number);
-            }
+            mPhone.setVoiceCallForwardingFlag(1, cffEnabled, info.number);
         }
 
         return TextUtils.replace(template, sources, destinations);
@@ -1587,14 +1636,12 @@
 
             infos = (CallForwardInfo[]) ar.result;
 
-            if (infos.length == 0) {
+            if (infos == null || infos.length == 0) {
                 // Assume the default is not active
                 sb.append(mContext.getText(com.android.internal.R.string.serviceDisabled));
 
                 // Set unconditional CFF in SIM to false
-                if (mIccRecords != null) {
-                    mPhone.setVoiceCallForwardingFlag(1, false, null);
-                }
+                mPhone.setVoiceCallForwardingFlag(1, false, null);
             } else {
 
                 SpannableStringBuilder tb = new SpannableStringBuilder();
@@ -1669,7 +1716,8 @@
     private CharSequence
     createQueryCallWaitingResultMessage(int serviceClass) {
         StringBuilder sb =
-                new StringBuilder(mContext.getText(com.android.internal.R.string.serviceEnabledFor));
+                new StringBuilder(
+                        mContext.getText(com.android.internal.R.string.serviceEnabledFor));
 
         for (int classMask = 1
                     ; classMask <= SERVICE_CLASS_MAX
@@ -1685,7 +1733,8 @@
     private CharSequence
     createQueryCallBarringResultMessage(int serviceClass)
     {
-        StringBuilder sb = new StringBuilder(mContext.getText(com.android.internal.R.string.serviceEnabledFor));
+        StringBuilder sb = new StringBuilder(
+                mContext.getText(com.android.internal.R.string.serviceEnabledFor));
 
         for (int classMask = 1
                     ; classMask <= SERVICE_CLASS_MAX
diff --git a/src/java/com/android/internal/telephony/gsm/GsmSMSDispatcher.java b/src/java/com/android/internal/telephony/gsm/GsmSMSDispatcher.java
index 99082ee..8a80b45 100644
--- a/src/java/com/android/internal/telephony/gsm/GsmSMSDispatcher.java
+++ b/src/java/com/android/internal/telephony/gsm/GsmSMSDispatcher.java
@@ -16,19 +16,21 @@
 
 package com.android.internal.telephony.gsm;
 
+import static com.android.internal.telephony.SmsResponse.NO_ERROR_CODE;
+
+import android.compat.annotation.UnsupportedAppUsage;
 import android.os.AsyncResult;
 import android.os.Message;
 import android.provider.Telephony.Sms.Intents;
-import android.telephony.Rlog;
 import android.telephony.ServiceState;
 import android.util.Pair;
 
 import com.android.internal.telephony.GsmAlphabet.TextEncodingDetails;
 import com.android.internal.telephony.InboundSmsHandler;
 import com.android.internal.telephony.Phone;
+import com.android.internal.telephony.SMSDispatcher;
 import com.android.internal.telephony.SmsConstants;
 import com.android.internal.telephony.SmsDispatchersController;
-import com.android.internal.telephony.SMSDispatcher;
 import com.android.internal.telephony.SmsHeader;
 import com.android.internal.telephony.SmsMessageBase;
 import com.android.internal.telephony.uicc.IccRecords;
@@ -36,6 +38,7 @@
 import com.android.internal.telephony.uicc.UiccCardApplication;
 import com.android.internal.telephony.uicc.UiccController;
 import com.android.internal.telephony.util.SMSDispatcherUtil;
+import com.android.telephony.Rlog;
 
 import java.util.HashMap;
 import java.util.concurrent.atomic.AtomicReference;
@@ -46,6 +49,7 @@
     private AtomicReference<IccRecords> mIccRecords = new AtomicReference<IccRecords>();
     private AtomicReference<UiccCardApplication> mUiccApplication =
             new AtomicReference<UiccCardApplication>();
+    @UnsupportedAppUsage
     private GsmInboundSmsHandler mGsmInboundSmsHandler;
 
     /** Status report received */
@@ -68,6 +72,7 @@
         mUiccController.unregisterForIccChanged(this);
     }
 
+    @UnsupportedAppUsage
     @Override
     protected String getFormat() {
         return SmsConstants.FORMAT_3GPP;
@@ -136,6 +141,7 @@
     private void handleStatusReport(AsyncResult ar) {
         byte[] pdu = (byte[]) ar.result;
         SmsMessage sms = SmsMessage.newFromCDS(pdu);
+        boolean handled = false;
 
         if (sms != null) {
             int messageRef = sms.mMessageRef;
@@ -149,27 +155,49 @@
                     if (result.second) {
                         deliveryPendingList.remove(i);
                     }
-                    // Only expect to see one tracker matching this messageref
-                    break;
+                    handled = true;
+                    break; // Only expect to see one tracker matching this messageref
                 }
             }
+            if (!handled) {
+                // Try to find the sent SMS from the map in ImsSmsDispatcher.
+                mSmsDispatchersController.handleSentOverImsStatusReport(
+                        messageRef, getFormat(), pdu);
+            }
         }
         mCi.acknowledgeLastIncomingGsmSms(true, Intents.RESULT_SMS_HANDLED, null);
     }
 
     /** {@inheritDoc} */
+    @UnsupportedAppUsage
     @Override
     protected void sendSms(SmsTracker tracker) {
+        int ss = mPhone.getServiceState().getState();
+
+        Rlog.d(TAG, "sendSms: "
+                + " isIms()=" + isIms()
+                + " mRetryCount=" + tracker.mRetryCount
+                + " mImsRetry=" + tracker.mImsRetry
+                + " mMessageRef=" + tracker.mMessageRef
+                + " mUsesImsServiceForIms=" + tracker.mUsesImsServiceForIms
+                + " SS=" + ss
+                + " id=" + tracker.mMessageId);
+
+        // if sms over IMS is not supported on data and voice is not available...
+        if (!isIms() && ss != ServiceState.STATE_IN_SERVICE) {
+        //In 5G case only Data Rat is reported.
+            if(mPhone.getServiceState().getRilDataRadioTechnology()
+                    != ServiceState.RIL_RADIO_TECHNOLOGY_NR) {
+                tracker.onFailed(mContext, getNotInServiceError(ss), NO_ERROR_CODE);
+                return;
+            }
+        }
+
+        Message reply = obtainMessage(EVENT_SEND_SMS_COMPLETE, tracker);
         HashMap<String, Object> map = tracker.getData();
-
         byte pdu[] = (byte[]) map.get("pdu");
-
+        byte smsc[] = (byte[]) map.get("smsc");
         if (tracker.mRetryCount > 0) {
-            Rlog.d(TAG, "sendSms: "
-                    + " mRetryCount=" + tracker.mRetryCount
-                    + " mMessageRef=" + tracker.mMessageRef
-                    + " SS=" + mPhone.getServiceState().getState());
-
             // per TS 23.040 Section 9.2.3.6:  If TP-MTI SMS-SUBMIT (0x01) type
             //   TP-RD (bit 2) is 1 for retry
             //   and TP-MR is set to previously failed sms TP-MR
@@ -178,23 +206,6 @@
                 pdu[1] = (byte) tracker.mMessageRef; // TP-MR
             }
         }
-        Rlog.d(TAG, "sendSms: "
-                + " isIms()=" + isIms()
-                + " mRetryCount=" + tracker.mRetryCount
-                + " mImsRetry=" + tracker.mImsRetry
-                + " mMessageRef=" + tracker.mMessageRef
-                + " mUsesImsServiceForIms=" + tracker.mUsesImsServiceForIms
-                + " SS=" + mPhone.getServiceState().getState());
-
-        int ss = mPhone.getServiceState().getState();
-        // if sms over IMS is not supported on data and voice is not available...
-        if (!isIms() && ss != ServiceState.STATE_IN_SERVICE) {
-            tracker.onFailed(mContext, getNotInServiceError(ss), 0/*errorCode*/);
-            return;
-        }
-
-        byte smsc[] = (byte[]) map.get("smsc");
-        Message reply = obtainMessage(EVENT_SEND_SMS_COMPLETE, tracker);
 
         // sms over gsm is used:
         //   if sms over IMS is not supported AND
diff --git a/src/java/com/android/internal/telephony/gsm/SimTlv.java b/src/java/com/android/internal/telephony/gsm/SimTlv.java
index c98b9a1..ee0d3ff 100644
--- a/src/java/com/android/internal/telephony/gsm/SimTlv.java
+++ b/src/java/com/android/internal/telephony/gsm/SimTlv.java
@@ -16,6 +16,8 @@
 
 package com.android.internal.telephony.gsm;
 
+import android.compat.annotation.UnsupportedAppUsage;
+
 /**
  * SIM Tag-Length-Value record
  * TS 102 223 Annex C
@@ -33,8 +35,10 @@
     int mCurOffset;
     int mCurDataOffset;
     int mCurDataLength;
+    @UnsupportedAppUsage
     boolean mHasValidTlvObject;
 
+    @UnsupportedAppUsage
     public SimTlv(byte[] record, int offset, int length) {
         mRecord = record;
 
@@ -45,6 +49,7 @@
         mHasValidTlvObject = parseCurrentTlvObject();
     }
 
+    @UnsupportedAppUsage
     public boolean nextObject() {
         if (!mHasValidTlvObject) return false;
         mCurOffset = mCurDataOffset + mCurDataLength;
@@ -52,6 +57,7 @@
         return mHasValidTlvObject;
     }
 
+    @UnsupportedAppUsage
     public boolean isValidObject() {
         return mHasValidTlvObject;
     }
@@ -62,6 +68,7 @@
      * 0 and 0xff are invalid tag values
      * valid tags range from 1 - 0xfe
      */
+    @UnsupportedAppUsage
     public int getTag() {
         if (!mHasValidTlvObject) return 0;
         return mRecord[mCurOffset] & 0xff;
@@ -72,6 +79,7 @@
      * returns null if !isValidObject()
      */
 
+    @UnsupportedAppUsage
     public byte[] getData() {
         if (!mHasValidTlvObject) return null;
 
diff --git a/src/java/com/android/internal/telephony/gsm/SsData.java b/src/java/com/android/internal/telephony/gsm/SsData.java
index a5f67d8..41fa45e 100644
--- a/src/java/com/android/internal/telephony/gsm/SsData.java
+++ b/src/java/com/android/internal/telephony/gsm/SsData.java
@@ -19,11 +19,9 @@
 
 package com.android.internal.telephony.gsm;
 
-import android.telephony.Rlog;
 import com.android.internal.telephony.CallForwardInfo;
 import com.android.internal.telephony.GsmCdmaPhone;
-
-import java.util.ArrayList;
+import com.android.telephony.Rlog;
 
 /**
  * See also RIL_StkCcUnsolSsResponse in include/telephony/ril.h
diff --git a/src/java/com/android/internal/telephony/gsm/UsimDataDownloadHandler.java b/src/java/com/android/internal/telephony/gsm/UsimDataDownloadHandler.java
index 91cbbf0..5d08846 100644
--- a/src/java/com/android/internal/telephony/gsm/UsimDataDownloadHandler.java
+++ b/src/java/com/android/internal/telephony/gsm/UsimDataDownloadHandler.java
@@ -22,7 +22,6 @@
 import android.os.Message;
 import android.provider.Telephony.Sms.Intents;
 import android.telephony.PhoneNumberUtils;
-import android.telephony.Rlog;
 import android.telephony.SmsManager;
 
 import com.android.internal.telephony.CommandsInterface;
@@ -31,6 +30,7 @@
 import com.android.internal.telephony.uicc.IccIoResult;
 import com.android.internal.telephony.uicc.IccUtils;
 import com.android.internal.telephony.uicc.UsimServiceTable;
+import com.android.telephony.Rlog;
 
 /**
  * Handler for SMS-PP data download messages.
diff --git a/src/java/com/android/internal/telephony/gsm/UsimPhoneBookManager.java b/src/java/com/android/internal/telephony/gsm/UsimPhoneBookManager.java
index 6489014..f6fc069 100755
--- a/src/java/com/android/internal/telephony/gsm/UsimPhoneBookManager.java
+++ b/src/java/com/android/internal/telephony/gsm/UsimPhoneBookManager.java
@@ -16,10 +16,10 @@
 
 package com.android.internal.telephony.gsm;
 
+import android.compat.annotation.UnsupportedAppUsage;
 import android.os.AsyncResult;
 import android.os.Handler;
 import android.os.Message;
-import android.telephony.Rlog;
 import android.util.SparseArray;
 import android.util.SparseIntArray;
 
@@ -28,6 +28,8 @@
 import com.android.internal.telephony.uicc.IccConstants;
 import com.android.internal.telephony.uicc.IccFileHandler;
 import com.android.internal.telephony.uicc.IccUtils;
+import com.android.telephony.Rlog;
+
 import java.util.ArrayList;
 
 /**
@@ -41,9 +43,12 @@
     private static final boolean DBG = true;
     private ArrayList<PbrRecord> mPbrRecords;
     private Boolean mIsPbrPresent;
+    @UnsupportedAppUsage
     private IccFileHandler mFh;
     private AdnRecordCache mAdnCache;
+    @UnsupportedAppUsage
     private Object mLock = new Object();
+    @UnsupportedAppUsage
     private ArrayList<AdnRecord> mPhoneBookRecords;
     private ArrayList<byte[]> mIapFileRecord;
     private ArrayList<byte[]> mEmailFileRecord;
@@ -119,6 +124,7 @@
         mSfiEfidTable = new SparseIntArray();
     }
 
+    @UnsupportedAppUsage
     public void reset() {
         mPhoneBookRecords.clear();
         mIapFileRecord = null;
@@ -131,6 +137,7 @@
     }
 
     // Load all phonebook related EFs from the SIM.
+    @UnsupportedAppUsage
     public ArrayList<AdnRecord> loadEfFilesFromUsim() {
         synchronized (mLock) {
             if (!mPhoneBookRecords.isEmpty()) {
@@ -660,6 +667,7 @@
         }
     }
 
+    @UnsupportedAppUsage
     private void log(String msg) {
         if(DBG) Rlog.d(LOG_TAG, msg);
     }
diff --git a/src/java/com/android/internal/telephony/ims/ImsResolver.java b/src/java/com/android/internal/telephony/ims/ImsResolver.java
index c4dd115..2d293a1 100644
--- a/src/java/com/android/internal/telephony/ims/ImsResolver.java
+++ b/src/java/com/android/internal/telephony/ims/ImsResolver.java
@@ -17,6 +17,7 @@
 package com.android.internal.telephony.ims;
 
 import android.Manifest;
+import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.content.BroadcastReceiver;
 import android.content.ComponentName;
@@ -26,44 +27,57 @@
 import android.content.pm.PackageManager;
 import android.content.pm.ResolveInfo;
 import android.content.pm.ServiceInfo;
+import android.os.AsyncResult;
 import android.os.Handler;
 import android.os.Looper;
 import android.os.Message;
 import android.os.PersistableBundle;
 import android.os.RemoteException;
 import android.os.UserHandle;
+import android.os.UserManager;
 import android.telephony.CarrierConfigManager;
 import android.telephony.SubscriptionManager;
+import android.telephony.TelephonyManager;
 import android.telephony.ims.ImsService;
 import android.telephony.ims.aidl.IImsConfig;
 import android.telephony.ims.aidl.IImsMmTelFeature;
 import android.telephony.ims.aidl.IImsRcsFeature;
 import android.telephony.ims.aidl.IImsRegistration;
 import android.telephony.ims.feature.ImsFeature;
+import android.telephony.ims.feature.MmTelFeature;
 import android.telephony.ims.stub.ImsFeatureConfiguration;
 import android.text.TextUtils;
+import android.util.ArrayMap;
+import android.util.LocalLog;
 import android.util.Log;
 import android.util.SparseArray;
 
 import com.android.ims.internal.IImsServiceFeatureCallback;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.os.SomeArgs;
+import com.android.internal.telephony.PhoneConfigurationManager;
+import com.android.internal.util.IndentingPrintWriter;
 
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
 import java.util.Objects;
 import java.util.Set;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.TimeUnit;
 import java.util.stream.Collectors;
-import java.util.stream.Stream;
 
 /**
  * Creates a list of ImsServices that are available to bind to based on the Device configuration
- * overlay value "config_ims_package" and Carrier Configuration value
- * "config_ims_package_override_string".
- * These ImsServices are then bound to in the following order:
+ * overlay values "config_ims_rcs_package" and "config_ims_mmtel_package" as well as Carrier
+ * Configuration value "config_ims_rcs_package_override_string" and
+ * "config_ims_mmtel_package_override_string".
+ * These ImsServices are then bound to in the following order for each mmtel and rcs feature:
  *
  * 1. Carrier Config defined override value per SIM.
  * 2. Device overlay default value (including no SIM case).
@@ -75,10 +89,14 @@
 public class ImsResolver implements ImsServiceController.ImsServiceControllerCallbacks {
 
     private static final String TAG = "ImsResolver";
+    private static final int GET_IMS_SERVICE_TIMEOUT_MS = 5000;
 
+    @VisibleForTesting
     public static final String METADATA_EMERGENCY_MMTEL_FEATURE =
             "android.telephony.ims.EMERGENCY_MMTEL_FEATURE";
+    @VisibleForTesting
     public static final String METADATA_MMTEL_FEATURE = "android.telephony.ims.MMTEL_FEATURE";
+    @VisibleForTesting
     public static final String METADATA_RCS_FEATURE = "android.telephony.ims.RCS_FEATURE";
     // Overrides the sanity permission check of android.permission.BIND_IMS_SERVICE for any
     // ImsService that is connecting to the platform.
@@ -93,14 +111,31 @@
     private static final int HANDLER_CONFIG_CHANGED = 2;
     // A query has been started for an ImsService to relay the features they support.
     private static final int HANDLER_START_DYNAMIC_FEATURE_QUERY = 3;
-    // A query to request ImsService features has completed or the ImsService has updated features.
+    // A dynamic query to request ImsService features has completed.
     private static final int HANDLER_DYNAMIC_FEATURE_CHANGE = 4;
     // Testing: Overrides the current configuration for ImsService binding
     private static final int HANDLER_OVERRIDE_IMS_SERVICE_CONFIG = 5;
+    // Based on boot complete indication. When this happens, there may be ImsServices that are not
+    // direct boot aware that need to be started.
+    private static final int HANDLER_BOOT_COMPLETE = 6;
+    // Sent when the number of slots has dynamically changed on the device. We will need to
+    // resize available ImsServiceController slots and perform dynamic queries again.
+    private static final int HANDLER_MSIM_CONFIGURATION_CHANGE = 7;
 
     // Delay between dynamic ImsService queries.
     private static final int DELAY_DYNAMIC_QUERY_MS = 5000;
 
+    private static class OverrideConfig {
+        public final int slotId;
+        public final boolean isCarrierService;
+        public final Map<Integer, String> featureTypeToPackageMap;
+
+        OverrideConfig(int slotIndex, boolean isCarrier, Map<Integer, String> feature) {
+            slotId = slotIndex;
+            isCarrierService = isCarrier;
+            featureTypeToPackageMap = feature;
+        }
+    }
 
     /**
      * Stores information about an ImsService, including the package name, class name, and features
@@ -116,15 +151,13 @@
 
         // Map slotId->Feature
         private final HashSet<ImsFeatureConfiguration.FeatureSlotPair> mSupportedFeatures;
-        private final int mNumSlots;
 
-        public ImsServiceInfo(int numSlots) {
-            mNumSlots = numSlots;
+        public ImsServiceInfo() {
             mSupportedFeatures = new HashSet<>();
         }
 
-        void addFeatureForAllSlots(int feature) {
-            for (int i = 0; i < mNumSlots; i++) {
+        void addFeatureForAllSlots(int numSlots, int feature) {
+            for (int i = 0; i < numSlots; i++) {
                 mSupportedFeatures.add(new ImsFeatureConfiguration.FeatureSlotPair(i, feature));
             }
         }
@@ -135,7 +168,7 @@
         }
 
         @VisibleForTesting
-        public HashSet<ImsFeatureConfiguration.FeatureSlotPair> getSupportedFeatures() {
+        public Set<ImsFeatureConfiguration.FeatureSlotPair> getSupportedFeatures() {
             return mSupportedFeatures;
         }
 
@@ -165,18 +198,12 @@
 
         @Override
         public String toString() {
-            StringBuilder res = new StringBuilder();
-            res.append("[ImsServiceInfo] name=");
-            res.append(name);
-            res.append(", supportedFeatures=[ ");
-            for (ImsFeatureConfiguration.FeatureSlotPair feature : mSupportedFeatures) {
-                res.append("(");
-                res.append(feature.slotId);
-                res.append(",");
-                res.append(feature.featureType);
-                res.append(") ");
-            }
-            return res.toString();
+            return "[ImsServiceInfo] name="
+                    + name
+                    + ", featureFromMetadata="
+                    + featureFromMetadata
+                    + ","
+                    + printFeatures(mSupportedFeatures);
         }
     }
 
@@ -214,7 +241,19 @@
                     SubscriptionManager.INVALID_SIM_SLOT_INDEX);
 
             if (slotId == SubscriptionManager.INVALID_SIM_SLOT_INDEX) {
-                Log.i(TAG, "Received SIM change for invalid slot id.");
+                Log.i(TAG, "Received CCC for invalid slot id.");
+                return;
+            }
+
+            int subId = intent.getIntExtra(CarrierConfigManager.EXTRA_SUBSCRIPTION_INDEX,
+                    SubscriptionManager.INVALID_SUBSCRIPTION_ID);
+            int slotSimState = mTelephonyManagerProxy.getSimState(mContext, slotId);
+            if (subId == SubscriptionManager.INVALID_SUBSCRIPTION_ID
+                    && slotSimState != TelephonyManager.SIM_STATE_ABSENT) {
+                // We only care about carrier config updates that happen when a slot is known to be
+                // absent or populated and the carrier config has been loaded.
+                Log.i(TAG, "Received CCC for slot " + slotId + " and sim state "
+                        + slotSimState + ", ignoring.");
                 return;
             }
 
@@ -224,6 +263,18 @@
         }
     };
 
+    // Receives the broadcast that the device has finished booting (and the device is no longer
+    // encrypted).
+    private BroadcastReceiver mBootCompleted = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            Log.i(TAG, "Received BOOT_COMPLETED");
+            // Recalculate all cached services to pick up ones that have just been enabled since
+            // boot complete.
+            mHandler.obtainMessage(HANDLER_BOOT_COMPLETE, null).sendToTarget();
+        }
+    };
+
     /**
      * Testing interface used to mock SubscriptionManager in testing
      */
@@ -239,6 +290,28 @@
         int getSlotIndex(int subId);
     }
 
+    /**
+     * Testing interface used to stub out TelephonyManager dependencies.
+     */
+    @VisibleForTesting
+    public interface TelephonyManagerProxy {
+        /**
+         * @return the SIM state for the slot ID specified.
+         */
+        int getSimState(Context context, int slotId);
+    }
+
+    private TelephonyManagerProxy mTelephonyManagerProxy = new TelephonyManagerProxy() {
+        @Override
+        public int getSimState(Context context, int slotId) {
+            TelephonyManager tm = context.getSystemService(TelephonyManager.class);
+            if (tm == null) {
+                return TelephonyManager.SIM_STATE_UNKNOWN;
+            }
+            return tm.getSimState(slotId);
+        }
+    };
+
     private SubscriptionManagerProxy mSubscriptionManagerProxy = new SubscriptionManagerProxy() {
         @Override
         public int getSubId(int slotId) {
@@ -310,35 +383,32 @@
                 }
             };
 
-    private ImsServiceControllerFactory mImsServiceControllerFactoryStaticBindingCompat =
-            new ImsServiceControllerFactory() {
-                @Override
-                public String getServiceInterface() {
-                    // The static method of binding does not use service interfaces.
-                    return null;
-                }
-
-                @Override
-                public ImsServiceController create(Context context, ComponentName componentName,
-                        ImsServiceController.ImsServiceControllerCallbacks callbacks) {
-                    return new ImsServiceControllerStaticCompat(context, componentName, callbacks);
-                }
-            };
-
     private ImsDynamicQueryManagerFactory mDynamicQueryManagerFactory =
             ImsServiceFeatureQueryManager::new;
 
     private final CarrierConfigManager mCarrierConfigManager;
     private final Context mContext;
+    // Special context created only for registering receivers for all users using UserHandle.ALL.
+    // The lifetime of a registered receiver is bounded by the lifetime of the context it's
+    // registered through, so we must retain the Context as long as we need the receiver to be
+    // active.
+    private final Context mReceiverContext;
     // Locks mBoundImsServicesByFeature only. Be careful to avoid deadlocks from
     // ImsServiceController callbacks.
     private final Object mBoundServicesLock = new Object();
-    private final int mNumSlots;
-    private final boolean mIsDynamicBinding;
-    // Package name of the default device service.
-    private String mDeviceService;
+    private int mNumSlots;
+    // Array index corresponds to slot, per slot there is a feature->package name mapping.
+    // should only be accessed from handler
+    private SparseArray<Map<Integer, String>> mCarrierServices;
+    // Package name of the default device services, Maps ImsFeature -> packageName.
+    // should only be accessed from handler
+    private Map<Integer, String> mDeviceServices;
+    // Persistent Logging
+    private final LocalLog mEventLog = new LocalLog(50);
 
-    // Synchronize all messages on a handler to ensure that the cache includes the most recent
+    private boolean mBootCompletedHandlerRan = false;
+
+    // Synchronize all events on a handler to ensure that the cache includes the most recent
     // version of the installed ImsServices.
     private Handler mHandler = new Handler(Looper.getMainLooper(), (msg) -> {
         switch (msg.what) {
@@ -352,8 +422,23 @@
                 maybeRemovedImsService(packageName);
                 break;
             }
+            case HANDLER_BOOT_COMPLETE: {
+                if (!mBootCompletedHandlerRan) {
+                    mBootCompletedHandlerRan = true;
+                    mEventLog.log("handling BOOT_COMPLETE");
+                    // Re-evaluate bound services for all slots after requerying packagemanager
+                    maybeAddedImsService(null /*packageName*/);
+                }
+                break;
+            }
             case HANDLER_CONFIG_CHANGED: {
                 int slotId = (Integer) msg.obj;
+                // If the msim config has changed and there is a residual event for an invalid slot,
+                // ignore.
+                if (slotId >= mNumSlots) {
+                    Log.w(TAG, "HANDLER_CONFIG_CHANGED for invalid slotid=" + slotId);
+                    break;
+                }
                 carrierConfigChanged(slotId);
                 break;
             }
@@ -372,38 +457,20 @@
                 break;
             }
             case HANDLER_OVERRIDE_IMS_SERVICE_CONFIG: {
-                int slotId = msg.arg1;
-                // arg2 will be equal to 1 if it is a carrier service.
-                boolean isCarrierImsService = (msg.arg2 == 1);
-                String packageName = (String) msg.obj;
-                if (isCarrierImsService) {
-                    Log.i(TAG, "overriding carrier ImsService - slot=" + slotId + " packageName="
-                            + packageName);
-                    maybeRebindService(slotId, packageName);
+                OverrideConfig config = (OverrideConfig) msg.obj;
+                if (config.isCarrierService) {
+                    overrideCarrierService(config.slotId,
+                            config.featureTypeToPackageMap);
                 } else {
-                    Log.i(TAG, "overriding device ImsService -  packageName=" + packageName);
-                    if (TextUtils.equals(mDeviceService, packageName)) {
-                        // No change in device service.
-                        break;
-                    }
-                    // Unbind from the previous ImsService before binding to the new one.
-                    unbindImsService(getImsServiceInfoFromCache(mDeviceService));
-                    mDeviceService = packageName;
-                    ImsServiceInfo deviceInfo = getImsServiceInfoFromCache(mDeviceService);
-                    if (deviceInfo == null) {
-                        // The package name is either "" or does not exist on the device.
-                        break;
-                    }
-                    if (deviceInfo.featureFromMetadata) {
-                        bindImsService(deviceInfo);
-                    } else {
-                        // newly added ImsServiceInfo that has not had features queried yet. Start
-                        // async bind and query features.
-                        scheduleQueryForFeatures(deviceInfo);
-                    }
+                    overrideDeviceService(config.featureTypeToPackageMap);
                 }
                 break;
             }
+            case HANDLER_MSIM_CONFIGURATION_CHANGE: {
+                AsyncResult result = (AsyncResult) msg.obj;
+                handleMsimConfigChange((Integer) result.result);
+                break;
+            }
             default:
                 return false;
         }
@@ -417,65 +484,86 @@
                 @Override
                 public void onComplete(ComponentName name,
                         Set<ImsFeatureConfiguration.FeatureSlotPair> features) {
-                    Log.d(TAG, "onComplete called for name: " + name + "features:"
-                            + printFeatures(features));
+                    Log.d(TAG, "onComplete called for name: " + name + printFeatures(features));
                     handleFeaturesChanged(name, features);
                 }
 
                 @Override
                 public void onError(ComponentName name) {
                     Log.w(TAG, "onError: " + name + "returned with an error result");
+                    mEventLog.log("onError - dynamic query error for " + name);
                     scheduleQueryForFeatures(name, DELAY_DYNAMIC_QUERY_MS);
                 }
+
+                @Override
+                public void onPermanentError(ComponentName name) {
+                    Log.w(TAG, "onPermanentError: component=" + name);
+                    mEventLog.log("onPermanentError - error for " + name);
+                    mHandler.obtainMessage(HANDLER_REMOVE_PACKAGE,
+                            name.getPackageName()).sendToTarget();
+                }
             };
 
-    // Array index corresponds to slot Id associated with the service package name.
-    private String[] mCarrierServices;
-    // List index corresponds to Slot Id, Maps ImsFeature.FEATURE->bound ImsServiceController
+    // Used during testing, overrides the carrier services while non-empty.
+    // Array index corresponds to slot, per slot there is a feature->package name mapping.
+    // should only be accessed from handler
+    private SparseArray<SparseArray<String>> mOverrideServices;
+    // Outer array index corresponds to Slot Id, Maps ImsFeature.FEATURE->bound ImsServiceController
     // Locked on mBoundServicesLock
-    private List<SparseArray<ImsServiceController>> mBoundImsServicesByFeature;
+    private SparseArray<SparseArray<ImsServiceController>> mBoundImsServicesByFeature;
     // not locked, only accessed on a handler thread.
+    // Tracks list of all installed ImsServices
     private Map<ComponentName, ImsServiceInfo> mInstalledServicesCache = new HashMap<>();
     // not locked, only accessed on a handler thread.
+    // Active ImsServiceControllers, which are bound to ImsServices.
     private Map<ComponentName, ImsServiceController> mActiveControllers = new HashMap<>();
-    // Only used as the Component name for legacy ImsServices that did not use dynamic binding.
-    private final ComponentName mStaticComponent;
     private ImsServiceFeatureQueryManager mFeatureQueryManager;
 
-    public ImsResolver(Context context, String defaultImsPackageName, int numSlots,
-            boolean isDynamicBinding) {
+    public ImsResolver(Context context, String defaultMmTelPackageName,
+            String defaultRcsPackageName, int numSlots) {
+        Log.i(TAG, "device MMTEL package: " + defaultMmTelPackageName + ", device RCS package:"
+                + defaultRcsPackageName);
         mContext = context;
-        mDeviceService = defaultImsPackageName;
         mNumSlots = numSlots;
-        mIsDynamicBinding = isDynamicBinding;
-        mStaticComponent = new ComponentName(mContext, ImsResolver.class);
-        if (!mIsDynamicBinding) {
-            Log.i(TAG, "ImsResolver initialized with static binding.");
-            mDeviceService = mStaticComponent.getPackageName();
-        }
+        mReceiverContext = context.createContextAsUser(UserHandle.ALL, 0 /*flags*/);
+
+        mCarrierServices = new SparseArray<>(mNumSlots);
+        mDeviceServices = new ArrayMap<>();
+        setDeviceConfiguration(defaultMmTelPackageName, ImsFeature.FEATURE_EMERGENCY_MMTEL);
+        setDeviceConfiguration(defaultMmTelPackageName, ImsFeature.FEATURE_MMTEL);
+        setDeviceConfiguration(defaultRcsPackageName, ImsFeature.FEATURE_RCS);
         mCarrierConfigManager = (CarrierConfigManager) mContext.getSystemService(
                 Context.CARRIER_CONFIG_SERVICE);
-        mCarrierServices = new String[numSlots];
-        mBoundImsServicesByFeature = Stream.generate(SparseArray<ImsServiceController>::new)
-                .limit(mNumSlots).collect(Collectors.toList());
+        mOverrideServices = new SparseArray<>(0 /*initial size*/);
+        mBoundImsServicesByFeature = new SparseArray<>(mNumSlots);
 
-        // Only register for Package/CarrierConfig updates if dynamic binding.
-        if(mIsDynamicBinding) {
-            IntentFilter appChangedFilter = new IntentFilter();
-            appChangedFilter.addAction(Intent.ACTION_PACKAGE_CHANGED);
-            appChangedFilter.addAction(Intent.ACTION_PACKAGE_REMOVED);
-            appChangedFilter.addAction(Intent.ACTION_PACKAGE_ADDED);
-            appChangedFilter.addDataScheme("package");
-            context.registerReceiverAsUser(mAppChangedReceiver, UserHandle.ALL, appChangedFilter,
-                    null,
-                    null);
+        IntentFilter appChangedFilter = new IntentFilter();
+        appChangedFilter.addAction(Intent.ACTION_PACKAGE_CHANGED);
+        appChangedFilter.addAction(Intent.ACTION_PACKAGE_REMOVED);
+        appChangedFilter.addAction(Intent.ACTION_PACKAGE_ADDED);
+        appChangedFilter.addDataScheme("package");
+        mReceiverContext.registerReceiver(mAppChangedReceiver, appChangedFilter);
+        mReceiverContext.registerReceiver(mConfigChangedReceiver, new IntentFilter(
+                CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED));
 
-            context.registerReceiver(mConfigChangedReceiver, new IntentFilter(
-                    CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED));
+        UserManager userManager = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
+        if (userManager.isUserUnlocked()) {
+            mHandler.obtainMessage(HANDLER_BOOT_COMPLETE, null).sendToTarget();
+        } else {
+            mReceiverContext.registerReceiver(mBootCompleted, new IntentFilter(
+                    Intent.ACTION_BOOT_COMPLETED));
+            if (userManager.isUserUnlocked()) {
+                mHandler.obtainMessage(HANDLER_BOOT_COMPLETE, null).sendToTarget();
+            }
         }
     }
 
     @VisibleForTesting
+    public void setTelephonyManagerProxy(TelephonyManagerProxy proxy) {
+        mTelephonyManagerProxy = proxy;
+    }
+
+    @VisibleForTesting
     public void setSubscriptionManagerProxy(SubscriptionManagerProxy proxy) {
         mSubscriptionManagerProxy = proxy;
     }
@@ -496,17 +584,62 @@
     }
 
     /**
-     * Needs to be called after the constructor to first populate the cache and possibly bind to
-     * ImsServices.
+     * Needs to be called after the constructor to kick off the process of binding to ImsServices.
      */
-    public void initPopulateCacheAndStartBind() {
-        Log.i(TAG, "Initializing cache and binding.");
+    public void initialize() {
+        mEventLog.log("Initializing");
+        Log.i(TAG, "Initializing cache.");
+        PhoneConfigurationManager.registerForMultiSimConfigChange(mHandler,
+                HANDLER_MSIM_CONFIGURATION_CHANGE, null);
         mFeatureQueryManager = mDynamicQueryManagerFactory.create(mContext, mDynamicQueryListener);
-        // Populates the CarrierConfig override package names for each slot
-        mHandler.obtainMessage(HANDLER_CONFIG_CHANGED,
-                SubscriptionManager.INVALID_SIM_SLOT_INDEX).sendToTarget();
-        // Starts first bind to the system.
-        mHandler.obtainMessage(HANDLER_ADD_PACKAGE, null).sendToTarget();
+
+        // This will get all services with the correct intent filter from PackageManager
+        List<ImsServiceInfo> infos = getImsServiceInfo(null);
+        for (ImsServiceInfo info : infos) {
+            if (!mInstalledServicesCache.containsKey(info.name)) {
+                mInstalledServicesCache.put(info.name, info);
+            }
+        }
+        // Update the package names of the carrier ImsServices if they do not exist already and
+        // possibly bind if carrier configs exist. Otherwise wait for CarrierConfigChanged
+        // indication.
+        bindCarrierServicesIfAvailable();
+    }
+
+    /**
+     * Destroys this ImsResolver. Used for tearing down static resources during testing.
+     */
+    @VisibleForTesting
+    public void destroy() {
+        PhoneConfigurationManager.unregisterForMultiSimConfigChange(mHandler);
+        mHandler.removeCallbacksAndMessages(null);
+    }
+
+    // Only start the bind if there is an existing Carrier Configuration. Otherwise, wait for
+    // carrier config changed.
+    private void bindCarrierServicesIfAvailable() {
+        boolean hasConfigChanged = false;
+        for (int slotId = 0; slotId < mNumSlots; slotId++) {
+            Map<Integer, String> featureMap = getImsPackageOverrideConfig(slotId);
+            for (int f = ImsFeature.FEATURE_EMERGENCY_MMTEL; f < ImsFeature.FEATURE_MAX; f++) {
+                String newPackageName = featureMap.getOrDefault(f, "");
+                if (!TextUtils.isEmpty(newPackageName)) {
+                    mEventLog.log("bindCarrierServicesIfAvailable - carrier package found: "
+                            + newPackageName + " on slot " + slotId);
+                    setCarrierConfiguredPackageName(newPackageName, slotId, f);
+                    ImsServiceInfo info = getImsServiceInfoFromCache(newPackageName);
+                    // We do not want to trigger feature configuration changes unless there is
+                    // already a valid carrier config change.
+                    if (info != null && info.featureFromMetadata) {
+                        hasConfigChanged = true;
+                    } else {
+                        // Config will change when this query completes
+                        scheduleQueryForFeatures(info);
+                    }
+                }
+            }
+        }
+        if (hasConfigChanged) calculateFeatureConfigurationChange();
     }
 
     /**
@@ -634,34 +767,130 @@
         return null;
     }
 
+    /**
+     * Unregister a previously registered IImsServiceFeatureCallback through
+     * {@link #getImsServiceControllerAndListen(int, int, IImsServiceFeatureCallback)} .
+     * @param slotId The slot id associated with the ImsFeature.
+     * @param feature The {@link ImsFeature.FeatureType}
+     * @param callback The callback to be unregistered.
+     */
+    public void unregisterImsFeatureCallback(int slotId, int feature,
+            IImsServiceFeatureCallback callback) {
+        ImsServiceController controller = getImsServiceController(slotId, feature);
+
+        if (controller != null) {
+            controller.removeImsServiceFeatureCallback(callback);
+        }
+    }
+
     // Used for testing only.
     public boolean overrideImsServiceConfiguration(int slotId, boolean isCarrierService,
-            String packageName) {
+            Map<Integer, String> featureConfig) {
         if (slotId < 0 || slotId >= mNumSlots) {
             Log.w(TAG, "overrideImsServiceConfiguration: invalid slotId!");
             return false;
         }
 
-        if (packageName == null) {
-            Log.w(TAG, "overrideImsServiceConfiguration: null packageName!");
-            return false;
-        }
-
-        // encode boolean to int for Message.
-        int carrierService = isCarrierService ? 1 : 0;
-        Message.obtain(mHandler, HANDLER_OVERRIDE_IMS_SERVICE_CONFIG, slotId, carrierService,
-                packageName).sendToTarget();
+        OverrideConfig overrideConfig = new OverrideConfig(slotId, isCarrierService, featureConfig);
+        Message.obtain(mHandler, HANDLER_OVERRIDE_IMS_SERVICE_CONFIG, overrideConfig)
+                .sendToTarget();
         return true;
     }
 
-    // used for testing only.
-    public String getImsServiceConfiguration(int slotId, boolean isCarrierService) {
+    // not synchronized, access through handler ONLY.
+    private String getDeviceConfiguration(@ImsFeature.FeatureType int featureType) {
+        return mDeviceServices.getOrDefault(featureType, "");
+    }
+
+    // not synchronized, access in handler ONLY.
+    private void setDeviceConfiguration(String name, @ImsFeature.FeatureType int featureType) {
+        mDeviceServices.put(featureType, name);
+    }
+
+    // not synchronized, access in handler ONLY.
+    private void setCarrierConfiguredPackageName(@NonNull String packageName, int slotId,
+            @ImsFeature.FeatureType int featureType) {
+        getCarrierConfiguredPackageNames(slotId).put(featureType, packageName);
+    }
+
+    // not synchronized, access in handler ONLY.
+    private @NonNull String getCarrierConfiguredPackageName(int slotId,
+            @ImsFeature.FeatureType int featureType) {
+        return getCarrierConfiguredPackageNames(slotId).getOrDefault(featureType, "");
+    }
+
+    // not synchronized, access in handler ONLY.
+    private @NonNull Map<Integer, String> getCarrierConfiguredPackageNames(int slotId) {
+        Map<Integer, String> carrierConfig = mCarrierServices.get(slotId);
+        if (carrierConfig == null) {
+            carrierConfig = new ArrayMap<>();
+            mCarrierServices.put(slotId, carrierConfig);
+        }
+        return carrierConfig;
+    }
+
+    // not synchronized, access in handler ONLY.
+    private void setOverridePackageName(@Nullable String packageName, int slotId,
+            @ImsFeature.FeatureType int featureType) {
+        getOverridePackageName(slotId).put(featureType, packageName);
+    }
+
+    // not synchronized, access in handler ONLY.
+    private @Nullable String getOverridePackageName(int slotId,
+            @ImsFeature.FeatureType int featureType) {
+        return getOverridePackageName(slotId).get(featureType);
+    }
+
+    // not synchronized, access in handler ONLY.
+    private @NonNull SparseArray<String> getOverridePackageName(int slotId) {
+        SparseArray<String> carrierConfig = mOverrideServices.get(slotId);
+        if (carrierConfig == null) {
+            carrierConfig = new SparseArray<>();
+            mOverrideServices.put(slotId, carrierConfig);
+        }
+        return carrierConfig;
+    }
+
+    /**
+     * @return true if there is a carrier configuration that exists for the slot & featureType pair
+     * and the cached carrier ImsService associated with the configuration also supports the
+     * requested ImsFeature type.
+     */
+    // not synchronized, access in handler ONLY.
+    private boolean doesCarrierConfigurationExist(int slotId,
+            @ImsFeature.FeatureType int featureType) {
+        String carrierPackage = getCarrierConfiguredPackageName(slotId, featureType);
+        if (TextUtils.isEmpty(carrierPackage)) {
+            return false;
+        }
+        // Config exists, but the carrier ImsService also needs to support this feature
+        ImsServiceInfo info = getImsServiceInfoFromCache(carrierPackage);
+        return info != null && info.getSupportedFeatures().stream().anyMatch(
+                feature -> feature.slotId == slotId && feature.featureType == featureType);
+    }
+
+    /**
+     * @return the package name of the ImsService with the requested configuration.
+     */
+    // used in shell commands queries during testing only.
+    public String getImsServiceConfiguration(int slotId, boolean isCarrierService,
+            @ImsFeature.FeatureType int featureType) {
         if (slotId < 0 || slotId >= mNumSlots) {
             Log.w(TAG, "getImsServiceConfiguration: invalid slotId!");
             return "";
         }
 
-        return isCarrierService ? mCarrierServices[slotId] : mDeviceService;
+        LinkedBlockingQueue<String> result = new LinkedBlockingQueue<>(1);
+        // access the configuration on the handler.
+        mHandler.post(() -> result.offer(isCarrierService
+                ? getCarrierConfiguredPackageName(slotId, featureType) :
+                getDeviceConfiguration(featureType)));
+        try {
+            return result.poll(GET_IMS_SERVICE_TIMEOUT_MS, TimeUnit.MILLISECONDS);
+        } catch (InterruptedException e) {
+            Log.w(TAG, "getImsServiceConfiguration: exception=" + e.getMessage());
+            return null;
+        }
     }
 
     private void putImsController(int slotId, int feature, ImsServiceController controller) {
@@ -675,16 +904,19 @@
             SparseArray<ImsServiceController> services = mBoundImsServicesByFeature.get(slotId);
             if (services == null) {
                 services = new SparseArray<>();
-                mBoundImsServicesByFeature.add(slotId, services);
+                mBoundImsServicesByFeature.put(slotId, services);
             }
+            mEventLog.log("putImsController - [" + slotId + ", "
+                    + ImsFeature.FEATURE_LOG_MAP.get(feature) + "] -> " + controller);
             Log.i(TAG, "ImsServiceController added on slot: " + slotId + " with feature: "
-                    + feature + " using package: " + controller.getComponentName());
+                    + ImsFeature.FEATURE_LOG_MAP.get(feature) + " using package: "
+                    + controller.getComponentName());
             services.put(feature, controller);
         }
     }
 
     private ImsServiceController removeImsController(int slotId, int feature) {
-        if (slotId < 0 || slotId >= mNumSlots || feature <= ImsFeature.FEATURE_INVALID
+        if (slotId < 0 || feature <= ImsFeature.FEATURE_INVALID
                 || feature >= ImsFeature.FEATURE_MAX) {
             Log.w(TAG, "removeImsController received invalid parameters - slot: " + slotId
                     + ", feature: " + feature);
@@ -697,8 +929,11 @@
             }
             ImsServiceController c = services.get(feature, null);
             if (c != null) {
+                mEventLog.log("removeImsController - [" + slotId + ", "
+                        + ImsFeature.FEATURE_LOG_MAP.get(feature) + "] -> " + c);
                 Log.i(TAG, "ImsServiceController removed on slot: " + slotId + " with feature: "
-                        + feature + " using package: " + c.getComponentName());
+                        + ImsFeature.FEATURE_LOG_MAP.get(feature) + " using package: "
+                        + c.getComponentName());
                 services.remove(feature);
             }
             return c;
@@ -711,7 +946,9 @@
     private void maybeAddedImsService(String packageName) {
         Log.d(TAG, "maybeAddedImsService, packageName: " + packageName);
         List<ImsServiceInfo> infos = getImsServiceInfo(packageName);
-        List<ImsServiceInfo> newlyAddedInfos = new ArrayList<>();
+        // Wait until all ImsServiceInfo is cached before calling
+        // calculateFeatureConfigurationChange to reduce churn.
+        boolean requiresCalculation = false;
         for (ImsServiceInfo info : infos) {
             // Checking to see if the ComponentName is the same, so we can update the supported
             // features. Will only be one (if it exists), since it is a set.
@@ -721,21 +958,26 @@
                 // out the cache for the existing features or update yet. Instead start a query
                 // for features dynamically.
                 if (info.featureFromMetadata) {
-                    // update features in the cache
+                    mEventLog.log("maybeAddedImsService - updating features for " + info.name
+                            + ": " + printFeatures(match.getSupportedFeatures()) + " -> "
+                            + printFeatures(info.getSupportedFeatures()));
                     Log.i(TAG, "Updating features in cached ImsService: " + info.name);
                     Log.d(TAG, "Updating features - Old features: " + match + " new features: "
                             + info);
+                    // update features in the cache
                     match.replaceFeatures(info.getSupportedFeatures());
-                    updateImsServiceFeatures(info);
+                    requiresCalculation = true;
                 } else {
+                    mEventLog.log("maybeAddedImsService - scheduling query for " + info);
                     // start a query to get ImsService features
                     scheduleQueryForFeatures(info);
                 }
             } else {
                 Log.i(TAG, "Adding newly added ImsService to cache: " + info.name);
+                mEventLog.log("maybeAddedImsService - adding new ImsService: " + info);
                 mInstalledServicesCache.put(info.name, info);
                 if (info.featureFromMetadata) {
-                    newlyAddedInfos.add(info);
+                    requiresCalculation = true;
                 } else {
                     // newly added ImsServiceInfo that has not had features queried yet. Start async
                     // bind and query features.
@@ -743,60 +985,40 @@
                 }
             }
         }
-        // Loop through the newly created ServiceInfos in a separate loop to make sure the cache
-        // is fully updated.
-        for (ImsServiceInfo info : newlyAddedInfos) {
-            if (isActiveCarrierService(info)) {
-                // New ImsService is registered to active carrier services and must be newly
-                // bound.
-                bindImsService(info);
-                // Update existing device service features
-                updateImsServiceFeatures(getImsServiceInfoFromCache(mDeviceService));
-            } else if (isDeviceService(info)) {
-                // New ImsService is registered as device default and must be newly bound.
-                bindImsService(info);
-            }
-        }
+        if (requiresCalculation) calculateFeatureConfigurationChange();
     }
 
-    // Remove the ImsService from the cache. At this point, the ImsService will have already been
-    // killed.
+    // Remove the ImsService from the cache. This may have been due to the ImsService being removed
+    // from the device or was returning permanent errors when bound.
     // Called from the handler ONLY
     private boolean maybeRemovedImsService(String packageName) {
         ImsServiceInfo match = getInfoByPackageName(mInstalledServicesCache, packageName);
         if (match != null) {
             mInstalledServicesCache.remove(match.name);
+            mEventLog.log("maybeRemovedImsService - removing ImsService: " + match);
             Log.i(TAG, "Removing ImsService: " + match.name);
             unbindImsService(match);
-            updateImsServiceFeatures(getImsServiceInfoFromCache(mDeviceService));
+            calculateFeatureConfigurationChange();
             return true;
         }
         return false;
     }
 
-    // Returns true if the CarrierConfig that has been loaded includes this ImsServiceInfo
-    // package name.
-    // Called from Handler ONLY
-    private boolean isActiveCarrierService(ImsServiceInfo info) {
-        for (int i = 0; i < mNumSlots; i++) {
-            if (TextUtils.equals(mCarrierServices[i], info.name.getPackageName())) {
-                return true;
-            }
-        }
-        return false;
-    }
-
     private boolean isDeviceService(ImsServiceInfo info) {
-        return TextUtils.equals(mDeviceService, info.name.getPackageName());
+        if (info == null) return false;
+        return mDeviceServices.containsValue(info.name.getPackageName());
     }
 
-    private int getSlotForActiveCarrierService(ImsServiceInfo info) {
+    private List<Integer> getSlotsForActiveCarrierService(ImsServiceInfo info) {
+        if (info == null) return Collections.emptyList();
+        List<Integer> slots = new ArrayList<>(mNumSlots);
         for (int i = 0; i < mNumSlots; i++) {
-            if (TextUtils.equals(mCarrierServices[i], info.name.getPackageName())) {
-                return i;
+            if (!TextUtils.isEmpty(getCarrierConfiguredPackageNames(i).values().stream()
+                    .filter(e -> e.equals(info.name.getPackageName())).findAny().orElse(""))) {
+                slots.add(i);
             }
         }
-        return SubscriptionManager.INVALID_SIM_SLOT_INDEX;
+        return slots;
     }
 
     private ImsServiceController getControllerByServiceInfo(
@@ -818,64 +1040,15 @@
         return searchMap.get(matchValue);
     }
 
-    // Creates new features in active ImsServices and removes obsolete cached features. If
-    // cachedInfo == null, then newInfo is assumed to be a new ImsService and will have all features
-    // created.
-    private void updateImsServiceFeatures(ImsServiceInfo newInfo) {
-        if (newInfo == null) {
-            return;
-        }
-        ImsServiceController controller = getControllerByServiceInfo(mActiveControllers, newInfo);
-        // Will return zero if these features are overridden or it should not currently have any
-        // features because it is not carrier/device.
-        HashSet<ImsFeatureConfiguration.FeatureSlotPair> features =
-                calculateFeaturesToCreate(newInfo);
-        if (shouldFeaturesCauseBind(features)) {
-            try {
-                if (controller != null) {
-                    Log.i(TAG, "Updating features for ImsService: "
-                            + controller.getComponentName());
-                    Log.d(TAG, "Updating Features - New Features: " + features);
-                    controller.changeImsServiceFeatures(features);
-                } else {
-                    Log.i(TAG, "updateImsServiceFeatures: unbound with active features, rebinding");
-                    bindImsServiceWithFeatures(newInfo, features);
-                }
-                // If the carrier service features have changed, the device features will also
-                // need to be recalculated.
-                if (isActiveCarrierService(newInfo)
-                        // Prevent infinite recursion from bad behavior
-                        && !TextUtils.equals(newInfo.name.getPackageName(), mDeviceService)) {
-                    Log.i(TAG, "Updating device default");
-                    updateImsServiceFeatures(getImsServiceInfoFromCache(mDeviceService));
-                }
-            } catch (RemoteException e) {
-                Log.e(TAG, "updateImsServiceFeatures: Remote Exception: " + e.getMessage());
-            }
-        // Don't stay bound if the ImsService is providing no features.
-        } else if (controller != null) {
-            Log.i(TAG, "Unbinding: features = 0 for ImsService: " + controller.getComponentName());
-            unbindImsService(newInfo);
-        }
-    }
-
-    // Bind to an ImsService and wait for the service to be connected to create ImsFeatures.
-    private void bindImsService(ImsServiceInfo info) {
-        if (info == null) {
-            return;
-        }
-        HashSet<ImsFeatureConfiguration.FeatureSlotPair> features = calculateFeaturesToCreate(info);
-        bindImsServiceWithFeatures(info, features);
-    }
-
     private void bindImsServiceWithFeatures(ImsServiceInfo info,
-            HashSet<ImsFeatureConfiguration.FeatureSlotPair> features) {
+            Set<ImsFeatureConfiguration.FeatureSlotPair> features) {
         // Only bind if there are features that will be created by the service.
         if (shouldFeaturesCauseBind(features)) {
             // Check to see if an active controller already exists
             ImsServiceController controller = getControllerByServiceInfo(mActiveControllers, info);
             if (controller != null) {
-                Log.i(TAG, "ImsService connection exists, updating features " + features);
+                Log.i(TAG, "ImsService connection exists for " + info.name + ", updating features "
+                        + features);
                 try {
                     controller.changeImsServiceFeatures(features);
                     // Features have been set, there was an error adding/removing. When the
@@ -888,6 +1061,8 @@
                 Log.i(TAG, "Binding ImsService: " + controller.getComponentName()
                         + " with features: " + features);
                 controller.bind(features);
+                mEventLog.log("bindImsServiceWithFeatures - create new controller: "
+                        + controller);
             }
             mActiveControllers.put(info.name, controller);
         }
@@ -903,6 +1078,7 @@
             // Calls imsServiceFeatureRemoved on all features in the controller
             try {
                 Log.i(TAG, "Unbinding ImsService: " + controller.getComponentName());
+                mEventLog.log("unbindImsService - unbinding and removing " + controller);
                 controller.unbind();
             } catch (RemoteException e) {
                 Log.e(TAG, "unbindImsService: Remote Exception: " + e.getMessage());
@@ -918,36 +1094,27 @@
     private HashSet<ImsFeatureConfiguration.FeatureSlotPair> calculateFeaturesToCreate(
             ImsServiceInfo info) {
         HashSet<ImsFeatureConfiguration.FeatureSlotPair> imsFeaturesBySlot = new HashSet<>();
-        // Check if the info is a carrier service
-        int slotId = getSlotForActiveCarrierService(info);
-        if (slotId != SubscriptionManager.INVALID_SIM_SLOT_INDEX) {
+        List<Integer> slots = getSlotsForActiveCarrierService(info);
+        if (!slots.isEmpty()) {
+            // There is an active carrier config associated with this. Return with the ImsService's
+            // supported features that are also within the carrier configuration
             imsFeaturesBySlot.addAll(info.getSupportedFeatures().stream()
-                    // Match slotId with feature slotId.
-                    .filter(feature -> slotId == feature.slotId)
+                    .filter(feature -> info.name.getPackageName().equals(
+                            getCarrierConfiguredPackageName(feature.slotId, feature.featureType)))
                     .collect(Collectors.toList()));
-        } else if (isDeviceService(info)) {
-            // For all slots that are not currently using a carrier ImsService, enable all features
-            // for the device default.
-            for (int i = 0; i < mNumSlots; i++) {
-                final int currSlotId = i;
-                ImsServiceInfo carrierImsInfo = getImsServiceInfoFromCache(mCarrierServices[i]);
-                if (carrierImsInfo == null) {
-                    // No Carrier override, add all features for this slot
-                    imsFeaturesBySlot.addAll(info.getSupportedFeatures().stream()
-                            .filter(feature -> currSlotId == feature.slotId)
-                            .collect(Collectors.toList()));
-                } else {
-                    // Add all features to the device service that are not currently covered by
-                    // the carrier ImsService.
-                    HashSet<ImsFeatureConfiguration.FeatureSlotPair> deviceFeatures =
-                            new HashSet<>(info.getSupportedFeatures());
-                    deviceFeatures.removeAll(carrierImsInfo.getSupportedFeatures());
-                    // only add features for current slot
-                    imsFeaturesBySlot.addAll(deviceFeatures.stream()
-                            .filter(feature -> currSlotId == feature.slotId).collect(
-                            Collectors.toList()));
-                }
-            }
+            return imsFeaturesBySlot;
+        }
+        if (isDeviceService(info)) {
+            imsFeaturesBySlot.addAll(info.getSupportedFeatures().stream()
+                    // only allow supported features that are also set for this package as the
+                    // device configuration.
+                    .filter(feature -> info.name.getPackageName().equals(
+                            getDeviceConfiguration(feature.featureType)))
+                    // filter out any separate carrier configuration, since that feature is handled
+                    // by the carrier ImsService.
+                    .filter(feature -> !doesCarrierConfigurationExist(feature.slotId,
+                            feature.featureType))
+                    .collect(Collectors.toList()));
         }
         return imsFeaturesBySlot;
     }
@@ -982,86 +1149,176 @@
         }
         Log.i(TAG, "imsServiceFeaturesChanged: config=" + config.getServiceFeatures()
                 + ", ComponentName=" + controller.getComponentName());
+        mEventLog.log("imsServiceFeaturesChanged - for " + controller + ", new config "
+                + config.getServiceFeatures());
         handleFeaturesChanged(controller.getComponentName(), config.getServiceFeatures());
     }
 
+    @Override
+    public void imsServiceBindPermanentError(ComponentName name) {
+        if (name == null) {
+            return;
+        }
+        Log.w(TAG, "imsServiceBindPermanentError: component=" + name);
+        mEventLog.log("imsServiceBindPermanentError - for " + name);
+        mHandler.obtainMessage(HANDLER_REMOVE_PACKAGE, name.getPackageName()).sendToTarget();
+    }
+
     /**
      * Determines if the features specified should cause a bind or keep a binding active to an
      * ImsService.
      * @return true if MMTEL or RCS features are present, false if they are not or only
      * EMERGENCY_MMTEL is specified.
      */
-    private boolean shouldFeaturesCauseBind(
-            HashSet<ImsFeatureConfiguration.FeatureSlotPair> features) {
+    private boolean shouldFeaturesCauseBind(Set<ImsFeatureConfiguration.FeatureSlotPair> features) {
         long bindableFeatures = features.stream()
                 // remove all emergency features
                 .filter(f -> f.featureType != ImsFeature.FEATURE_EMERGENCY_MMTEL).count();
         return bindableFeatures > 0;
     }
 
-    // Possibly rebind to another ImsService if currently installed ImsServices were changed or if
-    // the SIM card has changed.
+    // Possibly rebind to another ImsService for testing carrier ImsServices.
     // Called from the handler ONLY
-    private void maybeRebindService(int slotId, String newPackageName) {
-        if (slotId <= SubscriptionManager.INVALID_SIM_SLOT_INDEX) {
-            // not specified, replace package on all slots.
-            for (int i = 0; i < mNumSlots; i++) {
-                updateBoundCarrierServices(i, newPackageName);
-            }
-        } else {
-            updateBoundCarrierServices(slotId, newPackageName);
+    private void overrideCarrierService(int slotId, Map<Integer, String> featureMap) {
+        for (Integer featureType : featureMap.keySet()) {
+            String overridePackageName = featureMap.get(featureType);
+            mEventLog.log("overriding carrier ImsService to " + overridePackageName
+                    + " on slot " + slotId + " for feature "
+                    + ImsFeature.FEATURE_LOG_MAP.getOrDefault(featureType, "invalid"));
+            setOverridePackageName(overridePackageName, slotId, featureType);
         }
-
+        updateBoundServices(slotId, Collections.emptyMap());
     }
 
-    private void carrierConfigChanged(int slotId) {
-        int subId = mSubscriptionManagerProxy.getSubId(slotId);
-        PersistableBundle config = mCarrierConfigManager.getConfigForSubId(subId);
-        if (config != null) {
-            String newPackageName = config.getString(
-                    CarrierConfigManager.KEY_CONFIG_IMS_PACKAGE_OVERRIDE_STRING, null);
-            maybeRebindService(slotId, newPackageName);
-        } else {
-            Log.w(TAG, "carrierConfigChanged: CarrierConfig is null!");
-        }
-    }
-
-    private void updateBoundCarrierServices(int slotId, String newPackageName) {
-        if (slotId > SubscriptionManager.INVALID_SIM_SLOT_INDEX && slotId < mNumSlots) {
-            String oldPackageName = mCarrierServices[slotId];
-            mCarrierServices[slotId] = newPackageName;
-            if (!TextUtils.equals(newPackageName, oldPackageName)) {
-                Log.i(TAG, "Carrier Config updated, binding new ImsService");
-                // Unbind old ImsService, not needed anymore
-                // ImsService is retrieved from the cache. If the cache hasn't been populated yet,
-                // the calls to unbind/bind will fail (intended during initial start up).
-                unbindImsService(getImsServiceInfoFromCache(oldPackageName));
-                ImsServiceInfo newInfo = getImsServiceInfoFromCache(newPackageName);
-                // if there is no carrier ImsService, newInfo is null. This we still want to update
-                // bindings for device ImsService to pick up the missing features.
-                if (newInfo == null || newInfo.featureFromMetadata) {
-                    bindImsService(newInfo);
-                    // Recalculate the device ImsService features to reflect changes.
-                    updateImsServiceFeatures(getImsServiceInfoFromCache(mDeviceService));
+    // Possibly rebind to another ImsService for testing carrier ImsServices.
+    // Called from the handler ONLY
+    private void overrideDeviceService(Map<Integer, String> featureMap) {
+        boolean requiresRecalc = false;
+        for (Integer featureType : featureMap.keySet()) {
+            String overridePackageName = featureMap.get(featureType);
+            mEventLog.log("overriding device ImsService to " + overridePackageName + " for feature "
+                    + ImsFeature.FEATURE_LOG_MAP.getOrDefault(featureType, "invalid"));
+            String oldPackageName = getDeviceConfiguration(featureType);
+            if (!TextUtils.equals(oldPackageName, overridePackageName)) {
+                Log.i(TAG, "overrideDeviceService - device package changed (override): "
+                        + oldPackageName + " -> " + overridePackageName);
+                mEventLog.log("overrideDeviceService - device package changed (override): "
+                        + oldPackageName + " -> " + overridePackageName);
+                setDeviceConfiguration(overridePackageName, featureType);
+                ImsServiceInfo info = getImsServiceInfoFromCache(overridePackageName);
+                if (info == null || info.featureFromMetadata) {
+                    requiresRecalc = true;
                 } else {
-                    // ImsServiceInfo that has not had features queried yet. Start async
-                    // bind and query features.
-                    scheduleQueryForFeatures(newInfo);
+                    // Config will change when this query completes
+                    scheduleQueryForFeatures(info);
                 }
             }
         }
+        if (requiresRecalc) calculateFeatureConfigurationChange();
+    }
+
+    // Called from handler ONLY.
+    private void carrierConfigChanged(int slotId) {
+        updateBoundDeviceServices();
+
+        if (slotId <= SubscriptionManager.INVALID_SIM_SLOT_INDEX) {
+            // not specified, update carrier override cache and possibly rebind on all slots.
+            for (int i = 0; i < mNumSlots; i++) {
+                updateBoundServices(i, getImsPackageOverrideConfig(i));
+            }
+        }
+        updateBoundServices(slotId, getImsPackageOverrideConfig(slotId));
+    }
+
+    private void updateBoundDeviceServices() {
+        Log.d(TAG, "updateBoundDeviceServices: called");
+        ArrayMap<String, ImsServiceInfo> featureDynamicImsPackages = new ArrayMap<>();
+        for (int f = ImsFeature.FEATURE_EMERGENCY_MMTEL; f < ImsFeature.FEATURE_MAX; f++) {
+            String packageName = getDeviceConfiguration(f);
+            ImsServiceInfo serviceInfo = getImsServiceInfoFromCache(packageName);
+            if (serviceInfo != null && !serviceInfo.featureFromMetadata
+                    && !featureDynamicImsPackages.containsKey(packageName)) {
+                featureDynamicImsPackages.put(packageName, serviceInfo);
+
+                Log.d(TAG, "updateBoundDeviceServices: Schedule query for package=" + packageName);
+                scheduleQueryForFeatures(featureDynamicImsPackages.get(packageName));
+            }
+        }
+    }
+
+    private void updateBoundServices(int slotId, Map<Integer, String> featureMap) {
+        if (slotId <= SubscriptionManager.INVALID_SIM_SLOT_INDEX || slotId >= mNumSlots) {
+            return;
+        }
+        boolean hasConfigChanged = false;
+        boolean didQuerySchedule = false;
+        for (int f = ImsFeature.FEATURE_EMERGENCY_MMTEL; f < ImsFeature.FEATURE_MAX; f++) {
+            String overridePackageName = getOverridePackageName(slotId, f);
+            String oldPackageName = getCarrierConfiguredPackageName(slotId, f);
+            String newPackageName = featureMap.getOrDefault(f, "");
+            if (!TextUtils.isEmpty(overridePackageName)) {
+                // Do not allow carrier config changes to change the override package while it
+                // is in effect.
+                Log.i(TAG, String.format("updateBoundServices: overriding %s with %s for feature"
+                                + " %s on slot %d",
+                        TextUtils.isEmpty(newPackageName) ? "(none)" : newPackageName,
+                        overridePackageName,
+                        ImsFeature.FEATURE_LOG_MAP.getOrDefault(f, "invalid"), slotId));
+                newPackageName = overridePackageName;
+            }
+
+            setCarrierConfiguredPackageName(newPackageName, slotId, f);
+            // Carrier config may have not changed, but we still want to kick off a recalculation
+            // in case there has been a change to the supported device features.
+            ImsServiceInfo info = getImsServiceInfoFromCache(newPackageName);
+            mEventLog.log("updateBoundServices - carrier package changed: "
+                    + oldPackageName + " -> " + newPackageName + " on slot " + slotId
+                    + ", hasConfigChanged=" + hasConfigChanged);
+            if (info == null || info.featureFromMetadata) {
+                hasConfigChanged = true;
+            } else {
+                // Config will change when this query completes
+                scheduleQueryForFeatures(info);
+                didQuerySchedule = true;
+            }
+        }
+        if (hasConfigChanged) calculateFeatureConfigurationChange();
+
+        if (hasConfigChanged && didQuerySchedule) {
+            mEventLog.log("[warning] updateBoundServices - both hasConfigChange and query "
+                    + "scheduled on slot " + slotId);
+        }
+    }
+
+    private @NonNull Map<Integer, String> getImsPackageOverrideConfig(int slotId) {
+        int subId = mSubscriptionManagerProxy.getSubId(slotId);
+        PersistableBundle config = mCarrierConfigManager.getConfigForSubId(subId);
+        if (config == null) return Collections.emptyMap();
+        String packageNameMmTel = config.getString(
+                CarrierConfigManager.KEY_CONFIG_IMS_PACKAGE_OVERRIDE_STRING, null);
+        // Set the config equal for the deprecated key.
+        String packageNameRcs = packageNameMmTel;
+        packageNameMmTel = config.getString(
+                CarrierConfigManager.KEY_CONFIG_IMS_MMTEL_PACKAGE_OVERRIDE_STRING,
+                packageNameMmTel);
+        packageNameRcs = config.getString(
+                CarrierConfigManager.KEY_CONFIG_IMS_RCS_PACKAGE_OVERRIDE_STRING, packageNameRcs);
+        Map<Integer, String> result = new ArrayMap<>();
+        if (!TextUtils.isEmpty(packageNameMmTel)) {
+            result.put(ImsFeature.FEATURE_EMERGENCY_MMTEL, packageNameMmTel);
+            result.put(ImsFeature.FEATURE_MMTEL, packageNameMmTel);
+        }
+        if (!TextUtils.isEmpty(packageNameRcs)) {
+            result.put(ImsFeature.FEATURE_RCS, packageNameRcs);
+        }
+        return result;
     }
 
     /**
      * Schedules a query for dynamic ImsService features.
      */
     private void scheduleQueryForFeatures(ImsServiceInfo service, int delayMs) {
-        // if not current device/carrier service, don't perform query. If this changes, this method
-        // will be called again.
-        if (!isDeviceService(service) && getSlotForActiveCarrierService(service)
-                == SubscriptionManager.INVALID_SIM_SLOT_INDEX) {
-            Log.i(TAG, "scheduleQueryForFeatures: skipping query for ImsService that is not"
-                    + " set as carrier/device ImsService.");
+        if (service == null) {
             return;
         }
         Message msg = Message.obtain(mHandler, HANDLER_START_DYNAMIC_FEATURE_QUERY, service);
@@ -1099,12 +1356,79 @@
         mHandler.obtainMessage(HANDLER_DYNAMIC_FEATURE_CHANGE, args).sendToTarget();
     }
 
+    private void handleMsimConfigChange(Integer newNumSlots) {
+        int oldLen = mNumSlots;
+        if (oldLen == newNumSlots) {
+            return;
+        }
+        mNumSlots = newNumSlots;
+        Log.i(TAG, "handleMsimConfigChange: oldLen=" + oldLen + ", newLen=" + newNumSlots);
+        mEventLog.log("MSIM config change: " + oldLen + " -> " + newNumSlots);
+        if (newNumSlots < oldLen) {
+            // we need to trim data structures that use slots, however mBoundImsServicesByFeature
+            // will be updated by ImsServiceController changing to remove features on old slots.
+            // start at the index of the new highest slot + 1.
+            for (int oldSlot = newNumSlots; oldSlot < oldLen; oldSlot++) {
+                // First clear old carrier configs
+                Map<Integer, String> carrierConfigs = getCarrierConfiguredPackageNames(oldSlot);
+                for (Integer feature : carrierConfigs.keySet()) {
+                    setCarrierConfiguredPackageName("", oldSlot, feature);
+                }
+                // next clear old overrides
+                SparseArray<String> overrideConfigs = getOverridePackageName(oldSlot);
+                for (int i = 0; i < overrideConfigs.size(); i++) {
+                    int feature = overrideConfigs.keyAt(i);
+                    setOverridePackageName("", oldSlot, feature);
+                }
+            }
+        }
+        // Get the new config for each ImsService. For manifest queries, this will update the
+        // number of slots.
+        // This will get all services with the correct intent filter from PackageManager
+        List<ImsServiceInfo> infos = getImsServiceInfo(null);
+        for (ImsServiceInfo info : infos) {
+            ImsServiceInfo cachedInfo = mInstalledServicesCache.get(info.name);
+            if (cachedInfo != null) {
+                if (info.featureFromMetadata) {
+                    cachedInfo.replaceFeatures(info.getSupportedFeatures());
+                } else {
+                    // Remove features that are no longer supported by the device configuration.
+                    cachedInfo.getSupportedFeatures()
+                            .removeIf(filter -> filter.slotId >= newNumSlots);
+                }
+            } else {
+                // This is unexpected, put the new service on the queue to be added
+                mEventLog.log("handleMsimConfigChange: detected untracked service - " + info);
+                Log.w(TAG, "handleMsimConfigChange: detected untracked package, queueing to add "
+                        + info);
+                mHandler.obtainMessage(HANDLER_ADD_PACKAGE,
+                        info.name.getPackageName()).sendToTarget();
+            }
+        }
+
+        if (newNumSlots < oldLen) {
+            // A CarrierConfigChange will happen for the new slot, so only recalculate if there are
+            // less new slots because we need to remove the old capabilities.
+            calculateFeatureConfigurationChange();
+        }
+    }
+
     // Starts a dynamic query. Called from handler ONLY.
     private void startDynamicQuery(ImsServiceInfo service) {
+        // if not current device/carrier service, don't perform query. If this changes, this method
+        // will be called again.
+        if (!isDeviceService(service) && getSlotsForActiveCarrierService(service).isEmpty()) {
+            Log.i(TAG, "scheduleQueryForFeatures: skipping query for ImsService that is not"
+                    + " set as carrier/device ImsService.");
+            return;
+        }
+        mEventLog.log("startDynamicQuery - starting query for " + service);
         boolean queryStarted = mFeatureQueryManager.startQuery(service.name,
                 service.controllerFactory.getServiceInterface());
         if (!queryStarted) {
             Log.w(TAG, "startDynamicQuery: service could not connect. Retrying after delay.");
+            mEventLog.log("startDynamicQuery - query failed. Retrying in "
+                    + DELAY_DYNAMIC_QUERY_MS + " mS");
             scheduleQueryForFeatures(service, DELAY_DYNAMIC_QUERY_MS);
         } else {
             Log.d(TAG, "startDynamicQuery: Service queried, waiting for response.");
@@ -1116,34 +1440,63 @@
             Set<ImsFeatureConfiguration.FeatureSlotPair> features) {
         ImsServiceInfo service = getImsServiceInfoFromCache(name.getPackageName());
         if (service == null) {
-            Log.w(TAG, "handleFeaturesChanged: Couldn't find cached info for name: "
+            Log.w(TAG, "dynamicQueryComplete: Couldn't find cached info for name: "
                     + name);
             return;
         }
+        mEventLog.log("dynamicQueryComplete: for package " + name + ", features: "
+                + printFeatures(service.getSupportedFeatures()) + " -> " + printFeatures(features));
+        sanitizeFeatureConfig(features);
         // Add features to service
         service.replaceFeatures(features);
-        if (isActiveCarrierService(service)) {
-            // New ImsService is registered to active carrier services and must be newly
-            // bound.
-            bindImsService(service);
-            // Update existing device service features
-            updateImsServiceFeatures(getImsServiceInfoFromCache(mDeviceService));
-        } else if (isDeviceService(service)) {
-            // New ImsService is registered as device default and must be newly bound.
-            bindImsService(service);
+        // Wait until all queries have completed before changing the configuration to reduce churn.
+        if (!mFeatureQueryManager.isQueryInProgress()) {
+            if (mHandler.hasMessages(HANDLER_DYNAMIC_FEATURE_CHANGE)) {
+                mEventLog.log("[warning] dynamicQueryComplete - HANDLER_DYNAMIC_FEATURE_CHANGE "
+                        + "pending with calculateFeatureConfigurationChange()");
+            }
+            calculateFeatureConfigurationChange();
         }
     }
 
-    private String printFeatures(Set<ImsFeatureConfiguration.FeatureSlotPair> features) {
+    /**
+     * Ensure the feature includes MMTEL when it supports EMERGENCY_MMTEL, if not, remove.
+     */
+    private void sanitizeFeatureConfig(Set<ImsFeatureConfiguration.FeatureSlotPair> features) {
+        Set<ImsFeatureConfiguration.FeatureSlotPair> emergencyMmtelFeatures = features.stream()
+                .filter(feature -> feature.featureType == ImsFeature.FEATURE_EMERGENCY_MMTEL)
+                .collect(Collectors.toSet());
+        for (ImsFeatureConfiguration.FeatureSlotPair feature : emergencyMmtelFeatures) {
+            if (!features.contains(new ImsFeatureConfiguration.FeatureSlotPair(feature.slotId,
+                    ImsFeature.FEATURE_MMTEL))) {
+                features.remove(feature);
+            }
+        }
+    }
+
+    // Calculate the new configuration for the bound ImsServices.
+    // Should ONLY be called from the handler.
+    private void calculateFeatureConfigurationChange() {
+        for (ImsServiceInfo info : mInstalledServicesCache.values()) {
+            Set<ImsFeatureConfiguration.FeatureSlotPair> features = calculateFeaturesToCreate(info);
+            if (shouldFeaturesCauseBind(features)) {
+                bindImsServiceWithFeatures(info, features);
+            } else {
+                unbindImsService(info);
+            }
+        }
+    }
+
+    private static String printFeatures(Set<ImsFeatureConfiguration.FeatureSlotPair> features) {
         StringBuilder featureString = new StringBuilder();
-        featureString.append("features: [");
+        featureString.append(" features: [");
         if (features != null) {
             for (ImsFeatureConfiguration.FeatureSlotPair feature : features) {
                 featureString.append("{");
                 featureString.append(feature.slotId);
                 featureString.append(",");
-                featureString.append(feature.featureType);
-                featureString.append("} ");
+                featureString.append(ImsFeature.FEATURE_LOG_MAP.get(feature.featureType));
+                featureString.append("}");
             }
             featureString.append("]");
         }
@@ -1171,27 +1524,10 @@
     // get all packages that support ImsServices.
     private List<ImsServiceInfo> getImsServiceInfo(String packageName) {
         List<ImsServiceInfo> infos = new ArrayList<>();
-        if (!mIsDynamicBinding) {
-            // always return the same ImsService info.
-            infos.addAll(getStaticImsService());
-        } else {
-            // Search for Current ImsService implementations
-            infos.addAll(searchForImsServices(packageName, mImsServiceControllerFactory));
-            // Search for compat ImsService Implementations
-            infos.addAll(searchForImsServices(packageName, mImsServiceControllerFactoryCompat));
-        }
-        return infos;
-    }
-
-    private List<ImsServiceInfo> getStaticImsService() {
-        List<ImsServiceInfo> infos = new ArrayList<>();
-
-        ImsServiceInfo info = new ImsServiceInfo(mNumSlots);
-        info.name = mStaticComponent;
-        info.controllerFactory = mImsServiceControllerFactoryStaticBindingCompat;
-        info.addFeatureForAllSlots(ImsFeature.FEATURE_EMERGENCY_MMTEL);
-        info.addFeatureForAllSlots(ImsFeature.FEATURE_MMTEL);
-        infos.add(info);
+        // Search for Current ImsService implementations
+        infos.addAll(searchForImsServices(packageName, mImsServiceControllerFactory));
+        // Search for compat ImsService Implementations
+        infos.addAll(searchForImsServices(packageName, mImsServiceControllerFactoryCompat));
         return infos;
     }
 
@@ -1206,11 +1542,11 @@
         for (ResolveInfo entry : packageManager.queryIntentServicesAsUser(
                 serviceIntent,
                 PackageManager.GET_META_DATA,
-                mContext.getUserId())) {
+                UserHandle.getUserHandleForUid(UserHandle.myUserId()))) {
             ServiceInfo serviceInfo = entry.serviceInfo;
 
             if (serviceInfo != null) {
-                ImsServiceInfo info = new ImsServiceInfo(mNumSlots);
+                ImsServiceInfo info = new ImsServiceInfo();
                 info.name = new ComponentName(serviceInfo.packageName, serviceInfo.name);
                 info.controllerFactory = controllerFactory;
 
@@ -1222,15 +1558,17 @@
                 if (isDeviceService(info)
                         || mImsServiceControllerFactoryCompat == controllerFactory) {
                     if (serviceInfo.metaData != null) {
-                        if (serviceInfo.metaData.getBoolean(METADATA_EMERGENCY_MMTEL_FEATURE,
-                                false)) {
-                            info.addFeatureForAllSlots(ImsFeature.FEATURE_EMERGENCY_MMTEL);
-                        }
                         if (serviceInfo.metaData.getBoolean(METADATA_MMTEL_FEATURE, false)) {
-                            info.addFeatureForAllSlots(ImsFeature.FEATURE_MMTEL);
+                            info.addFeatureForAllSlots(mNumSlots, ImsFeature.FEATURE_MMTEL);
+                            // only allow FEATURE_EMERGENCY_MMTEL if FEATURE_MMTEL is defined.
+                            if (serviceInfo.metaData.getBoolean(METADATA_EMERGENCY_MMTEL_FEATURE,
+                                    false)) {
+                                info.addFeatureForAllSlots(mNumSlots,
+                                        ImsFeature.FEATURE_EMERGENCY_MMTEL);
+                            }
                         }
                         if (serviceInfo.metaData.getBoolean(METADATA_RCS_FEATURE, false)) {
-                            info.addFeatureForAllSlots(ImsFeature.FEATURE_RCS);
+                            info.addFeatureForAllSlots(mNumSlots, ImsFeature.FEATURE_RCS);
                         }
                     }
                     // Only dynamic query if we are not a compat version of ImsService and the
@@ -1261,4 +1599,72 @@
         }
         return infos;
     }
+
+    // Dump is called on the main thread, since ImsResolver Handler is also handled on main thread,
+    // we shouldn't need to worry about concurrent access of private params.
+    public void dump(FileDescriptor fd, PrintWriter printWriter, String[] args) {
+        IndentingPrintWriter pw = new IndentingPrintWriter(printWriter, "  ");
+        pw.println("ImsResolver:");
+        pw.increaseIndent();
+        pw.println("Configurations:");
+        pw.increaseIndent();
+        pw.println("Device:");
+        pw.increaseIndent();
+        for (Integer i : mDeviceServices.keySet()) {
+            pw.println(ImsFeature.FEATURE_LOG_MAP.get(i) + " -> " + mDeviceServices.get(i));
+        }
+        pw.decreaseIndent();
+        pw.println("Carrier: ");
+        pw.increaseIndent();
+        for (int i = 0; i < mNumSlots; i++) {
+            for (int j = 0; j < MmTelFeature.FEATURE_MAX; j++) {
+                pw.print("slot=");
+                pw.print(i);
+                pw.print(", feature=");
+                pw.print(ImsFeature.FEATURE_LOG_MAP.getOrDefault(j, "?"));
+                pw.println(": ");
+                pw.increaseIndent();
+                String name = getCarrierConfiguredPackageName(i, j);
+                pw.println(TextUtils.isEmpty(name) ? "none" : name);
+                pw.decreaseIndent();
+            }
+        }
+        pw.decreaseIndent();
+        pw.decreaseIndent();
+        pw.println("Bound Features:");
+        pw.increaseIndent();
+        for (int i = 0; i < mNumSlots; i++) {
+            for (int j = 0; j < MmTelFeature.FEATURE_MAX; j++) {
+                pw.print("slot=");
+                pw.print(i);
+                pw.print(", feature=");
+                pw.print(ImsFeature.FEATURE_LOG_MAP.getOrDefault(j, "?"));
+                pw.println(": ");
+                pw.increaseIndent();
+                ImsServiceController c = getImsServiceController(i, j);
+                pw.println(c == null ? "none" : c);
+                pw.decreaseIndent();
+            }
+        }
+        pw.decreaseIndent();
+        pw.println("Cached ImsServices:");
+        pw.increaseIndent();
+        for (ImsServiceInfo i : mInstalledServicesCache.values()) {
+            pw.println(i);
+        }
+        pw.decreaseIndent();
+        pw.println("Active controllers:");
+        pw.increaseIndent();
+        for (ImsServiceController c : mActiveControllers.values()) {
+            pw.println(c);
+            pw.increaseIndent();
+            c.dump(pw);
+            pw.decreaseIndent();
+        }
+        pw.decreaseIndent();
+        pw.println("Event Log:");
+        pw.increaseIndent();
+        mEventLog.dump(pw);
+        pw.decreaseIndent();
+    }
 }
diff --git a/src/java/com/android/internal/telephony/ims/ImsServiceController.java b/src/java/com/android/internal/telephony/ims/ImsServiceController.java
index fbfa3b0..bc5ddbe 100644
--- a/src/java/com/android/internal/telephony/ims/ImsServiceController.java
+++ b/src/java/com/android/internal/telephony/ims/ImsServiceController.java
@@ -20,13 +20,13 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.ServiceConnection;
-import android.content.pm.IPackageManager;
 import android.os.Handler;
 import android.os.HandlerThread;
 import android.os.IBinder;
 import android.os.IInterface;
 import android.os.RemoteException;
-import android.os.ServiceManager;
+import android.os.UserHandle;
+import android.permission.PermissionManager;
 import android.telephony.ims.ImsService;
 import android.telephony.ims.aidl.IImsConfig;
 import android.telephony.ims.aidl.IImsMmTelFeature;
@@ -35,17 +35,22 @@
 import android.telephony.ims.aidl.IImsServiceController;
 import android.telephony.ims.feature.ImsFeature;
 import android.telephony.ims.stub.ImsFeatureConfiguration;
+import android.util.LocalLog;
 import android.util.Log;
 
 import com.android.ims.internal.IImsFeatureStatusCallback;
 import com.android.ims.internal.IImsServiceFeatureCallback;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.telephony.ExponentialBackoff;
+import com.android.internal.telephony.util.TelephonyUtils;
 
+import java.io.PrintWriter;
 import java.util.HashSet;
 import java.util.Iterator;
 import java.util.Set;
 import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.CountDownLatch;
+import java.util.stream.Collectors;
 
 /**
  * Manages the Binding lifecycle of one ImsService as well as the relevant ImsFeatures that the
@@ -71,25 +76,26 @@
             synchronized (mLock) {
                 mIsBound = true;
                 mIsBinding = false;
-                Log.d(LOG_TAG, "ImsService(" + name + "): onServiceConnected with binder: "
-                        + service);
-                if (service != null) {
-                    try {
-                        setServiceController(service);
-                        notifyImsServiceReady();
-                        // create all associated features in the ImsService
-                        for (ImsFeatureConfiguration.FeatureSlotPair i : mImsFeatures) {
-                            addImsServiceFeature(i);
-                        }
-                    } catch (RemoteException e) {
-                        mIsBound = false;
-                        mIsBinding = false;
-                        // Remote exception means that the binder already died.
-                        cleanupConnection();
-                        startDelayedRebindToService();
-                        Log.e(LOG_TAG, "ImsService(" + name + ") RemoteException:"
-                                + e.getMessage());
+                try {
+                    mLocalLog.log("onServiceConnected");
+                    Log.d(LOG_TAG, "ImsService(" + name + "): onServiceConnected with binder: "
+                            + service);
+                    setServiceController(service);
+                    notifyImsServiceReady();
+                    // create all associated features in the ImsService
+                    for (ImsFeatureConfiguration.FeatureSlotPair i : mImsFeatures) {
+                        addImsServiceFeature(i);
                     }
+                } catch (RemoteException e) {
+                    mIsBound = false;
+                    mIsBinding = false;
+                    // Remote exception means that the binder already died.
+                    cleanupConnection();
+                    startDelayedRebindToService();
+                    mLocalLog.log("onConnected exception=" + e.getMessage() + ", retry in "
+                            + mBackoff.getCurrentDelay() + " mS");
+                    Log.e(LOG_TAG, "ImsService(" + name + ") RemoteException:"
+                            + e.getMessage());
                 }
             }
         }
@@ -100,6 +106,7 @@
                 mIsBinding = false;
             }
             cleanupConnection();
+            mLocalLog.log("onServiceDisconnected");
             Log.w(LOG_TAG, "ImsService(" + name + "): onServiceDisconnected. Waiting...");
             // Service disconnected, but we are still technically bound. Waiting for reconnect.
         }
@@ -110,27 +117,38 @@
                 mIsBinding = false;
                 mIsBound = false;
             }
+            if (mImsServiceConnection != null) {
+                // according to the docs, we should fully unbind before rebinding again.
+                mContext.unbindService(mImsServiceConnection);
+            }
             cleanupConnection();
             Log.w(LOG_TAG, "ImsService(" + name + "): onBindingDied. Starting rebind...");
             startDelayedRebindToService();
+            mLocalLog.log("onBindingDied, retrying in " + mBackoff.getCurrentDelay() + " mS");
         }
 
+        @Override
+        public void onNullBinding(ComponentName name) {
+            Log.w(LOG_TAG, "ImsService(" + name + "): onNullBinding. Removing.");
+            mLocalLog.log("onNullBinding");
+            synchronized (mLock) {
+                mIsBinding = false;
+                mIsBound = false;
+            }
+            cleanupConnection();
+            if (mCallbacks != null) {
+                // Will trigger an unbind.
+                mCallbacks.imsServiceBindPermanentError(getComponentName());
+            }
+        }
+
+        // Does not clear features, just removes all active features.
         private void cleanupConnection() {
             cleanupAllFeatures();
             cleanUpService();
         }
     }
 
-    private ImsService.Listener mFeatureChangedListener = new ImsService.Listener() {
-        @Override
-        public void onUpdateSupportedImsFeatures(ImsFeatureConfiguration c) {
-            if (mCallbacks == null) {
-                return;
-            }
-            mCallbacks.imsServiceFeaturesChanged(c, ImsServiceController.this);
-        }
-    };
-
     /**
      * Defines callbacks that are used by the ImsServiceController to notify when an ImsService
      * has created or removed a new feature as well as the associated ImsServiceController.
@@ -151,6 +169,12 @@
          */
         void imsServiceFeaturesChanged(ImsFeatureConfiguration config,
                 ImsServiceController controller);
+
+        /**
+         * Called by the ImsServiceController when there has been an error binding that is
+         * not recoverable, such as the ImsService returning a null binder.
+         */
+        void imsServiceBindPermanentError(ComponentName name);
     }
 
     /**
@@ -174,16 +198,17 @@
     private static final String LOG_TAG = "ImsServiceController";
     private static final int REBIND_START_DELAY_MS = 2 * 1000; // 2 seconds
     private static final int REBIND_MAXIMUM_DELAY_MS = 60 * 1000; // 1 minute
+    private static final long CHANGE_PERMISSION_TIMEOUT_MS = 15 * 1000; // 15 seconds
     private final ComponentName mComponentName;
     private final HandlerThread mHandlerThread = new HandlerThread("ImsServiceControllerHandler");
-    private final IPackageManager mPackageManager;
+    private final PermissionManager mPermissionManager;
     private ImsServiceControllerCallbacks mCallbacks;
     private ExponentialBackoff mBackoff;
 
     private boolean mIsBound = false;
     private boolean mIsBinding = false;
     // Set of a pair of slotId->feature
-    private HashSet<ImsFeatureConfiguration.FeatureSlotPair> mImsFeatures;
+    private Set<ImsFeatureConfiguration.FeatureSlotPair> mImsFeatures;
     // Binder interfaces to the features set in mImsFeatures;
     private HashSet<ImsFeatureContainer> mImsFeatureBinders = new HashSet<>();
     private IImsServiceController mIImsServiceController;
@@ -191,10 +216,22 @@
     private Set<IImsServiceFeatureCallback> mImsStatusCallbacks = ConcurrentHashMap.newKeySet();
     // Only added or removed, never accessed on purpose.
     private Set<ImsFeatureStatusCallback> mFeatureStatusCallbacks = new HashSet<>();
+    private final LocalLog mLocalLog = new LocalLog(10);
 
     protected final Object mLock = new Object();
     protected final Context mContext;
 
+    private ImsService.Listener mFeatureChangedListener = new ImsService.Listener() {
+        @Override
+        public void onUpdateSupportedImsFeatures(ImsFeatureConfiguration c) {
+            if (mCallbacks == null) {
+                return;
+            }
+            mLocalLog.log("onUpdateSupportedImsFeatures to " + c.getServiceFeatures());
+            mCallbacks.imsServiceFeaturesChanged(c, ImsServiceController.this);
+        }
+    };
+
     private class ImsFeatureContainer {
         public int slotId;
         public int featureType;
@@ -246,7 +283,8 @@
             @Override
             public void notifyImsFeatureStatus(int featureStatus) throws RemoteException {
                 Log.i(LOG_TAG, "notifyImsFeatureStatus: slot=" + mSlotId + ", feature="
-                        + mFeatureType + ", status=" + featureStatus);
+                        + ImsFeature.FEATURE_LOG_MAP.get(mFeatureType) + ", status="
+                        + ImsFeature.STATE_LOG_MAP.get(featureStatus));
                 sendImsFeatureStatusChanged(mSlotId, mFeatureType, featureStatus);
             }
         };
@@ -298,7 +336,8 @@
                 2, /* multiplier */
                 mHandlerThread.getLooper(),
                 mRestartImsServiceRunnable);
-        mPackageManager = IPackageManager.Stub.asInterface(ServiceManager.getService("package"));
+        mPermissionManager =
+                (PermissionManager) mContext.getSystemService(Context.PERMISSION_SERVICE);
     }
 
     @VisibleForTesting
@@ -315,7 +354,7 @@
                 2, /* multiplier */
                 handler,
                 mRestartImsServiceRunnable);
-        mPackageManager = null;
+        mPermissionManager = null;
     }
 
     /**
@@ -327,10 +366,11 @@
      * @return {@link true} if the service is in the process of being bound, {@link false} if it
      * has failed.
      */
-    public boolean bind(HashSet<ImsFeatureConfiguration.FeatureSlotPair> imsFeatureSet) {
+    public boolean bind(Set<ImsFeatureConfiguration.FeatureSlotPair> imsFeatureSet) {
         synchronized (mLock) {
             if (!mIsBound && !mIsBinding) {
                 mIsBinding = true;
+                sanitizeFeatureConfig(imsFeatureSet);
                 mImsFeatures = imsFeatureSet;
                 grantPermissionsToService();
                 Intent imsServiceIntent = new Intent(getServiceInterface()).setComponent(
@@ -338,17 +378,22 @@
                 mImsServiceConnection = new ImsServiceConnection();
                 int serviceFlags = Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE
                         | Context.BIND_IMPORTANT;
+                mLocalLog.log("binding " + imsFeatureSet);
                 Log.i(LOG_TAG, "Binding ImsService:" + mComponentName);
                 try {
-                    boolean bindSucceeded = startBindToService(imsServiceIntent,
+                    boolean bindSucceeded = mContext.bindService(imsServiceIntent,
                             mImsServiceConnection, serviceFlags);
                     if (!bindSucceeded) {
+                        mLocalLog.log("    binding failed, retrying in "
+                                + mBackoff.getCurrentDelay() + " mS");
                         mIsBinding = false;
                         mBackoff.notifyFailed();
                     }
                     return bindSucceeded;
                 } catch (Exception e) {
                     mBackoff.notifyFailed();
+                    mLocalLog.log("    binding exception=" + e.getMessage() + ", retrying in "
+                            + mBackoff.getCurrentDelay() + " mS");
                     Log.e(LOG_TAG, "Error binding (" + mComponentName + ") with exception: "
                             + e.getMessage() + ", rebinding in " + mBackoff.getCurrentDelay()
                             + " ms");
@@ -361,12 +406,18 @@
     }
 
     /**
-     * Starts the bind to the ImsService. Overridden by subclasses that need to access the service
-     * in a different fashion.
+     * Ensure the feature includes MMTEL when it supports EMERGENCY_MMTEL, if not, remove.
      */
-    protected boolean startBindToService(Intent intent, ImsServiceConnection connection,
-            int flags) {
-        return mContext.bindService(intent, connection, flags);
+    private void sanitizeFeatureConfig(Set<ImsFeatureConfiguration.FeatureSlotPair> features) {
+        Set<ImsFeatureConfiguration.FeatureSlotPair> emergencyMmtelFeatures = features.stream()
+                .filter(feature -> feature.featureType == ImsFeature.FEATURE_EMERGENCY_MMTEL)
+                .collect(Collectors.toSet());
+        for (ImsFeatureConfiguration.FeatureSlotPair feature : emergencyMmtelFeatures) {
+            if (!features.contains(new ImsFeatureConfiguration.FeatureSlotPair(feature.slotId,
+                    ImsFeature.FEATURE_MMTEL))) {
+                features.remove(feature);
+            }
+        }
     }
 
     /**
@@ -383,7 +434,10 @@
             changeImsServiceFeatures(new HashSet<>());
             removeImsServiceFeatureCallbacks();
             Log.i(LOG_TAG, "Unbinding ImsService: " + mComponentName);
+            mLocalLog.log("unbinding");
             mContext.unbindService(mImsServiceConnection);
+            mIsBound = false;
+            mIsBinding = false;
             cleanUpService();
         }
     }
@@ -393,9 +447,14 @@
      * ImsFeature that is removed, {@link IImsServiceController#removeImsFeature} is called.
      */
     public void changeImsServiceFeatures(
-            HashSet<ImsFeatureConfiguration.FeatureSlotPair> newImsFeatures)
+            Set<ImsFeatureConfiguration.FeatureSlotPair> newImsFeatures)
             throws RemoteException {
+        sanitizeFeatureConfig(newImsFeatures);
         synchronized (mLock) {
+            if (mImsFeatures.equals(newImsFeatures)) {
+                return;
+            }
+            mLocalLog.log("Features changed (" + mImsFeatures + "->" + newImsFeatures + ")");
             Log.i(LOG_TAG, "Features changed (" + mImsFeatures + "->" + newImsFeatures + ") for "
                     + "ImsService: " + mComponentName);
             HashSet<ImsFeatureConfiguration.FeatureSlotPair> oldImsFeatures =
@@ -445,19 +504,28 @@
      */
     public void addImsServiceFeatureCallback(IImsServiceFeatureCallback callback) {
         mImsStatusCallbacks.add(callback);
+        Set<ImsFeatureConfiguration.FeatureSlotPair> features;
         synchronized (mLock) {
             if (mImsFeatures == null || mImsFeatures.isEmpty()) {
                 return;
             }
-            // notify the new status callback of the features that are available.
-            try {
-                for (ImsFeatureConfiguration.FeatureSlotPair i : mImsFeatures) {
-                    callback.imsFeatureCreated(i.slotId, i.featureType);
-                }
-            } catch (RemoteException e) {
-                Log.w(LOG_TAG, "addImsServiceFeatureCallback: exception notifying callback");
-            }
+            features = new HashSet<>(mImsFeatures);
         }
+        // notify the new status callback of the features that are available.
+        try {
+            for (ImsFeatureConfiguration.FeatureSlotPair i : features) {
+                callback.imsFeatureCreated(i.slotId, i.featureType);
+            }
+        } catch (RemoteException e) {
+            Log.w(LOG_TAG, "addImsServiceFeatureCallback: exception notifying callback");
+        }
+    }
+
+    /**
+     * Removes a previously registered callback if it was associated with this feature.
+     */
+    public void removeImsServiceFeatureCallback(IImsServiceFeatureCallback callback) {
+        mImsStatusCallbacks.remove(callback);
     }
 
     public void enableIms(int slotId) {
@@ -584,17 +652,27 @@
         mBackoff.start();
     }
 
-    // Grant runtime permissions to ImsService. PackageManager ensures that the ImsService is
+    // Grant runtime permissions to ImsService. PermissionManager ensures that the ImsService is
     // system/signed before granting permissions.
     private void grantPermissionsToService() {
+        mLocalLog.log("grant permissions to " + getComponentName());
         Log.i(LOG_TAG, "Granting Runtime permissions to:" + getComponentName());
         String[] pkgToGrant = {mComponentName.getPackageName()};
         try {
-            if (mPackageManager != null) {
-                mPackageManager.grantDefaultPermissionsToEnabledImsServices(pkgToGrant,
-                        mContext.getUserId());
+            if (mPermissionManager != null) {
+                CountDownLatch latch = new CountDownLatch(1);
+                mPermissionManager.grantDefaultPermissionsToEnabledImsServices(
+                        pkgToGrant, UserHandle.of(UserHandle.myUserId()), Runnable::run,
+                        isSuccess -> {
+                            if (isSuccess) {
+                                latch.countDown();
+                            } else {
+                                Log.e(LOG_TAG, "Failed to grant permissions to service.");
+                            }
+                        });
+                TelephonyUtils.waitUntilReady(latch, CHANGE_PERMISSION_TIMEOUT_MS);
             }
-        } catch (RemoteException e) {
+        } catch (RuntimeException e) {
             Log.w(LOG_TAG, "Unable to grant permissions, binder died.");
         }
     }
@@ -658,12 +736,12 @@
             IInterface f = createImsFeature(featurePair.slotId, featurePair.featureType,
                     c.getCallback());
             addImsFeatureBinder(featurePair.slotId, featurePair.featureType, f);
-            // Signal ImsResolver to change supported ImsFeatures for this ImsServiceController
-            mCallbacks.imsServiceFeatureCreated(featurePair.slotId, featurePair.featureType, this);
         } else {
             // Don't update ImsService for emergency MMTEL feature.
             Log.i(LOG_TAG, "supports emergency calling on slot " + featurePair.slotId);
         }
+        // Signal ImsResolver to change supported ImsFeatures for this ImsServiceController
+        mCallbacks.imsServiceFeatureCreated(featurePair.slotId, featurePair.featureType, this);
         // Send callback to ImsServiceProxy to change supported ImsFeatures including emergency
         // MMTEL state.
         sendImsFeatureCreatedCallback(featurePair.slotId, featurePair.featureType);
@@ -675,6 +753,8 @@
             Log.w(LOG_TAG, "removeImsServiceFeature called with null values.");
             return;
         }
+        // Signal ImsResolver to change supported ImsFeatures for this ImsServiceController
+        mCallbacks.imsServiceFeatureRemoved(featurePair.slotId, featurePair.featureType, this);
         if (featurePair.featureType != ImsFeature.FEATURE_EMERGENCY_MMTEL) {
             ImsFeatureStatusCallback callbackToRemove = mFeatureStatusCallbacks.stream().filter(c ->
                     c.mSlotId == featurePair.slotId && c.mFeatureType == featurePair.featureType)
@@ -684,15 +764,14 @@
                 mFeatureStatusCallbacks.remove(callbackToRemove);
             }
             removeImsFeatureBinder(featurePair.slotId, featurePair.featureType);
-            // Signal ImsResolver to change supported ImsFeatures for this ImsServiceController
-            mCallbacks.imsServiceFeatureRemoved(featurePair.slotId, featurePair.featureType, this);
             try {
                 removeImsFeature(featurePair.slotId, featurePair.featureType,
                         (callbackToRemove != null ? callbackToRemove.getCallback() : null));
             } catch (RemoteException e) {
                 // The connection to this ImsService doesn't exist. This may happen if the service
                 // has died and we are removing features.
-                Log.i(LOG_TAG, "Couldn't remove feature {" + featurePair.featureType
+                Log.i(LOG_TAG, "Couldn't remove feature {"
+                        + ImsFeature.FEATURE_LOG_MAP.get(featurePair.featureType)
                         + "}, connection is down: " + e.getMessage());
             }
         } else {
@@ -767,4 +846,18 @@
             setServiceController(null);
         }
     }
+
+    @Override
+    public String toString() {
+        synchronized (mLock) {
+            return "[ImsServiceController: componentName=" + getComponentName() + ", features="
+                    + mImsFeatures + ", isBinding=" + mIsBinding + ", isBound=" + mIsBound
+                    + ", serviceController=" + getImsServiceController() + ", rebindDelay="
+                    + getRebindDelay() + "]";
+        }
+    }
+
+    public void dump(PrintWriter printWriter) {
+        mLocalLog.dump(printWriter);
+    }
 }
diff --git a/src/java/com/android/internal/telephony/ims/ImsServiceControllerCompat.java b/src/java/com/android/internal/telephony/ims/ImsServiceControllerCompat.java
index 11cae27..835d780 100644
--- a/src/java/com/android/internal/telephony/ims/ImsServiceControllerCompat.java
+++ b/src/java/com/android/internal/telephony/ims/ImsServiceControllerCompat.java
@@ -171,7 +171,7 @@
         return mServiceController != null;
     }
 
-    protected MmTelInterfaceAdapter getInterface(int slotId, IImsFeatureStatusCallback c)
+    private MmTelInterfaceAdapter getInterface(int slotId, IImsFeatureStatusCallback c)
             throws RemoteException {
         IImsMMTelFeature feature = mServiceController.createMMTelFeature(slotId, c);
         if (feature == null) {
diff --git a/src/java/com/android/internal/telephony/ims/ImsServiceControllerStaticCompat.java b/src/java/com/android/internal/telephony/ims/ImsServiceControllerStaticCompat.java
deleted file mode 100644
index bd033db..0000000
--- a/src/java/com/android/internal/telephony/ims/ImsServiceControllerStaticCompat.java
+++ /dev/null
@@ -1,119 +0,0 @@
-/*
- * 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.ims;
-
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.content.ServiceConnection;
-import android.os.IBinder;
-import android.os.RemoteException;
-import android.os.ServiceManager;
-import android.util.Log;
-
-import com.android.ims.internal.IImsFeatureStatusCallback;
-import com.android.ims.internal.IImsService;
-
-/**
- * A compat layer for communicating with older devices that still used the ServiceManager to get
- * the ImsService.
- */
-
-public class ImsServiceControllerStaticCompat extends ImsServiceControllerCompat {
-
-    private static final String TAG = "ImsSCStaticCompat";
-
-    private static final String IMS_SERVICE_NAME = "ims";
-
-    private class ImsDeathRecipient implements IBinder.DeathRecipient {
-
-        private ComponentName mComponentName;
-        private ServiceConnection mServiceConnection;
-
-        ImsDeathRecipient(ComponentName name, ServiceConnection conn) {
-            mComponentName = name;
-            mServiceConnection = conn;
-        }
-
-        @Override
-        public void binderDied() {
-            Log.e(TAG, "ImsService(" + mComponentName + ") died. Restarting...");
-            // This is hacky... ImsServiceController uses the traditional service binding procedure,
-            // so we have to emulate it when using a persistent service.
-            mServiceConnection.onBindingDied(mComponentName);
-        }
-    }
-
-    private IImsService mImsServiceCompat = null;
-    private ImsDeathRecipient mImsDeathRecipient = null;
-
-    public ImsServiceControllerStaticCompat(Context context, ComponentName componentName,
-            ImsServiceController.ImsServiceControllerCallbacks callbacks) {
-        super(context, componentName, callbacks);
-    }
-
-    @Override
-    public boolean startBindToService(Intent intent, ImsServiceConnection connection, int flags) {
-        IBinder binder = ServiceManager.checkService(IMS_SERVICE_NAME);
-
-        if (binder == null) {
-            return false;
-        }
-        // This is a little hacky, but we are going to call the onServiceConnected to "pretend" like
-        // bindService has completed here, which will pass the binder to setServiceController and
-        // set up all supporting structures.
-        ComponentName name = new ComponentName(mContext, ImsServiceControllerStaticCompat.class);
-        connection.onServiceConnected(name, binder);
-        try {
-            mImsDeathRecipient = new ImsDeathRecipient(name, connection);
-            binder.linkToDeath(mImsDeathRecipient, 0);
-        } catch (RemoteException e) {
-            // The binder connection is already dead.. signal to the ImsServiceController to retry.
-            mImsDeathRecipient.binderDied();
-            mImsDeathRecipient = null;
-        }
-        return true;
-    }
-
-    @Override
-    protected void setServiceController(IBinder serviceController) {
-        if (serviceController == null) {
-            // The service controller has been set to null, meaning it has been unbound or died.
-            // Unlink if needed.
-            if (mImsServiceCompat != null) {
-                mImsServiceCompat.asBinder().unlinkToDeath(mImsDeathRecipient, 0);
-            }
-            mImsDeathRecipient = null;
-        }
-        mImsServiceCompat = IImsService.Stub.asInterface(serviceController);
-    }
-
-    @Override
-    // used for add/remove features and cleanup in ImsServiceController.
-    protected boolean isServiceControllerAvailable() {
-        return mImsServiceCompat != null;
-    }
-
-    @Override
-    protected MmTelInterfaceAdapter getInterface(int slotId, IImsFeatureStatusCallback c) {
-        if (mImsServiceCompat == null) {
-            Log.w(TAG, "getInterface: IImsService returned null.");
-            return null;
-        }
-        return new ImsServiceInterfaceAdapter(slotId, mImsServiceCompat.asBinder());
-    }
-}
diff --git a/src/java/com/android/internal/telephony/ims/ImsServiceFeatureQueryManager.java b/src/java/com/android/internal/telephony/ims/ImsServiceFeatureQueryManager.java
index cbeeead..638b76f 100644
--- a/src/java/com/android/internal/telephony/ims/ImsServiceFeatureQueryManager.java
+++ b/src/java/com/android/internal/telephony/ims/ImsServiceFeatureQueryManager.java
@@ -25,6 +25,7 @@
 import android.telephony.ims.stub.ImsFeatureConfiguration;
 import android.util.Log;
 
+import java.util.Collections;
 import java.util.HashMap;
 import java.util.Map;
 import java.util.Set;
@@ -71,8 +72,9 @@
             if (service != null) {
                 queryImsFeatures(IImsServiceController.Stub.asInterface(service));
             } else {
-                Log.w(LOG_TAG, "onServiceConnected: " + name + " binder null, cleaning up.");
+                Log.w(LOG_TAG, "onServiceConnected: " + name + " binder null.");
                 cleanup();
+                mListener.onPermanentError(name);
             }
         }
 
@@ -81,6 +83,21 @@
             Log.w(LOG_TAG, "onServiceDisconnected for component: " + name);
         }
 
+        @Override
+        public void onBindingDied(ComponentName name) {
+            Log.w(LOG_TAG, "onBindingDied: " + name);
+            cleanup();
+            // retry again!
+            mListener.onError(name);
+        }
+
+        @Override
+        public void onNullBinding(ComponentName name) {
+            Log.w(LOG_TAG, "onNullBinding: " + name);
+            cleanup();
+            mListener.onPermanentError(name);
+        }
+
         private void queryImsFeatures(IImsServiceController controller) {
             ImsFeatureConfiguration config;
             try {
@@ -88,10 +105,18 @@
             } catch (Exception e) {
                 Log.w(LOG_TAG, "queryImsFeatures - error: " + e);
                 cleanup();
+                // Retry again!
                 mListener.onError(mName);
                 return;
             }
-            Set<ImsFeatureConfiguration.FeatureSlotPair> servicePairs = config.getServiceFeatures();
+            Set<ImsFeatureConfiguration.FeatureSlotPair> servicePairs;
+            if (config == null) {
+                // ensure that if the ImsService sent a null config, we return an empty feature
+                // set to the ImsResolver.
+                servicePairs = Collections.emptySet();
+            } else {
+                servicePairs = config.getServiceFeatures();
+            }
             // Complete, remove from active queries and notify.
             cleanup();
             mListener.onComplete(mName, servicePairs);
@@ -117,6 +142,11 @@
          * Called when a query has failed and should be retried.
          */
         void onError(ComponentName name);
+
+        /**
+         * Called when a query has failed due to a permanent error and should not be retried.
+         */
+        void onPermanentError(ComponentName name);
     }
 
     // Maps an active ImsService query (by Package Name String) its query.
diff --git a/src/java/com/android/internal/telephony/ims/ImsServiceInterfaceAdapter.java b/src/java/com/android/internal/telephony/ims/ImsServiceInterfaceAdapter.java
deleted file mode 100644
index f554e6f..0000000
--- a/src/java/com/android/internal/telephony/ims/ImsServiceInterfaceAdapter.java
+++ /dev/null
@@ -1,128 +0,0 @@
-/*
- * 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.ims;
-
-import android.app.PendingIntent;
-import android.os.IBinder;
-import android.os.Message;
-import android.os.RemoteException;
-import android.telephony.ims.ImsCallProfile;
-import android.telephony.ims.compat.feature.ImsFeature;
-
-import com.android.ims.internal.IImsCallSession;
-import com.android.ims.internal.IImsConfig;
-import com.android.ims.internal.IImsEcbm;
-import com.android.ims.internal.IImsMultiEndpoint;
-import com.android.ims.internal.IImsRegistrationListener;
-import com.android.ims.internal.IImsService;
-import com.android.ims.internal.IImsUt;
-
-/**
- * Compatibility layer for IImsService implementations of IMS. Converts "generic" MMTel commands
- * to implementation.
- */
-
-public class ImsServiceInterfaceAdapter extends MmTelInterfaceAdapter {
-
-    private static final int SERVICE_ID = ImsFeature.MMTEL;
-
-    public ImsServiceInterfaceAdapter(int slotId, IBinder binder) {
-        super(slotId, binder);
-    }
-
-    public int startSession(PendingIntent incomingCallIntent, IImsRegistrationListener listener)
-            throws RemoteException {
-        return getInterface().open(mSlotId, ImsFeature.MMTEL, incomingCallIntent, listener);
-    }
-
-    public void endSession(int sessionId) throws RemoteException {
-        getInterface().close(sessionId);
-    }
-
-    public boolean isConnected(int callSessionType, int callType) throws RemoteException {
-        return getInterface().isConnected(SERVICE_ID, callSessionType, callType);
-    }
-
-    public boolean isOpened() throws RemoteException {
-        return getInterface().isOpened(SERVICE_ID);
-    }
-
-    public int getFeatureState() throws RemoteException {
-        return ImsFeature.STATE_READY;
-    }
-
-    public void addRegistrationListener(IImsRegistrationListener listener) throws RemoteException {
-        getInterface().addRegistrationListener(mSlotId, ImsFeature.MMTEL, listener);
-    }
-
-    public void removeRegistrationListener(IImsRegistrationListener listener)
-            throws RemoteException {
-        // Not Implemented in the old ImsService. If the registration listener becomes invalid, the
-        // ImsService will remove it.
-    }
-
-    public ImsCallProfile createCallProfile(int sessionId, int callSessionType, int callType)
-            throws RemoteException {
-        return getInterface().createCallProfile(sessionId, callSessionType, callType);
-    }
-
-    public IImsCallSession createCallSession(int sessionId, ImsCallProfile profile)
-            throws RemoteException {
-        return getInterface().createCallSession(sessionId, profile, null);
-    }
-
-    public IImsCallSession getPendingCallSession(int sessionId, String callId)
-            throws RemoteException {
-        return getInterface().getPendingCallSession(sessionId, callId);
-    }
-
-    public IImsUt getUtInterface() throws RemoteException {
-        return getInterface().getUtInterface(SERVICE_ID);
-    }
-
-    public IImsConfig getConfigInterface() throws RemoteException {
-        return getInterface().getConfigInterface(mSlotId);
-    }
-
-    public void turnOnIms() throws RemoteException {
-        getInterface().turnOnIms(mSlotId);
-    }
-
-    public void turnOffIms() throws RemoteException {
-        getInterface().turnOffIms(mSlotId);
-    }
-
-    public IImsEcbm getEcbmInterface() throws RemoteException {
-        return getInterface().getEcbmInterface(SERVICE_ID);
-    }
-
-    public void setUiTTYMode(int uiTtyMode, Message onComplete) throws RemoteException {
-        getInterface().setUiTTYMode(SERVICE_ID, uiTtyMode, onComplete);
-    }
-
-    public IImsMultiEndpoint getMultiEndpointInterface() throws RemoteException {
-        return getInterface().getMultiEndpointInterface(SERVICE_ID);
-    }
-
-    private IImsService getInterface() throws RemoteException {
-        IImsService feature = IImsService.Stub.asInterface(mBinder);
-        if (feature == null) {
-            throw new RemoteException("Binder not Available");
-        }
-        return feature;
-    }
-}
diff --git a/src/java/com/android/internal/telephony/ims/RcsEventQueryHelper.java b/src/java/com/android/internal/telephony/ims/RcsEventQueryHelper.java
deleted file mode 100644
index fcd69fa..0000000
--- a/src/java/com/android/internal/telephony/ims/RcsEventQueryHelper.java
+++ /dev/null
@@ -1,196 +0,0 @@
-/*
- * Copyright (C) 2019 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.ims;
-
-import static android.provider.Telephony.RcsColumns.RcsEventTypes.ICON_CHANGED_EVENT_TYPE;
-import static android.provider.Telephony.RcsColumns.RcsEventTypes.NAME_CHANGED_EVENT_TYPE;
-import static android.provider.Telephony.RcsColumns.RcsEventTypes.PARTICIPANT_ALIAS_CHANGED_EVENT_TYPE;
-import static android.provider.Telephony.RcsColumns.RcsEventTypes.PARTICIPANT_JOINED_EVENT_TYPE;
-import static android.provider.Telephony.RcsColumns.RcsEventTypes.PARTICIPANT_LEFT_EVENT_TYPE;
-import static android.provider.Telephony.RcsColumns.RcsGroupThreadColumns.RCS_GROUP_THREAD_URI;
-import static android.provider.Telephony.RcsColumns.RcsParticipantColumns.RCS_PARTICIPANT_URI;
-import static android.provider.Telephony.RcsColumns.RcsParticipantEventColumns.ALIAS_CHANGE_EVENT_URI_PART;
-import static android.provider.Telephony.RcsColumns.RcsParticipantEventColumns.NEW_ALIAS_COLUMN;
-import static android.provider.Telephony.RcsColumns.RcsThreadColumns.RCS_THREAD_ID_COLUMN;
-import static android.provider.Telephony.RcsColumns.RcsThreadEventColumns.DESTINATION_PARTICIPANT_ID_COLUMN;
-import static android.provider.Telephony.RcsColumns.RcsThreadEventColumns.EVENT_TYPE_COLUMN;
-import static android.provider.Telephony.RcsColumns.RcsThreadEventColumns.ICON_CHANGED_URI_PART;
-import static android.provider.Telephony.RcsColumns.RcsThreadEventColumns.NAME_CHANGED_URI_PART;
-import static android.provider.Telephony.RcsColumns.RcsThreadEventColumns.NEW_ICON_URI_COLUMN;
-import static android.provider.Telephony.RcsColumns.RcsThreadEventColumns.NEW_NAME_COLUMN;
-import static android.provider.Telephony.RcsColumns.RcsThreadEventColumns.PARTICIPANT_JOINED_URI_PART;
-import static android.provider.Telephony.RcsColumns.RcsThreadEventColumns.PARTICIPANT_LEFT_URI_PART;
-import static android.provider.Telephony.RcsColumns.RcsThreadEventColumns.SOURCE_PARTICIPANT_ID_COLUMN;
-import static android.provider.Telephony.RcsColumns.RcsThreadEventColumns.TIMESTAMP_COLUMN;
-import static android.provider.Telephony.RcsColumns.RcsUnifiedEventHelper.RCS_EVENT_QUERY_URI;
-import static android.telephony.ims.RcsQueryContinuationToken.QUERY_CONTINUATION_TOKEN;
-
-import android.content.ContentResolver;
-import android.content.ContentValues;
-import android.database.Cursor;
-import android.net.Uri;
-import android.os.Bundle;
-import android.os.RemoteException;
-import android.telephony.Rlog;
-import android.telephony.ims.RcsEventDescriptor;
-import android.telephony.ims.RcsEventQueryResultDescriptor;
-import android.telephony.ims.RcsGroupThreadIconChangedEventDescriptor;
-import android.telephony.ims.RcsGroupThreadNameChangedEventDescriptor;
-import android.telephony.ims.RcsGroupThreadParticipantJoinedEventDescriptor;
-import android.telephony.ims.RcsGroupThreadParticipantLeftEventDescriptor;
-import android.telephony.ims.RcsParticipantAliasChangedEventDescriptor;
-import android.telephony.ims.RcsQueryContinuationToken;
-
-import java.util.ArrayList;
-import java.util.List;
-
-class RcsEventQueryHelper {
-    private final ContentResolver mContentResolver;
-
-    RcsEventQueryHelper(ContentResolver contentResolver) {
-        mContentResolver = contentResolver;
-    }
-
-    Uri getParticipantEventInsertionUri(int participantId) {
-        return RCS_PARTICIPANT_URI.buildUpon().appendPath(Integer.toString(participantId))
-                .appendPath(ALIAS_CHANGE_EVENT_URI_PART).build();
-    }
-
-    RcsEventQueryResultDescriptor performEventQuery(Bundle bundle) throws RemoteException {
-        RcsQueryContinuationToken continuationToken = null;
-        List<RcsEventDescriptor> eventList = new ArrayList<>();
-
-        try (Cursor cursor = mContentResolver.query(RCS_EVENT_QUERY_URI, null, bundle, null)) {
-            if (cursor == null) {
-                throw new RemoteException("Event query failed, bundle: " + bundle);
-            }
-
-            while (cursor.moveToNext()) {
-                int eventType = cursor.getInt(cursor.getColumnIndex(EVENT_TYPE_COLUMN));
-                switch (eventType) {
-                    case PARTICIPANT_ALIAS_CHANGED_EVENT_TYPE:
-                        eventList.add(createNewParticipantAliasChangedEvent(cursor));
-                        break;
-                    case PARTICIPANT_JOINED_EVENT_TYPE:
-                        eventList.add(createNewParticipantJoinedEvent(cursor));
-                        break;
-                    case PARTICIPANT_LEFT_EVENT_TYPE:
-                        eventList.add(createNewParticipantLeftEvent(cursor));
-                        break;
-                    case NAME_CHANGED_EVENT_TYPE:
-                        eventList.add(createNewGroupNameChangedEvent(cursor));
-                        break;
-                    case ICON_CHANGED_EVENT_TYPE:
-                        eventList.add(createNewGroupIconChangedEvent(cursor));
-                        break;
-                    default:
-                        Rlog.e(RcsMessageStoreController.TAG,
-                                "RcsEventQueryHelper: invalid event type: " + eventType);
-                }
-            }
-
-            Bundle cursorExtras = cursor.getExtras();
-            if (cursorExtras != null) {
-                continuationToken = cursorExtras.getParcelable(QUERY_CONTINUATION_TOKEN);
-            }
-        }
-
-        return new RcsEventQueryResultDescriptor(continuationToken, eventList);
-    }
-
-    int createGroupThreadEvent(int eventType, long timestamp, int threadId,
-            int originationParticipantId, ContentValues eventSpecificValues)
-            throws RemoteException {
-        ContentValues values = new ContentValues(eventSpecificValues);
-        values.put(EVENT_TYPE_COLUMN, eventType);
-        values.put(TIMESTAMP_COLUMN, timestamp);
-        values.put(SOURCE_PARTICIPANT_ID_COLUMN, originationParticipantId);
-
-        Uri eventUri = RCS_GROUP_THREAD_URI.buildUpon().appendPath(
-                Integer.toString(threadId)).appendPath(getPathForEventType(eventType)).build();
-        Uri insertionUri = mContentResolver.insert(eventUri, values);
-
-        int eventId = 0;
-        if (insertionUri != null) {
-            eventId = Integer.parseInt(insertionUri.getLastPathSegment());
-        }
-
-        if (eventId <= 0) {
-            throw new RemoteException(
-                "Could not create event with type: " + eventType + " on thread: " + threadId);
-        }
-        return eventId;
-    }
-
-    private String getPathForEventType(int eventType) throws RemoteException {
-        switch (eventType) {
-            case PARTICIPANT_JOINED_EVENT_TYPE:
-                return PARTICIPANT_JOINED_URI_PART;
-            case PARTICIPANT_LEFT_EVENT_TYPE:
-                return PARTICIPANT_LEFT_URI_PART;
-            case NAME_CHANGED_EVENT_TYPE:
-                return NAME_CHANGED_URI_PART;
-            case ICON_CHANGED_EVENT_TYPE:
-                return ICON_CHANGED_URI_PART;
-            default:
-                throw new RemoteException("Event type unrecognized: " + eventType);
-        }
-    }
-
-    private RcsGroupThreadIconChangedEventDescriptor createNewGroupIconChangedEvent(Cursor cursor) {
-        String newIcon = cursor.getString(cursor.getColumnIndex(NEW_ICON_URI_COLUMN));
-
-        return new RcsGroupThreadIconChangedEventDescriptor(
-                cursor.getLong(cursor.getColumnIndex(TIMESTAMP_COLUMN)),
-                cursor.getInt(cursor.getColumnIndex(RCS_THREAD_ID_COLUMN)),
-                cursor.getInt(cursor.getColumnIndex(SOURCE_PARTICIPANT_ID_COLUMN)),
-                newIcon == null ? null : Uri.parse(newIcon));
-    }
-
-    private RcsGroupThreadNameChangedEventDescriptor createNewGroupNameChangedEvent(Cursor cursor) {
-        return new RcsGroupThreadNameChangedEventDescriptor(
-                cursor.getLong(cursor.getColumnIndex(TIMESTAMP_COLUMN)),
-                cursor.getInt(cursor.getColumnIndex(RCS_THREAD_ID_COLUMN)),
-                cursor.getInt(cursor.getColumnIndex(SOURCE_PARTICIPANT_ID_COLUMN)),
-                cursor.getString(cursor.getColumnIndex(NEW_NAME_COLUMN)));
-    }
-
-    private RcsGroupThreadParticipantLeftEventDescriptor
-            createNewParticipantLeftEvent(Cursor cursor) {
-        return new RcsGroupThreadParticipantLeftEventDescriptor(
-                cursor.getLong(cursor.getColumnIndex(TIMESTAMP_COLUMN)),
-                cursor.getInt(cursor.getColumnIndex(RCS_THREAD_ID_COLUMN)),
-                cursor.getInt(cursor.getColumnIndex(SOURCE_PARTICIPANT_ID_COLUMN)),
-                cursor.getInt(cursor.getColumnIndex(DESTINATION_PARTICIPANT_ID_COLUMN)));
-    }
-
-    private RcsGroupThreadParticipantJoinedEventDescriptor
-            createNewParticipantJoinedEvent(Cursor cursor) {
-        return new RcsGroupThreadParticipantJoinedEventDescriptor(
-                cursor.getLong(cursor.getColumnIndex(TIMESTAMP_COLUMN)),
-                cursor.getInt(cursor.getColumnIndex(RCS_THREAD_ID_COLUMN)),
-                cursor.getInt(cursor.getColumnIndex(SOURCE_PARTICIPANT_ID_COLUMN)),
-                cursor.getInt(cursor.getColumnIndex(DESTINATION_PARTICIPANT_ID_COLUMN)));
-    }
-
-    private RcsParticipantAliasChangedEventDescriptor
-            createNewParticipantAliasChangedEvent(Cursor cursor) {
-        return new RcsParticipantAliasChangedEventDescriptor(
-                cursor.getLong(cursor.getColumnIndex(TIMESTAMP_COLUMN)),
-                cursor.getInt(cursor.getColumnIndex(SOURCE_PARTICIPANT_ID_COLUMN)),
-                cursor.getString(cursor.getColumnIndex(NEW_ALIAS_COLUMN)));
-    }
-}
diff --git a/src/java/com/android/internal/telephony/ims/RcsMessageQueryHelper.java b/src/java/com/android/internal/telephony/ims/RcsMessageQueryHelper.java
deleted file mode 100644
index f8a0fe8..0000000
--- a/src/java/com/android/internal/telephony/ims/RcsMessageQueryHelper.java
+++ /dev/null
@@ -1,213 +0,0 @@
-/*
- * Copyright (C) 2017 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.ims;
-
-import static android.provider.Telephony.RcsColumns.CONTENT_AND_AUTHORITY;
-import static android.provider.Telephony.RcsColumns.Rcs1To1ThreadColumns.RCS_1_TO_1_THREAD_URI_PART;
-import static android.provider.Telephony.RcsColumns.RcsFileTransferColumns.CONTENT_TYPE_COLUMN;
-import static android.provider.Telephony.RcsColumns.RcsFileTransferColumns.CONTENT_URI_COLUMN;
-import static android.provider.Telephony.RcsColumns.RcsFileTransferColumns.DURATION_MILLIS_COLUMN;
-import static android.provider.Telephony.RcsColumns.RcsFileTransferColumns.FILE_SIZE_COLUMN;
-import static android.provider.Telephony.RcsColumns.RcsFileTransferColumns.FILE_TRANSFER_URI;
-import static android.provider.Telephony.RcsColumns.RcsFileTransferColumns.FILE_TRANSFER_URI_PART;
-import static android.provider.Telephony.RcsColumns.RcsFileTransferColumns.HEIGHT_COLUMN;
-import static android.provider.Telephony.RcsColumns.RcsFileTransferColumns.PREVIEW_TYPE_COLUMN;
-import static android.provider.Telephony.RcsColumns.RcsFileTransferColumns.PREVIEW_URI_COLUMN;
-import static android.provider.Telephony.RcsColumns.RcsFileTransferColumns.SESSION_ID_COLUMN;
-import static android.provider.Telephony.RcsColumns.RcsFileTransferColumns.SUCCESSFULLY_TRANSFERRED_BYTES;
-import static android.provider.Telephony.RcsColumns.RcsFileTransferColumns.TRANSFER_STATUS_COLUMN;
-import static android.provider.Telephony.RcsColumns.RcsFileTransferColumns.WIDTH_COLUMN;
-import static android.provider.Telephony.RcsColumns.RcsGroupThreadColumns.RCS_GROUP_THREAD_URI_PART;
-import static android.provider.Telephony.RcsColumns.RcsIncomingMessageColumns.INCOMING_MESSAGE_URI;
-import static android.provider.Telephony.RcsColumns.RcsIncomingMessageColumns.INCOMING_MESSAGE_URI_PART;
-import static android.provider.Telephony.RcsColumns.RcsMessageColumns.GLOBAL_ID_COLUMN;
-import static android.provider.Telephony.RcsColumns.RcsMessageColumns.MESSAGE_ID_COLUMN;
-import static android.provider.Telephony.RcsColumns.RcsMessageColumns.ORIGINATION_TIMESTAMP_COLUMN;
-import static android.provider.Telephony.RcsColumns.RcsMessageColumns.STATUS_COLUMN;
-import static android.provider.Telephony.RcsColumns.RcsMessageColumns.SUB_ID_COLUMN;
-import static android.provider.Telephony.RcsColumns.RcsMessageDeliveryColumns.DELIVERY_URI_PART;
-import static android.provider.Telephony.RcsColumns.RcsOutgoingMessageColumns.OUTGOING_MESSAGE_URI;
-import static android.provider.Telephony.RcsColumns.RcsOutgoingMessageColumns.OUTGOING_MESSAGE_URI_PART;
-import static android.provider.Telephony.RcsColumns.RcsParticipantColumns.RCS_PARTICIPANT_ID_COLUMN;
-import static android.provider.Telephony.RcsColumns.RcsThreadColumns.RCS_THREAD_ID_COLUMN;
-import static android.provider.Telephony.RcsColumns.RcsUnifiedMessageColumns.MESSAGE_TYPE_COLUMN;
-import static android.provider.Telephony.RcsColumns.RcsUnifiedMessageColumns.MESSAGE_TYPE_INCOMING;
-import static android.provider.Telephony.RcsColumns.RcsUnifiedMessageColumns.MESSAGE_TYPE_OUTGOING;
-import static android.provider.Telephony.RcsColumns.RcsUnifiedMessageColumns.UNIFIED_MESSAGE_URI;
-import static android.telephony.ims.RcsQueryContinuationToken.QUERY_CONTINUATION_TOKEN;
-
-import android.content.ContentResolver;
-import android.content.ContentValues;
-import android.database.Cursor;
-import android.net.Uri;
-import android.os.Bundle;
-import android.os.RemoteException;
-import android.telephony.ims.RcsFileTransferCreationParams;
-import android.telephony.ims.RcsMessageCreationParams;
-import android.telephony.ims.RcsMessageQueryResultParcelable;
-import android.telephony.ims.RcsQueryContinuationToken;
-
-import com.android.ims.RcsTypeIdPair;
-
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * A helper class focused on querying RCS messages from the
- * {@link com.android.providers.telephony.RcsProvider}
- */
-class RcsMessageQueryHelper {
-
-    private final ContentResolver mContentResolver;
-
-    RcsMessageQueryHelper(ContentResolver contentResolver) {
-        mContentResolver = contentResolver;
-    }
-
-    RcsMessageQueryResultParcelable performMessageQuery(Bundle bundle) throws RemoteException {
-        RcsQueryContinuationToken continuationToken = null;
-        List<RcsTypeIdPair> messageTypeIdPairs = new ArrayList<>();
-
-        try (Cursor cursor = mContentResolver.query(UNIFIED_MESSAGE_URI, null, bundle, null)) {
-            if (cursor == null) {
-                throw new RemoteException("Could not perform message query, bundle: " + bundle);
-            }
-
-            while (cursor != null && cursor.moveToNext()) {
-                boolean isIncoming = cursor.getInt(cursor.getColumnIndex(MESSAGE_TYPE_COLUMN))
-                        == MESSAGE_TYPE_INCOMING;
-                int messageId = cursor.getInt(cursor.getColumnIndex(MESSAGE_ID_COLUMN));
-
-                messageTypeIdPairs.add(new RcsTypeIdPair(
-                        isIncoming ? MESSAGE_TYPE_INCOMING : MESSAGE_TYPE_OUTGOING, messageId));
-            }
-
-            if (cursor != null) {
-                Bundle cursorExtras = cursor.getExtras();
-                if (cursorExtras != null) {
-                    continuationToken =
-                            cursorExtras.getParcelable(QUERY_CONTINUATION_TOKEN);
-                }
-            }
-        }
-
-        return new RcsMessageQueryResultParcelable(continuationToken, messageTypeIdPairs);
-    }
-
-    void createContentValuesForGenericMessage(ContentValues contentValues, int threadId,
-            RcsMessageCreationParams rcsMessageCreationParams) {
-        contentValues.put(GLOBAL_ID_COLUMN, rcsMessageCreationParams.getRcsMessageGlobalId());
-        contentValues.put(SUB_ID_COLUMN, rcsMessageCreationParams.getSubId());
-        contentValues.put(STATUS_COLUMN, rcsMessageCreationParams.getMessageStatus());
-        contentValues.put(ORIGINATION_TIMESTAMP_COLUMN,
-                rcsMessageCreationParams.getOriginationTimestamp());
-        contentValues.put(RCS_THREAD_ID_COLUMN, threadId);
-    }
-
-    Uri getMessageInsertionUri(boolean isIncoming) {
-        return isIncoming ? INCOMING_MESSAGE_URI : OUTGOING_MESSAGE_URI;
-    }
-
-    Uri getMessageDeletionUri(int messageId, boolean isIncoming, int rcsThreadId, boolean isGroup) {
-        return CONTENT_AND_AUTHORITY.buildUpon().appendPath(
-                isGroup ? RCS_GROUP_THREAD_URI_PART : RCS_1_TO_1_THREAD_URI_PART).appendPath(
-                Integer.toString(rcsThreadId)).appendPath(
-                isIncoming ? INCOMING_MESSAGE_URI_PART : OUTGOING_MESSAGE_URI_PART).appendPath(
-                Integer.toString(messageId)).build();
-    }
-
-    Uri getMessageUpdateUri(int messageId, boolean isIncoming) {
-        return getMessageInsertionUri(isIncoming).buildUpon().appendPath(
-                Integer.toString(messageId)).build();
-    }
-
-    int[] getDeliveryParticipantsForMessage(int messageId) throws RemoteException {
-        int[] participantIds;
-
-        try (Cursor cursor = mContentResolver.query(getMessageDeliveryQueryUri(messageId), null,
-                null, null)) {
-            if (cursor == null) {
-                throw new RemoteException(
-                        "Could not query deliveries for message, messageId: " + messageId);
-            }
-
-            participantIds = new int[cursor.getCount()];
-
-            for (int i = 0; cursor.moveToNext(); i++) {
-                participantIds[i] = cursor.getInt(cursor.getColumnIndex(RCS_PARTICIPANT_ID_COLUMN));
-            }
-        }
-
-        return participantIds;
-    }
-
-    Uri getMessageDeliveryUri(int messageId, int participantId) {
-        return Uri.withAppendedPath(getMessageDeliveryQueryUri(messageId),
-                Integer.toString(participantId));
-    }
-
-    long getLongValueFromDelivery(int messageId, int participantId,
-            String columnName) throws RemoteException {
-        try (Cursor cursor = mContentResolver.query(getMessageDeliveryUri(messageId, participantId),
-                null, null, null)) {
-            if (cursor == null || !cursor.moveToFirst()) {
-                throw new RemoteException(
-                        "Could not read delivery for message: " + messageId + ", participant: "
-                                + participantId);
-            }
-
-            return cursor.getLong(cursor.getColumnIndex(columnName));
-        }
-    }
-
-    private Uri getMessageDeliveryQueryUri(int messageId) {
-        return getMessageInsertionUri(false).buildUpon().appendPath(
-                Integer.toString(messageId)).appendPath(DELIVERY_URI_PART).build();
-    }
-
-    ContentValues getContentValuesForFileTransfer(
-            RcsFileTransferCreationParams fileTransferCreationParameters) {
-        ContentValues contentValues = new ContentValues();
-        contentValues.put(SESSION_ID_COLUMN,
-                fileTransferCreationParameters.getRcsFileTransferSessionId());
-        contentValues.put(CONTENT_URI_COLUMN,
-                fileTransferCreationParameters.getContentUri().toString());
-        contentValues.put(CONTENT_TYPE_COLUMN, fileTransferCreationParameters.getContentMimeType());
-        contentValues.put(FILE_SIZE_COLUMN, fileTransferCreationParameters.getFileSize());
-        contentValues.put(SUCCESSFULLY_TRANSFERRED_BYTES,
-                fileTransferCreationParameters.getTransferOffset());
-        contentValues.put(TRANSFER_STATUS_COLUMN,
-                fileTransferCreationParameters.getFileTransferStatus());
-        contentValues.put(WIDTH_COLUMN, fileTransferCreationParameters.getWidth());
-        contentValues.put(HEIGHT_COLUMN, fileTransferCreationParameters.getHeight());
-        contentValues.put(DURATION_MILLIS_COLUMN,
-                fileTransferCreationParameters.getMediaDuration());
-        contentValues.put(PREVIEW_URI_COLUMN,
-                fileTransferCreationParameters.getPreviewUri().toString());
-        contentValues.put(PREVIEW_TYPE_COLUMN, fileTransferCreationParameters.getPreviewMimeType());
-
-        return contentValues;
-    }
-
-    Uri getFileTransferInsertionUri(int messageId) {
-        return UNIFIED_MESSAGE_URI.buildUpon().appendPath(Integer.toString(messageId)).appendPath(
-                FILE_TRANSFER_URI_PART).build();
-    }
-
-    Uri getFileTransferUpdateUri(int partId) {
-        return Uri.withAppendedPath(FILE_TRANSFER_URI, Integer.toString(partId));
-    }
-}
diff --git a/src/java/com/android/internal/telephony/ims/RcsMessageStoreController.java b/src/java/com/android/internal/telephony/ims/RcsMessageStoreController.java
deleted file mode 100644
index e7d3f48..0000000
--- a/src/java/com/android/internal/telephony/ims/RcsMessageStoreController.java
+++ /dev/null
@@ -1,1051 +0,0 @@
-/*
- * Copyright (C) 2019 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.ims;
-
-import static android.provider.Telephony.RcsColumns.Rcs1To1ThreadColumns.FALLBACK_THREAD_ID_COLUMN;
-import static android.provider.Telephony.RcsColumns.Rcs1To1ThreadColumns.RCS_1_TO_1_THREAD_URI;
-import static android.provider.Telephony.RcsColumns.RcsEventTypes.ICON_CHANGED_EVENT_TYPE;
-import static android.provider.Telephony.RcsColumns.RcsEventTypes.NAME_CHANGED_EVENT_TYPE;
-import static android.provider.Telephony.RcsColumns.RcsEventTypes.PARTICIPANT_JOINED_EVENT_TYPE;
-import static android.provider.Telephony.RcsColumns.RcsEventTypes.PARTICIPANT_LEFT_EVENT_TYPE;
-import static android.provider.Telephony.RcsColumns.RcsFileTransferColumns.CONTENT_TYPE_COLUMN;
-import static android.provider.Telephony.RcsColumns.RcsFileTransferColumns.CONTENT_URI_COLUMN;
-import static android.provider.Telephony.RcsColumns.RcsFileTransferColumns.DURATION_MILLIS_COLUMN;
-import static android.provider.Telephony.RcsColumns.RcsFileTransferColumns.FILE_SIZE_COLUMN;
-import static android.provider.Telephony.RcsColumns.RcsFileTransferColumns.FILE_TRANSFER_ID_COLUMN;
-import static android.provider.Telephony.RcsColumns.RcsFileTransferColumns.FILE_TRANSFER_URI;
-import static android.provider.Telephony.RcsColumns.RcsFileTransferColumns.HEIGHT_COLUMN;
-import static android.provider.Telephony.RcsColumns.RcsFileTransferColumns.PREVIEW_TYPE_COLUMN;
-import static android.provider.Telephony.RcsColumns.RcsFileTransferColumns.PREVIEW_URI_COLUMN;
-import static android.provider.Telephony.RcsColumns.RcsFileTransferColumns.SESSION_ID_COLUMN;
-import static android.provider.Telephony.RcsColumns.RcsFileTransferColumns.SUCCESSFULLY_TRANSFERRED_BYTES;
-import static android.provider.Telephony.RcsColumns.RcsFileTransferColumns.TRANSFER_STATUS_COLUMN;
-import static android.provider.Telephony.RcsColumns.RcsFileTransferColumns.WIDTH_COLUMN;
-import static android.provider.Telephony.RcsColumns.RcsGroupThreadColumns.CONFERENCE_URI_COLUMN;
-import static android.provider.Telephony.RcsColumns.RcsGroupThreadColumns.GROUP_ICON_COLUMN;
-import static android.provider.Telephony.RcsColumns.RcsGroupThreadColumns.GROUP_NAME_COLUMN;
-import static android.provider.Telephony.RcsColumns.RcsGroupThreadColumns.OWNER_PARTICIPANT_COLUMN;
-import static android.provider.Telephony.RcsColumns.RcsGroupThreadColumns.RCS_GROUP_THREAD_URI;
-import static android.provider.Telephony.RcsColumns.RcsIncomingMessageColumns.ARRIVAL_TIMESTAMP_COLUMN;
-import static android.provider.Telephony.RcsColumns.RcsIncomingMessageColumns.INCOMING_MESSAGE_URI;
-import static android.provider.Telephony.RcsColumns.RcsIncomingMessageColumns.SEEN_TIMESTAMP_COLUMN;
-import static android.provider.Telephony.RcsColumns.RcsIncomingMessageColumns.SENDER_PARTICIPANT_ID_COLUMN;
-import static android.provider.Telephony.RcsColumns.RcsMessageColumns.GLOBAL_ID_COLUMN;
-import static android.provider.Telephony.RcsColumns.RcsMessageColumns.LATITUDE_COLUMN;
-import static android.provider.Telephony.RcsColumns.RcsMessageColumns.LONGITUDE_COLUMN;
-import static android.provider.Telephony.RcsColumns.RcsMessageColumns.MESSAGE_ID_COLUMN;
-import static android.provider.Telephony.RcsColumns.RcsMessageColumns.MESSAGE_TEXT_COLUMN;
-import static android.provider.Telephony.RcsColumns.RcsMessageColumns.ORIGINATION_TIMESTAMP_COLUMN;
-import static android.provider.Telephony.RcsColumns.RcsMessageColumns.STATUS_COLUMN;
-import static android.provider.Telephony.RcsColumns.RcsMessageColumns.SUB_ID_COLUMN;
-import static android.provider.Telephony.RcsColumns.RcsMessageDeliveryColumns.DELIVERED_TIMESTAMP_COLUMN;
-import static android.provider.Telephony.RcsColumns.RcsParticipantColumns.CANONICAL_ADDRESS_ID_COLUMN;
-import static android.provider.Telephony.RcsColumns.RcsParticipantColumns.RCS_ALIAS_COLUMN;
-import static android.provider.Telephony.RcsColumns.RcsParticipantColumns.RCS_PARTICIPANT_ID_COLUMN;
-import static android.provider.Telephony.RcsColumns.RcsParticipantColumns.RCS_PARTICIPANT_URI;
-import static android.provider.Telephony.RcsColumns.RcsParticipantEventColumns.NEW_ALIAS_COLUMN;
-import static android.provider.Telephony.RcsColumns.RcsThreadColumns.RCS_THREAD_ID_COLUMN;
-import static android.provider.Telephony.RcsColumns.RcsThreadEventColumns.DESTINATION_PARTICIPANT_ID_COLUMN;
-import static android.provider.Telephony.RcsColumns.RcsThreadEventColumns.NEW_ICON_URI_COLUMN;
-import static android.provider.Telephony.RcsColumns.RcsThreadEventColumns.NEW_NAME_COLUMN;
-import static android.provider.Telephony.RcsColumns.RcsThreadEventColumns.SOURCE_PARTICIPANT_ID_COLUMN;
-import static android.provider.Telephony.RcsColumns.RcsThreadEventColumns.TIMESTAMP_COLUMN;
-import static android.provider.Telephony.RcsColumns.RcsUnifiedThreadColumns.THREAD_TYPE_GROUP;
-import static android.provider.Telephony.RcsColumns.TRANSACTION_FAILED;
-import static android.telephony.ims.RcsEventQueryParams.EVENT_QUERY_PARAMETERS_KEY;
-import static android.telephony.ims.RcsMessageQueryParams.MESSAGE_QUERY_PARAMETERS_KEY;
-import static android.telephony.ims.RcsParticipantQueryParams.PARTICIPANT_QUERY_PARAMETERS_KEY;
-import static android.telephony.ims.RcsQueryContinuationToken.QUERY_CONTINUATION_TOKEN;
-import static android.telephony.ims.RcsThreadQueryParams.THREAD_QUERY_PARAMETERS_KEY;
-
-import static com.android.internal.telephony.ims.RcsMessageStoreUtil.getMessageTableUri;
-import static com.android.internal.telephony.ims.RcsParticipantQueryHelper.getUriForParticipant;
-import static com.android.internal.telephony.ims.RcsThreadQueryHelper.get1To1ThreadUri;
-import static com.android.internal.telephony.ims.RcsThreadQueryHelper.getAllParticipantsInThreadUri;
-import static com.android.internal.telephony.ims.RcsThreadQueryHelper.getGroupThreadUri;
-import static com.android.internal.telephony.ims.RcsThreadQueryHelper.getParticipantInThreadUri;
-
-import android.content.ContentResolver;
-import android.content.ContentValues;
-import android.content.Context;
-import android.database.Cursor;
-import android.net.Uri;
-import android.os.Bundle;
-import android.os.RemoteException;
-import android.os.ServiceManager;
-import android.provider.Telephony;
-import android.telephony.Rlog;
-import android.telephony.ims.RcsEventQueryParams;
-import android.telephony.ims.RcsEventQueryResultDescriptor;
-import android.telephony.ims.RcsFileTransferCreationParams;
-import android.telephony.ims.RcsIncomingMessageCreationParams;
-import android.telephony.ims.RcsMessageQueryParams;
-import android.telephony.ims.RcsMessageQueryResultParcelable;
-import android.telephony.ims.RcsMessageSnippet;
-import android.telephony.ims.RcsMessageStore;
-import android.telephony.ims.RcsOutgoingMessageCreationParams;
-import android.telephony.ims.RcsParticipantQueryParams;
-import android.telephony.ims.RcsParticipantQueryResultParcelable;
-import android.telephony.ims.RcsQueryContinuationToken;
-import android.telephony.ims.RcsThreadQueryParams;
-import android.telephony.ims.RcsThreadQueryResultParcelable;
-import android.telephony.ims.aidl.IRcs;
-
-import com.android.internal.annotations.VisibleForTesting;
-
-/**
- * Backing implementation of {@link RcsMessageStore}.
- */
-public class RcsMessageStoreController extends IRcs.Stub {
-
-    static final String TAG = "RcsMsgStoreController";
-    private static final String RCS_SERVICE_NAME = "ircs";
-
-    private static RcsMessageStoreController sInstance;
-
-    private final Context mContext;
-    private final ContentResolver mContentResolver;
-    private final RcsParticipantQueryHelper mParticipantQueryHelper;
-    private final RcsMessageQueryHelper mMessageQueryHelper;
-    private final RcsEventQueryHelper mEventQueryHelper;
-    private final RcsThreadQueryHelper mThreadQueryHelper;
-    private final RcsMessageStoreUtil mMessageStoreUtil;
-
-    /**
-     * Initialize the instance. Should only be called once.
-     */
-    public static RcsMessageStoreController init(Context context) {
-        synchronized (RcsMessageStoreController.class) {
-            if (sInstance == null) {
-                sInstance = new RcsMessageStoreController(context);
-                if (ServiceManager.getService(RCS_SERVICE_NAME) == null) {
-                    ServiceManager.addService(RCS_SERVICE_NAME, sInstance);
-                }
-            } else {
-                Rlog.e(TAG, "init() called multiple times! sInstance = " + sInstance);
-            }
-        }
-        return sInstance;
-    }
-
-    interface ThrowingSupplier<T> {
-        T get() throws RemoteException;
-    }
-
-    interface ThrowingRunnable {
-        void run() throws RemoteException;
-    }
-
-    private void performWriteOperation(String callingPackage, ThrowingRunnable fn) {
-        RcsPermissions.checkWritePermissions(mContext, callingPackage);
-
-        try {
-            fn.run();
-        } catch (RemoteException e) {
-            throw new RuntimeException(e);
-        }
-    }
-
-    private <T> T performCreateOperation(String callingPackage, ThrowingSupplier<T> fn) {
-        RcsPermissions.checkWritePermissions(mContext, callingPackage);
-
-        try {
-            return fn.get();
-        } catch (RemoteException e) {
-            throw new RuntimeException(e);
-        }
-    }
-
-    private <T> T performReadOperation(String callingPackage, ThrowingSupplier<T> fn) {
-        RcsPermissions.checkReadPermissions(mContext, callingPackage);
-
-        try {
-            return fn.get();
-        } catch (RemoteException e) {
-            throw new RuntimeException(e);
-        }
-    }
-
-    @VisibleForTesting
-    public RcsMessageStoreController(Context context) {
-        mContext = context;
-        mContentResolver = context.getContentResolver();
-        mParticipantQueryHelper = new RcsParticipantQueryHelper(mContentResolver);
-        mMessageQueryHelper = new RcsMessageQueryHelper(mContentResolver);
-        mThreadQueryHelper = new RcsThreadQueryHelper(mContentResolver, mParticipantQueryHelper);
-        mEventQueryHelper = new RcsEventQueryHelper(mContentResolver);
-        mMessageStoreUtil = new RcsMessageStoreUtil(mContentResolver);
-    }
-
-    @Override
-    public boolean deleteThread(int threadId, int threadType, String callingPackage) {
-        return performCreateOperation(callingPackage, () -> {
-            int deletionCount = mContentResolver.delete(
-                    threadType == THREAD_TYPE_GROUP ? RCS_GROUP_THREAD_URI : RCS_1_TO_1_THREAD_URI,
-                    RCS_THREAD_ID_COLUMN + "=?",
-                    new String[]{Integer.toString(threadId)});
-
-            return deletionCount > 0;
-        });
-    }
-
-    @Override
-    public RcsMessageSnippet getMessageSnippet(int threadId, String callingPackage) {
-        return performReadOperation(callingPackage, () -> {
-            // TODO - implement
-            return null;
-        });
-    }
-
-    @Override
-    public RcsThreadQueryResultParcelable getRcsThreads(RcsThreadQueryParams queryParameters,
-            String callingPackage) {
-        return performReadOperation(callingPackage, () -> {
-            Bundle bundle = new Bundle();
-            bundle.putParcelable(THREAD_QUERY_PARAMETERS_KEY, queryParameters);
-            return mThreadQueryHelper.performThreadQuery(bundle);
-        });
-    }
-
-    @Override
-    public RcsThreadQueryResultParcelable getRcsThreadsWithToken(
-            RcsQueryContinuationToken continuationToken, String callingPackage) {
-        return performReadOperation(callingPackage, () -> {
-            Bundle bundle = new Bundle();
-            bundle.putParcelable(QUERY_CONTINUATION_TOKEN, continuationToken);
-            return mThreadQueryHelper.performThreadQuery(bundle);
-        });
-    }
-
-    @Override
-    public RcsParticipantQueryResultParcelable getParticipants(
-            RcsParticipantQueryParams queryParameters, String callingPackage) {
-        return performReadOperation(callingPackage, () -> {
-            Bundle bundle = new Bundle();
-            bundle.putParcelable(PARTICIPANT_QUERY_PARAMETERS_KEY, queryParameters);
-            return mParticipantQueryHelper.performParticipantQuery(bundle);
-        });
-    }
-
-    @Override
-    public RcsParticipantQueryResultParcelable getParticipantsWithToken(
-            RcsQueryContinuationToken continuationToken, String callingPackage) {
-        return performReadOperation(callingPackage, () -> {
-            Bundle bundle = new Bundle();
-            bundle.putParcelable(QUERY_CONTINUATION_TOKEN, continuationToken);
-            return mParticipantQueryHelper.performParticipantQuery(bundle);
-        });
-    }
-
-    @Override
-    public RcsMessageQueryResultParcelable getMessages(RcsMessageQueryParams queryParams,
-            String callingPackage) {
-        return performReadOperation(callingPackage, () -> {
-            Bundle bundle = new Bundle();
-            bundle.putParcelable(MESSAGE_QUERY_PARAMETERS_KEY, queryParams);
-            return mMessageQueryHelper.performMessageQuery(bundle);
-        });
-    }
-
-    @Override
-    public RcsMessageQueryResultParcelable getMessagesWithToken(
-            RcsQueryContinuationToken continuationToken, String callingPackage) {
-        return performReadOperation(callingPackage, () -> {
-            Bundle bundle = new Bundle();
-            bundle.putParcelable(QUERY_CONTINUATION_TOKEN, continuationToken);
-            return mMessageQueryHelper.performMessageQuery(bundle);
-        });
-    }
-
-    @Override
-    public RcsEventQueryResultDescriptor getEvents(RcsEventQueryParams queryParameters,
-            String callingPackage) {
-        return performReadOperation(callingPackage, () -> {
-            Bundle bundle = new Bundle();
-            bundle.putParcelable(EVENT_QUERY_PARAMETERS_KEY, queryParameters);
-            return mEventQueryHelper.performEventQuery(bundle);
-        });
-    }
-
-    @Override
-    public RcsEventQueryResultDescriptor getEventsWithToken(
-            RcsQueryContinuationToken continuationToken, String callingPackage) {
-        return performReadOperation(callingPackage, () -> {
-            Bundle bundle = new Bundle();
-            bundle.putParcelable(QUERY_CONTINUATION_TOKEN, continuationToken);
-            return mEventQueryHelper.performEventQuery(bundle);
-        });
-    }
-
-    @Override
-    public int createRcs1To1Thread(int recipientId, String callingPackage) {
-        return performCreateOperation(callingPackage,
-                () -> mThreadQueryHelper.create1To1Thread(recipientId));
-    }
-
-    @Override
-    public int createGroupThread(int[] participantIds, String groupName, Uri groupIcon,
-            String callingPackage) {
-        return performCreateOperation(callingPackage, () -> {
-            int groupThreadId = mThreadQueryHelper.createGroupThread(groupName, groupIcon);
-            if (groupThreadId <= 0) {
-                throw new RemoteException("Could not create RcsGroupThread.");
-            }
-
-            // Insert participants
-            // TODO(123718879): Instead of adding participants here, add them under RcsProvider
-            //  under one transaction
-            if (participantIds != null) {
-                for (int participantId : participantIds) {
-                    addParticipantToGroupThread(groupThreadId, participantId, callingPackage);
-                }
-            }
-
-            return groupThreadId;
-        });
-    }
-
-    /**
-     * TODO(109759350) Instead of sending the update query directly to RcsProvider, this function
-     * orchestrates between RcsProvider and MmsSmsProvider. This is because we are not fully decided
-     * on whether we should have RCS storage in a separate database file.
-     */
-    @Override
-    public int createRcsParticipant(String canonicalAddress, String alias, String callingPackage) {
-        return performCreateOperation(callingPackage, () -> {
-            ContentValues contentValues = new ContentValues();
-
-            long canonicalAddressId = Telephony.RcsColumns.RcsCanonicalAddressHelper
-                    .getOrCreateCanonicalAddressId(mContentResolver, canonicalAddress);
-
-            if (canonicalAddressId == TRANSACTION_FAILED) {
-                throw new RemoteException("Could not create or make canonical address entry");
-            }
-
-            contentValues.put(CANONICAL_ADDRESS_ID_COLUMN, canonicalAddressId);
-            contentValues.put(RCS_ALIAS_COLUMN, alias);
-
-            // TODO (123719857) - Disallow creation of duplicate participants
-            Uri newParticipantUri = mContentResolver.insert(RCS_PARTICIPANT_URI, contentValues);
-            int newParticipantRowId;
-
-            if (newParticipantUri == null) {
-                throw new RemoteException("Error inserting new participant into RcsProvider");
-            }
-
-            try {
-                newParticipantRowId = Integer.parseInt(newParticipantUri.getLastPathSegment());
-            } catch (NumberFormatException e) {
-                throw new RemoteException(
-                        "Uri returned after creating a participant is malformed: "
-                                + newParticipantUri);
-            }
-
-            return newParticipantRowId;
-        });
-    }
-
-    @Override
-    public String getRcsParticipantCanonicalAddress(int participantId, String callingPackage) {
-        return performReadOperation(callingPackage,
-                () -> mMessageStoreUtil.getStringValueFromTableRow(RCS_PARTICIPANT_URI,
-                        Telephony.CanonicalAddressesColumns.ADDRESS, RCS_PARTICIPANT_ID_COLUMN,
-                        participantId));
-    }
-
-    @Override
-    public String getRcsParticipantAlias(int participantId, String callingPackage) {
-        return performReadOperation(callingPackage,
-                () -> mMessageStoreUtil.getStringValueFromTableRow(RCS_PARTICIPANT_URI,
-                        RCS_ALIAS_COLUMN,
-                        RCS_PARTICIPANT_ID_COLUMN, participantId));
-    }
-
-    @Override
-    public void setRcsParticipantAlias(int id, String alias, String callingPackage) {
-        performWriteOperation(callingPackage,
-                () -> mMessageStoreUtil.updateValueOfProviderUri(getUriForParticipant(id),
-                        RCS_ALIAS_COLUMN,
-                        alias, "Could not update RCS participant alias"));
-    }
-
-    @Override
-    public String getRcsParticipantContactId(int participantId, String callingPackage) {
-        return performReadOperation(callingPackage, () -> {
-            // TODO - implement
-            return null;
-        });
-    }
-
-    @Override
-    public void setRcsParticipantContactId(int participantId, String contactId,
-            String callingPackage) {
-        performWriteOperation(callingPackage, () -> {
-            // TODO - implement
-        });
-    }
-
-    @Override
-    public void set1To1ThreadFallbackThreadId(int rcsThreadId, long fallbackId,
-            String callingPackage) {
-        performWriteOperation(callingPackage,
-                () -> mMessageStoreUtil.updateValueOfProviderUri(get1To1ThreadUri(rcsThreadId),
-                        FALLBACK_THREAD_ID_COLUMN, fallbackId, "Could not set fallback thread ID"));
-    }
-
-    @Override
-    public long get1To1ThreadFallbackThreadId(int rcsThreadId, String callingPackage) {
-        return performReadOperation(callingPackage,
-                () -> mMessageStoreUtil.getLongValueFromTableRow(RCS_1_TO_1_THREAD_URI,
-                        FALLBACK_THREAD_ID_COLUMN,
-                        RCS_THREAD_ID_COLUMN, rcsThreadId));
-    }
-
-    @Override
-    public int get1To1ThreadOtherParticipantId(int rcsThreadId, String callingPackage) {
-        return performReadOperation(callingPackage, () -> {
-            Uri uri = get1To1ThreadUri(rcsThreadId);
-            String[] projection = new String[]{RCS_PARTICIPANT_ID_COLUMN};
-            try (Cursor cursor = mContentResolver.query(uri, projection, null, null)) {
-                if (cursor == null || cursor.getCount() != 1) {
-                    throw new RemoteException("Could not get the thread recipient");
-                }
-                cursor.moveToNext();
-                return cursor.getInt(
-                        cursor.getColumnIndex(RCS_PARTICIPANT_ID_COLUMN));
-            }
-        });
-    }
-
-    @Override
-    public void setGroupThreadName(int rcsThreadId, String groupName, String callingPackage) {
-        performWriteOperation(callingPackage,
-                () -> mMessageStoreUtil.updateValueOfProviderUri(getGroupThreadUri(rcsThreadId),
-                        GROUP_NAME_COLUMN, groupName, "Could not update group name"));
-    }
-
-    @Override
-    public String getGroupThreadName(int rcsThreadId, String callingPackage) {
-        return performReadOperation(callingPackage,
-                () -> mMessageStoreUtil.getStringValueFromTableRow(RCS_GROUP_THREAD_URI,
-                        GROUP_NAME_COLUMN,
-                        RCS_THREAD_ID_COLUMN, rcsThreadId));
-    }
-
-    @Override
-    public void setGroupThreadIcon(int rcsThreadId, Uri groupIcon, String callingPackage) {
-        performWriteOperation(callingPackage,
-                () -> mMessageStoreUtil.updateValueOfProviderUri(getGroupThreadUri(rcsThreadId),
-                        GROUP_ICON_COLUMN, groupIcon, "Could not update group icon"));
-    }
-
-    @Override
-    public Uri getGroupThreadIcon(int rcsThreadId, String callingPackage) {
-        return performReadOperation(callingPackage,
-                () -> mMessageStoreUtil.getUriValueFromTableRow(RCS_GROUP_THREAD_URI,
-                        GROUP_ICON_COLUMN,
-                        RCS_THREAD_ID_COLUMN, rcsThreadId));
-    }
-
-    @Override
-    public void setGroupThreadOwner(int rcsThreadId, int participantId, String callingPackage) {
-        performWriteOperation(callingPackage,
-                () -> mMessageStoreUtil.updateValueOfProviderUri(getGroupThreadUri(rcsThreadId),
-                        OWNER_PARTICIPANT_COLUMN, participantId, "Could not set the group owner"));
-    }
-
-    @Override
-    public int getGroupThreadOwner(int rcsThreadId, String callingPackage) {
-        return performReadOperation(callingPackage,
-                () -> mMessageStoreUtil.getIntValueFromTableRow(RCS_GROUP_THREAD_URI,
-                        OWNER_PARTICIPANT_COLUMN,
-                        RCS_THREAD_ID_COLUMN, rcsThreadId));
-    }
-
-    @Override
-    public void setGroupThreadConferenceUri(int rcsThreadId, Uri uri, String callingPackage) {
-        performWriteOperation(callingPackage,
-                () -> mMessageStoreUtil.updateValueOfProviderUri(getGroupThreadUri(rcsThreadId),
-                        CONFERENCE_URI_COLUMN, uri, "Could not set the conference URI for group"));
-    }
-
-    @Override
-    public Uri getGroupThreadConferenceUri(int rcsThreadId, String callingPackage) {
-        return performReadOperation(callingPackage,
-                () -> mMessageStoreUtil.getUriValueFromTableRow(RCS_GROUP_THREAD_URI,
-                        CONFERENCE_URI_COLUMN, RCS_THREAD_ID_COLUMN, rcsThreadId));
-    }
-
-    @Override
-    public void addParticipantToGroupThread(int rcsThreadId, int participantId,
-            String callingPackage) {
-        performWriteOperation(callingPackage, () -> {
-            ContentValues contentValues = new ContentValues(2);
-            contentValues.put(RCS_THREAD_ID_COLUMN, rcsThreadId);
-            contentValues.put(RCS_PARTICIPANT_ID_COLUMN, participantId);
-
-            mContentResolver.insert(getAllParticipantsInThreadUri(rcsThreadId), contentValues);
-        });
-    }
-
-    @Override
-    public void removeParticipantFromGroupThread(int rcsThreadId, int participantId,
-            String callingPackage) {
-        performWriteOperation(callingPackage,
-                () -> mContentResolver.delete(getParticipantInThreadUri(rcsThreadId, participantId),
-                        null,
-                        null));
-    }
-
-    @Override
-    public int addIncomingMessage(int rcsThreadId,
-            RcsIncomingMessageCreationParams rcsIncomingMessageCreationParams,
-            String callingPackage) {
-        return performCreateOperation(callingPackage, () -> {
-            ContentValues contentValues = new ContentValues();
-
-            contentValues.put(ARRIVAL_TIMESTAMP_COLUMN,
-                    rcsIncomingMessageCreationParams.getArrivalTimestamp());
-            contentValues.put(SEEN_TIMESTAMP_COLUMN,
-                    rcsIncomingMessageCreationParams.getSeenTimestamp());
-            contentValues.put(SENDER_PARTICIPANT_ID_COLUMN,
-                    rcsIncomingMessageCreationParams.getSenderParticipantId());
-
-            mMessageQueryHelper.createContentValuesForGenericMessage(contentValues, rcsThreadId,
-                    rcsIncomingMessageCreationParams);
-
-            return addMessage(rcsThreadId, true, contentValues);
-        });
-    }
-
-    @Override
-    public int addOutgoingMessage(int rcsThreadId,
-            RcsOutgoingMessageCreationParams rcsOutgoingMessageCreationParameters,
-            String callingPackage) {
-        return performCreateOperation(callingPackage, () -> {
-            ContentValues contentValues = new ContentValues();
-
-            mMessageQueryHelper.createContentValuesForGenericMessage(contentValues, rcsThreadId,
-                    rcsOutgoingMessageCreationParameters);
-
-            return addMessage(rcsThreadId, false, contentValues);
-        });
-    }
-
-    private int addMessage(int rcsThreadId, boolean isIncoming, ContentValues contentValues)
-            throws RemoteException {
-        Uri uri = mContentResolver.insert(mMessageQueryHelper.getMessageInsertionUri(isIncoming),
-                contentValues);
-
-        if (uri == null) {
-            throw new RemoteException(
-                    "Could not create message on thread, threadId: " + rcsThreadId);
-        }
-
-        return Integer.parseInt(uri.getLastPathSegment());
-    }
-
-    @Override
-    public void deleteMessage(int messageId, boolean isIncoming, int rcsThreadId, boolean isGroup,
-            String callingPackage) {
-        performWriteOperation(callingPackage, () -> mContentResolver.delete(
-                mMessageQueryHelper.getMessageDeletionUri(messageId, isIncoming, rcsThreadId,
-                        isGroup),
-                null, null));
-    }
-
-    @Override
-    public void setMessageSubId(int messageId, boolean isIncoming, int subId,
-            String callingPackage) {
-        performWriteOperation(callingPackage, () -> mMessageStoreUtil.updateValueOfProviderUri(
-                mMessageQueryHelper.getMessageUpdateUri(messageId, isIncoming), SUB_ID_COLUMN,
-                subId, "Could not set subscription ID for message"));
-    }
-
-    @Override
-    public int getMessageSubId(int messageId, boolean isIncoming, String callingPackage) {
-        return performReadOperation(callingPackage,
-                () -> mMessageStoreUtil.getIntValueFromTableRow(getMessageTableUri(isIncoming),
-                        SUB_ID_COLUMN, MESSAGE_ID_COLUMN, messageId));
-    }
-
-    @Override
-    public void setMessageStatus(int messageId, boolean isIncoming, int status,
-            String callingPackage) {
-        performWriteOperation(callingPackage, () -> mMessageStoreUtil.updateValueOfProviderUri(
-                mMessageQueryHelper.getMessageUpdateUri(messageId, isIncoming), STATUS_COLUMN,
-                status, "Could not set the status for message"));
-    }
-
-    @Override
-    public int getMessageStatus(int messageId, boolean isIncoming, String callingPackage) {
-        return performReadOperation(callingPackage,
-                () -> mMessageStoreUtil.getIntValueFromTableRow(getMessageTableUri(isIncoming),
-                        STATUS_COLUMN, MESSAGE_ID_COLUMN, messageId));
-    }
-
-    @Override
-    public void setMessageOriginationTimestamp(int messageId, boolean isIncoming,
-            long originationTimestamp, String callingPackage) {
-        performWriteOperation(callingPackage, () -> mMessageStoreUtil.updateValueOfProviderUri(
-                mMessageQueryHelper.getMessageUpdateUri(messageId, isIncoming),
-                ORIGINATION_TIMESTAMP_COLUMN, originationTimestamp,
-                "Could not set the origination timestamp for message"));
-    }
-
-    @Override
-    public long getMessageOriginationTimestamp(int messageId, boolean isIncoming,
-            String callingPackage) {
-        return performReadOperation(callingPackage,
-                () -> mMessageStoreUtil.getLongValueFromTableRow(getMessageTableUri(isIncoming),
-                        ORIGINATION_TIMESTAMP_COLUMN, MESSAGE_ID_COLUMN, messageId));
-    }
-
-    @Override
-    public void setGlobalMessageIdForMessage(int messageId, boolean isIncoming, String globalId,
-            String callingPackage) {
-        performWriteOperation(callingPackage, () -> mMessageStoreUtil.updateValueOfProviderUri(
-                mMessageQueryHelper.getMessageUpdateUri(messageId, isIncoming),
-                GLOBAL_ID_COLUMN,
-                globalId, "Could not set the global ID for message"));
-    }
-
-    @Override
-    public String getGlobalMessageIdForMessage(int messageId, boolean isIncoming,
-            String callingPackage) {
-        return performReadOperation(callingPackage,
-                () -> mMessageStoreUtil.getStringValueFromTableRow(getMessageTableUri(isIncoming),
-                        GLOBAL_ID_COLUMN, MESSAGE_ID_COLUMN, messageId));
-    }
-
-    @Override
-    public void setMessageArrivalTimestamp(int messageId, boolean isIncoming,
-            long arrivalTimestamp, String callingPackage) {
-        performWriteOperation(callingPackage, () -> mMessageStoreUtil.updateValueOfProviderUri(
-                mMessageQueryHelper.getMessageUpdateUri(messageId, isIncoming),
-                ARRIVAL_TIMESTAMP_COLUMN, arrivalTimestamp,
-                "Could not update the arrival timestamp for message"));
-    }
-
-    @Override
-    public long getMessageArrivalTimestamp(int messageId, boolean isIncoming,
-            String callingPackage) {
-        return performReadOperation(callingPackage,
-                () -> mMessageStoreUtil.getLongValueFromTableRow(getMessageTableUri(isIncoming),
-                        ARRIVAL_TIMESTAMP_COLUMN, MESSAGE_ID_COLUMN, messageId));
-    }
-
-    @Override
-    public void setMessageSeenTimestamp(int messageId, boolean isIncoming,
-            long notifiedTimestamp, String callingPackage) {
-        performWriteOperation(callingPackage, () -> mMessageStoreUtil.updateValueOfProviderUri(
-                mMessageQueryHelper.getMessageUpdateUri(messageId, isIncoming),
-                SEEN_TIMESTAMP_COLUMN, notifiedTimestamp,
-                "Could not set the notified timestamp for message"));
-    }
-
-    @Override
-    public long getMessageSeenTimestamp(int messageId, boolean isIncoming, String callingPackage) {
-        return performReadOperation(callingPackage,
-                () -> mMessageStoreUtil.getLongValueFromTableRow(getMessageTableUri(isIncoming),
-                        SEEN_TIMESTAMP_COLUMN, MESSAGE_ID_COLUMN, messageId));
-    }
-
-    @Override
-    public int[] getMessageRecipients(int messageId, String callingPackage) {
-        return performReadOperation(callingPackage,
-                () -> mMessageQueryHelper.getDeliveryParticipantsForMessage(messageId));
-    }
-
-    @Override
-    public long getOutgoingDeliveryDeliveredTimestamp(int messageId, int participantId,
-            String callingPackage) {
-        return performReadOperation(callingPackage,
-                () -> mMessageQueryHelper.getLongValueFromDelivery(messageId, participantId,
-                        DELIVERED_TIMESTAMP_COLUMN));
-    }
-
-    @Override
-    public void setOutgoingDeliveryDeliveredTimestamp(int messageId, int participantId,
-            long deliveredTimestamp, String callingPackage) {
-        performWriteOperation(callingPackage, () -> mMessageStoreUtil.updateValueOfProviderUri(
-                mMessageQueryHelper.getMessageDeliveryUri(messageId, participantId),
-                DELIVERED_TIMESTAMP_COLUMN, deliveredTimestamp,
-                "Could not update the delivered timestamp for outgoing delivery"));
-    }
-
-    @Override
-    public long getOutgoingDeliverySeenTimestamp(int messageId, int participantId,
-            String callingPackage) {
-        return performReadOperation(callingPackage,
-                () -> mMessageQueryHelper.getLongValueFromDelivery(messageId, participantId,
-                        SEEN_TIMESTAMP_COLUMN));
-    }
-
-    @Override
-    public void setOutgoingDeliverySeenTimestamp(int messageId, int participantId,
-            long seenTimestamp, String callingPackage) {
-        performWriteOperation(callingPackage, () -> mMessageStoreUtil.updateValueOfProviderUri(
-                mMessageQueryHelper.getMessageDeliveryUri(messageId, participantId),
-                SEEN_TIMESTAMP_COLUMN, seenTimestamp,
-                "Could not update the seen timestamp for outgoing delivery"));
-    }
-
-    @Override
-    public int getOutgoingDeliveryStatus(int messageId, int participantId, String callingPackage) {
-        return performReadOperation(callingPackage, () -> {
-            // TODO - implement
-            return 0;
-        });
-    }
-
-    @Override
-    public void setOutgoingDeliveryStatus(int messageId, int participantId, int status,
-            String callingPackage) {
-        performWriteOperation(callingPackage, () -> {
-            // TODO - implement
-        });
-    }
-
-    @Override
-    public void setTextForMessage(int messageId, boolean isIncoming, String text,
-            String callingPackage) {
-        performWriteOperation(callingPackage, () -> mMessageStoreUtil.updateValueOfProviderUri(
-                mMessageQueryHelper.getMessageUpdateUri(messageId, isIncoming),
-                MESSAGE_TEXT_COLUMN,
-                text, "Could not set the text for message"));
-    }
-
-    @Override
-    public String getTextForMessage(int messageId, boolean isIncoming, String callingPackage) {
-        return performReadOperation(callingPackage,
-                () -> mMessageStoreUtil.getStringValueFromTableRow(getMessageTableUri(isIncoming),
-                        MESSAGE_TEXT_COLUMN, MESSAGE_ID_COLUMN, messageId));
-    }
-
-    @Override
-    public void setLatitudeForMessage(int messageId, boolean isIncoming, double latitude,
-            String callingPackage) {
-        performWriteOperation(callingPackage, () -> mMessageStoreUtil.updateValueOfProviderUri(
-                mMessageQueryHelper.getMessageUpdateUri(messageId, isIncoming), LATITUDE_COLUMN,
-                latitude, "Could not update latitude for message"));
-    }
-
-    @Override
-    public double getLatitudeForMessage(int messageId, boolean isIncoming, String callingPackage) {
-        return performReadOperation(callingPackage,
-                () -> mMessageStoreUtil.getDoubleValueFromTableRow(getMessageTableUri(isIncoming),
-                        LATITUDE_COLUMN, MESSAGE_ID_COLUMN, messageId));
-    }
-
-    @Override
-    public void setLongitudeForMessage(int messageId, boolean isIncoming, double longitude,
-            String callingPackage) {
-        performWriteOperation(callingPackage, () -> mMessageStoreUtil.updateValueOfProviderUri(
-                mMessageQueryHelper.getMessageUpdateUri(messageId, isIncoming),
-                LONGITUDE_COLUMN,
-                longitude, "Could not set longitude for message"));
-    }
-
-    @Override
-    public double getLongitudeForMessage(int messageId, boolean isIncoming, String callingPackage) {
-        return performReadOperation(callingPackage,
-                () -> mMessageStoreUtil.getDoubleValueFromTableRow(getMessageTableUri(isIncoming),
-                        LONGITUDE_COLUMN, MESSAGE_ID_COLUMN, messageId));
-    }
-
-    @Override
-    public int[] getFileTransfersAttachedToMessage(int messageId, boolean isIncoming,
-            String callingPackage) {
-        return performReadOperation(callingPackage, () -> {
-            // TODO - implement
-            return new int[0];
-        });
-    }
-
-    @Override
-    public int getSenderParticipant(int messageId, String callingPackage) {
-        return performReadOperation(callingPackage,
-                () -> mMessageStoreUtil.getIntValueFromTableRow(INCOMING_MESSAGE_URI,
-                        SENDER_PARTICIPANT_ID_COLUMN, MESSAGE_ID_COLUMN, messageId));
-    }
-
-    @Override
-    public void deleteFileTransfer(int partId, String callingPackage) {
-        performWriteOperation(callingPackage,
-                () -> mContentResolver.delete(mMessageQueryHelper.getFileTransferUpdateUri(partId),
-                        null,
-                        null));
-    }
-
-    @Override
-    public int storeFileTransfer(int messageId, boolean isIncoming,
-            RcsFileTransferCreationParams fileTransferCreationParameters, String callingPackage) {
-        return performCreateOperation(callingPackage, () -> {
-            ContentValues contentValues = mMessageQueryHelper.getContentValuesForFileTransfer(
-                    fileTransferCreationParameters);
-            Uri uri = mContentResolver.insert(
-                    mMessageQueryHelper.getFileTransferInsertionUri(messageId), contentValues);
-
-            if (uri != null) {
-                return Integer.parseInt(uri.getLastPathSegment());
-            }
-
-            return TRANSACTION_FAILED;
-        });
-    }
-
-    @Override
-    public void setFileTransferSessionId(int partId, String sessionId, String callingPackage) {
-        performWriteOperation(callingPackage, () -> mMessageStoreUtil.updateValueOfProviderUri(
-                mMessageQueryHelper.getFileTransferUpdateUri(partId), SESSION_ID_COLUMN,
-                sessionId,
-                "Could not set session ID for file transfer"));
-    }
-
-    @Override
-    public String getFileTransferSessionId(int partId, String callingPackage) {
-        return performReadOperation(callingPackage,
-                () -> mMessageStoreUtil.getStringValueFromTableRow(FILE_TRANSFER_URI,
-                        SESSION_ID_COLUMN,
-                        FILE_TRANSFER_ID_COLUMN, partId));
-    }
-
-    @Override
-    public void setFileTransferContentUri(int partId, Uri contentUri, String callingPackage) {
-        performWriteOperation(callingPackage, () -> mMessageStoreUtil.updateValueOfProviderUri(
-                mMessageQueryHelper.getFileTransferUpdateUri(partId), CONTENT_URI_COLUMN,
-                contentUri, "Could not set content URI for file transfer"));
-    }
-
-    @Override
-    public Uri getFileTransferContentUri(int partId, String callingPackage) {
-        return performReadOperation(callingPackage,
-                () -> mMessageStoreUtil.getUriValueFromTableRow(FILE_TRANSFER_URI,
-                        CONTENT_URI_COLUMN,
-                        FILE_TRANSFER_ID_COLUMN, partId));
-    }
-
-    @Override
-    public void setFileTransferContentType(int partId, String contentType, String callingPackage) {
-        performWriteOperation(callingPackage, () -> mMessageStoreUtil.updateValueOfProviderUri(
-                mMessageQueryHelper.getFileTransferUpdateUri(partId), CONTENT_TYPE_COLUMN,
-                contentType, "Could not set content type for file transfer"));
-    }
-
-    @Override
-    public String getFileTransferContentType(int partId, String callingPackage) {
-        return performReadOperation(callingPackage,
-                () -> mMessageStoreUtil.getStringValueFromTableRow(FILE_TRANSFER_URI,
-                        CONTENT_TYPE_COLUMN,
-                        FILE_TRANSFER_ID_COLUMN, partId));
-    }
-
-    @Override
-    public void setFileTransferFileSize(int partId, long fileSize, String callingPackage) {
-        performWriteOperation(callingPackage, () -> mMessageStoreUtil.updateValueOfProviderUri(
-                mMessageQueryHelper.getFileTransferUpdateUri(partId), FILE_SIZE_COLUMN,
-                fileSize,
-                "Could not set file size for file transfer"));
-    }
-
-    @Override
-    public long getFileTransferFileSize(int partId, String callingPackage) {
-        return performReadOperation(callingPackage,
-                () -> mMessageStoreUtil.getLongValueFromTableRow(FILE_TRANSFER_URI,
-                        FILE_SIZE_COLUMN,
-                        FILE_TRANSFER_ID_COLUMN, partId));
-    }
-
-    @Override
-    public void setFileTransferTransferOffset(int partId, long transferOffset,
-            String callingPackage) {
-        performWriteOperation(callingPackage, () -> mMessageStoreUtil.updateValueOfProviderUri(
-                mMessageQueryHelper.getFileTransferUpdateUri(partId),
-                SUCCESSFULLY_TRANSFERRED_BYTES,
-                transferOffset, "Could not set transfer offset for file transfer"));
-    }
-
-    @Override
-    public long getFileTransferTransferOffset(int partId, String callingPackage) {
-        return performReadOperation(callingPackage,
-                () -> mMessageStoreUtil.getLongValueFromTableRow(FILE_TRANSFER_URI,
-                        SUCCESSFULLY_TRANSFERRED_BYTES,
-                        FILE_TRANSFER_ID_COLUMN, partId));
-    }
-
-    @Override
-    public void setFileTransferStatus(int partId, int transferStatus, String callingPackage) {
-        performWriteOperation(callingPackage, () -> mMessageStoreUtil.updateValueOfProviderUri(
-                mMessageQueryHelper.getFileTransferUpdateUri(partId), TRANSFER_STATUS_COLUMN,
-                transferStatus, "Could not set transfer status for file transfer"));
-    }
-
-    @Override
-    public int getFileTransferStatus(int partId, String callingPackage) {
-        return performReadOperation(callingPackage,
-                () -> mMessageStoreUtil.getIntValueFromTableRow(FILE_TRANSFER_URI, STATUS_COLUMN,
-                        FILE_TRANSFER_ID_COLUMN, partId));
-    }
-
-    @Override
-    public void setFileTransferWidth(int partId, int width, String callingPackage) {
-        performWriteOperation(callingPackage, () -> mMessageStoreUtil.updateValueOfProviderUri(
-                mMessageQueryHelper.getFileTransferUpdateUri(partId), WIDTH_COLUMN, width,
-                "Could not set width of file transfer"));
-    }
-
-    @Override
-    public int getFileTransferWidth(int partId, String callingPackage) {
-        return performReadOperation(callingPackage,
-                () -> mMessageStoreUtil.getIntValueFromTableRow(FILE_TRANSFER_URI, WIDTH_COLUMN,
-                        FILE_TRANSFER_ID_COLUMN, partId));
-    }
-
-    @Override
-    public void setFileTransferHeight(int partId, int height, String callingPackage) {
-        performWriteOperation(callingPackage, () -> mMessageStoreUtil.updateValueOfProviderUri(
-                mMessageQueryHelper.getFileTransferUpdateUri(partId), HEIGHT_COLUMN, height,
-                "Could not set height of file transfer"));
-    }
-
-    @Override
-    public int getFileTransferHeight(int partId, String callingPackage) {
-        return performReadOperation(callingPackage,
-                () -> mMessageStoreUtil.getIntValueFromTableRow(FILE_TRANSFER_URI, HEIGHT_COLUMN,
-                        FILE_TRANSFER_ID_COLUMN, partId));
-    }
-
-    @Override
-    public void setFileTransferLength(int partId, long length, String callingPackage) {
-        performWriteOperation(callingPackage, () -> mMessageStoreUtil.updateValueOfProviderUri(
-                mMessageQueryHelper.getFileTransferUpdateUri(partId), DURATION_MILLIS_COLUMN,
-                length,
-                "Could not set length of file transfer"));
-    }
-
-    @Override
-    public long getFileTransferLength(int partId, String callingPackage) {
-        return performReadOperation(callingPackage,
-                () -> mMessageStoreUtil.getLongValueFromTableRow(FILE_TRANSFER_URI,
-                        DURATION_MILLIS_COLUMN,
-                        FILE_TRANSFER_ID_COLUMN, partId));
-    }
-
-    @Override
-    public void setFileTransferPreviewUri(int partId, Uri uri, String callingPackage) {
-        performWriteOperation(callingPackage, () -> mMessageStoreUtil.updateValueOfProviderUri(
-                mMessageQueryHelper.getFileTransferUpdateUri(partId), PREVIEW_URI_COLUMN, uri,
-                "Could not set preview URI of file transfer"));
-    }
-
-    @Override
-    public Uri getFileTransferPreviewUri(int partId, String callingPackage) {
-        return performReadOperation(callingPackage,
-                () -> mMessageStoreUtil.getUriValueFromTableRow(FILE_TRANSFER_URI,
-                        DURATION_MILLIS_COLUMN,
-                        FILE_TRANSFER_ID_COLUMN, partId));
-    }
-
-    @Override
-    public void setFileTransferPreviewType(int partId, String type, String callingPackage) {
-        performWriteOperation(callingPackage, () -> mMessageStoreUtil.updateValueOfProviderUri(
-                mMessageQueryHelper.getFileTransferUpdateUri(partId), PREVIEW_TYPE_COLUMN, type,
-                "Could not set preview type of file transfer"));
-    }
-
-    @Override
-    public String getFileTransferPreviewType(int partId, String callingPackage) {
-        return performReadOperation(callingPackage,
-                () -> mMessageStoreUtil.getStringValueFromTableRow(FILE_TRANSFER_URI,
-                        PREVIEW_TYPE_COLUMN,
-                        FILE_TRANSFER_ID_COLUMN, partId));
-    }
-
-    @Override
-    public int createGroupThreadNameChangedEvent(long timestamp, int threadId,
-            int originationParticipantId, String newName, String callingPackage) {
-        return performCreateOperation(callingPackage, () -> {
-            ContentValues eventSpecificValues = new ContentValues();
-            eventSpecificValues.put(NEW_NAME_COLUMN, newName);
-
-            return mEventQueryHelper.createGroupThreadEvent(NAME_CHANGED_EVENT_TYPE, timestamp,
-                    threadId, originationParticipantId, eventSpecificValues);
-        });
-    }
-
-    @Override
-    public int createGroupThreadIconChangedEvent(long timestamp, int threadId,
-            int originationParticipantId, Uri newIcon, String callingPackage) {
-        return performCreateOperation(callingPackage, () -> {
-            ContentValues eventSpecificValues = new ContentValues();
-            eventSpecificValues.put(NEW_ICON_URI_COLUMN,
-                    newIcon == null ? null : newIcon.toString());
-
-            return mEventQueryHelper.createGroupThreadEvent(ICON_CHANGED_EVENT_TYPE, timestamp,
-                    threadId, originationParticipantId, eventSpecificValues);
-        });
-    }
-
-    @Override
-    public int createGroupThreadParticipantJoinedEvent(long timestamp, int threadId,
-            int originationParticipantId, int participantId, String callingPackage) {
-        return performCreateOperation(callingPackage, () -> {
-            ContentValues eventSpecificValues = new ContentValues();
-            eventSpecificValues.put(DESTINATION_PARTICIPANT_ID_COLUMN, participantId);
-
-            return mEventQueryHelper.createGroupThreadEvent(PARTICIPANT_JOINED_EVENT_TYPE,
-                    timestamp,
-                    threadId, originationParticipantId, eventSpecificValues);
-        });
-    }
-
-    @Override
-    public int createGroupThreadParticipantLeftEvent(long timestamp, int threadId,
-            int originationParticipantId, int participantId, String callingPackage) {
-        return performCreateOperation(callingPackage, () -> {
-            ContentValues eventSpecificValues = new ContentValues();
-            eventSpecificValues.put(DESTINATION_PARTICIPANT_ID_COLUMN, participantId);
-
-            return mEventQueryHelper.createGroupThreadEvent(PARTICIPANT_LEFT_EVENT_TYPE, timestamp,
-                    threadId, originationParticipantId, eventSpecificValues);
-        });
-    }
-
-    @Override
-    public int createParticipantAliasChangedEvent(long timestamp, int participantId,
-            String newAlias, String callingPackage) {
-        return performCreateOperation(callingPackage, () -> {
-            ContentValues contentValues = new ContentValues(4);
-            contentValues.put(TIMESTAMP_COLUMN, timestamp);
-            contentValues.put(SOURCE_PARTICIPANT_ID_COLUMN, participantId);
-            contentValues.put(NEW_ALIAS_COLUMN, newAlias);
-
-            Uri uri = mContentResolver.insert(
-                    mEventQueryHelper.getParticipantEventInsertionUri(participantId),
-                    contentValues);
-
-            if (uri == null) {
-                throw new RemoteException(
-                        "Could not create RcsParticipantAliasChangedEvent with participant id: "
-                                + participantId);
-            }
-
-            return Integer.parseInt(uri.getLastPathSegment());
-        });
-    }
-}
diff --git a/src/java/com/android/internal/telephony/ims/RcsMessageStoreUtil.java b/src/java/com/android/internal/telephony/ims/RcsMessageStoreUtil.java
deleted file mode 100644
index 09eb2ff..0000000
--- a/src/java/com/android/internal/telephony/ims/RcsMessageStoreUtil.java
+++ /dev/null
@@ -1,161 +0,0 @@
-/*
- * Copyright (C) 2019 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.ims;
-
-import static android.provider.Telephony.RcsColumns.RcsIncomingMessageColumns.INCOMING_MESSAGE_URI;
-import static android.provider.Telephony.RcsColumns.RcsOutgoingMessageColumns.OUTGOING_MESSAGE_URI;
-
-import android.content.ContentResolver;
-import android.content.ContentValues;
-import android.database.Cursor;
-import android.net.Uri;
-import android.os.RemoteException;
-import android.text.TextUtils;
-
-/**
- * Utility functions for {@link RcsMessageStoreController}
- *
- * @hide
- */
-public class RcsMessageStoreUtil {
-    private ContentResolver mContentResolver;
-
-    RcsMessageStoreUtil(ContentResolver contentResolver) {
-        mContentResolver = contentResolver;
-    }
-
-    int getIntValueFromTableRow(Uri tableUri, String valueColumn, String idColumn,
-            int idValue) throws RemoteException {
-        try (Cursor cursor = getValueFromTableRow(tableUri, valueColumn, idColumn, idValue)) {
-            if (cursor != null && cursor.moveToFirst()) {
-                return cursor.getInt(cursor.getColumnIndex(valueColumn));
-            } else {
-                throw new RemoteException("The row with (" + idColumn + " = " + idValue
-                        + ") could not be found in " + tableUri);
-            }
-        }
-    }
-
-    long getLongValueFromTableRow(Uri tableUri, String valueColumn, String idColumn,
-            int idValue) throws RemoteException {
-        try (Cursor cursor = getValueFromTableRow(tableUri, valueColumn, idColumn, idValue)) {
-            if (cursor != null && cursor.moveToFirst()) {
-                return cursor.getLong(cursor.getColumnIndex(valueColumn));
-            } else {
-                throw new RemoteException("The row with (" + idColumn + " = " + idValue
-                        + ") could not be found in " + tableUri);
-            }
-        }
-    }
-
-    double getDoubleValueFromTableRow(Uri tableUri, String valueColumn, String idColumn,
-            int idValue) throws RemoteException {
-        try (Cursor cursor = getValueFromTableRow(tableUri, valueColumn, idColumn, idValue)) {
-            if (cursor != null && cursor.moveToFirst()) {
-                return cursor.getDouble(cursor.getColumnIndex(valueColumn));
-            } else {
-                throw new RemoteException("The row with (" + idColumn + " = " + idValue
-                        + ") could not be found in " + tableUri);
-            }
-        }
-    }
-
-    String getStringValueFromTableRow(Uri tableUri, String valueColumn, String idColumn,
-            int idValue) throws RemoteException {
-        try (Cursor cursor = getValueFromTableRow(tableUri, valueColumn, idColumn, idValue)) {
-            if (cursor != null && cursor.moveToFirst()) {
-                return cursor.getString(cursor.getColumnIndex(valueColumn));
-            } else {
-                throw new RemoteException("The row with (" + idColumn + " = " + idValue
-                        + ") could not be found in " + tableUri);
-            }
-        }
-    }
-
-    Uri getUriValueFromTableRow(Uri tableUri, String valueColumn, String idColumn,
-            int idValue) throws RemoteException {
-        try (Cursor cursor = getValueFromTableRow(tableUri, valueColumn, idColumn, idValue)) {
-            if (cursor != null && cursor.moveToFirst()) {
-                String uriAsString = cursor.getString(cursor.getColumnIndex(valueColumn));
-
-                if (!TextUtils.isEmpty(uriAsString)) {
-                    return Uri.parse(uriAsString);
-                }
-                return null;
-            } else {
-                throw new RemoteException("The row with (" + idColumn + " = " + idValue
-                        + ") could not be found in " + tableUri);
-            }
-        }
-    }
-
-    void updateValueOfProviderUri(Uri uri, String valueColumn, int value, String errorMessage)
-            throws RemoteException {
-        ContentValues contentValues = new ContentValues(1);
-        contentValues.put(valueColumn, value);
-        performUpdate(uri, contentValues, errorMessage);
-    }
-
-    void updateValueOfProviderUri(Uri uri, String valueColumn, double value, String errorMessage)
-            throws RemoteException {
-        ContentValues contentValues = new ContentValues(1);
-        contentValues.put(valueColumn, value);
-        performUpdate(uri, contentValues, errorMessage);
-    }
-
-    void updateValueOfProviderUri(Uri uri, String valueColumn, long value, String errorMessage)
-            throws RemoteException {
-        ContentValues contentValues = new ContentValues(1);
-        contentValues.put(valueColumn, value);
-        performUpdate(uri, contentValues, errorMessage);
-    }
-
-    void updateValueOfProviderUri(Uri uri, String valueColumn, String value, String errorMessage)
-            throws RemoteException {
-        ContentValues contentValues = new ContentValues(1);
-        contentValues.put(valueColumn, value);
-        performUpdate(uri, contentValues, errorMessage);
-    }
-
-    void updateValueOfProviderUri(Uri uri, String valueColumn, Uri value, String errorMessage)
-            throws RemoteException {
-        ContentValues contentValues = new ContentValues(1);
-        contentValues.put(valueColumn, value == null ? null : value.toString());
-        performUpdate(uri, contentValues, errorMessage);
-    }
-
-    private void performUpdate(Uri uri, ContentValues contentValues, String errorMessage)
-            throws RemoteException {
-        int updateCount = mContentResolver.update(uri, contentValues, null, null);
-
-        // TODO - convert remote exceptions to return values.
-        if (updateCount <= 0) {
-            throw new RemoteException(errorMessage);
-        }
-    }
-
-    private Cursor getValueFromTableRow(Uri tableUri, String valueColumn, String idColumn,
-            int idValue) {
-        return mContentResolver.query(tableUri, new String[]{valueColumn}, idColumn + "=?",
-                new String[]{Integer.toString(idValue)}, null);
-    }
-
-    static Uri getMessageTableUri(boolean isIncoming) {
-        return isIncoming ? INCOMING_MESSAGE_URI : OUTGOING_MESSAGE_URI;
-    }
-
-
-}
diff --git a/src/java/com/android/internal/telephony/ims/RcsParticipantQueryHelper.java b/src/java/com/android/internal/telephony/ims/RcsParticipantQueryHelper.java
deleted file mode 100644
index 11f9e3b..0000000
--- a/src/java/com/android/internal/telephony/ims/RcsParticipantQueryHelper.java
+++ /dev/null
@@ -1,67 +0,0 @@
-/*
- * Copyright (C) 2019 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.ims;
-
-import static android.provider.Telephony.RcsColumns.RcsParticipantColumns.RCS_PARTICIPANT_ID_COLUMN;
-import static android.provider.Telephony.RcsColumns.RcsParticipantColumns.RCS_PARTICIPANT_URI;
-import static android.telephony.ims.RcsQueryContinuationToken.QUERY_CONTINUATION_TOKEN;
-
-import android.content.ContentResolver;
-import android.database.Cursor;
-import android.net.Uri;
-import android.os.Bundle;
-import android.os.RemoteException;
-import android.telephony.ims.RcsParticipantQueryResultParcelable;
-import android.telephony.ims.RcsQueryContinuationToken;
-
-import java.util.ArrayList;
-import java.util.List;
-
-class RcsParticipantQueryHelper {
-    private final ContentResolver mContentResolver;
-
-    RcsParticipantQueryHelper(ContentResolver contentResolver) {
-        mContentResolver = contentResolver;
-    }
-
-    RcsParticipantQueryResultParcelable performParticipantQuery(Bundle bundle)
-            throws RemoteException {
-        RcsQueryContinuationToken continuationToken = null;
-        List<Integer> participantList = new ArrayList<>();
-
-        try (Cursor cursor = mContentResolver.query(RCS_PARTICIPANT_URI, null, bundle, null)) {
-            if (cursor == null) {
-                throw new RemoteException("Could not perform participant query, bundle: " + bundle);
-            }
-
-            while (cursor.moveToNext()) {
-                participantList.add(
-                        cursor.getInt(cursor.getColumnIndex(RCS_PARTICIPANT_ID_COLUMN)));
-            }
-
-            Bundle cursorExtras = cursor.getExtras();
-            if (cursorExtras != null) {
-                continuationToken = cursorExtras.getParcelable(QUERY_CONTINUATION_TOKEN);
-            }
-        }
-
-        return new RcsParticipantQueryResultParcelable(continuationToken, participantList);
-    }
-
-    static Uri getUriForParticipant(int participantId) {
-        return Uri.withAppendedPath(RCS_PARTICIPANT_URI, Integer.toString(participantId));
-    }
-}
diff --git a/src/java/com/android/internal/telephony/ims/RcsPermissions.java b/src/java/com/android/internal/telephony/ims/RcsPermissions.java
deleted file mode 100644
index 2b623bb..0000000
--- a/src/java/com/android/internal/telephony/ims/RcsPermissions.java
+++ /dev/null
@@ -1,61 +0,0 @@
-/*
- * Copyright (C) 2019 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.ims;
-
-import android.Manifest;
-import android.app.AppOpsManager;
-import android.content.Context;
-import android.os.Binder;
-
-class RcsPermissions {
-    static void checkReadPermissions(Context context, String callingPackage) {
-        int pid = Binder.getCallingPid();
-        int uid = Binder.getCallingUid();
-
-        context.enforcePermission(Manifest.permission.READ_SMS, pid, uid, null);
-
-        checkOp(context, uid, callingPackage, AppOpsManager.OP_READ_SMS);
-    }
-
-    static void checkWritePermissions(Context context, String callingPackage) {
-        int uid = Binder.getCallingUid();
-
-        checkOp(context, uid, callingPackage, AppOpsManager.OP_WRITE_SMS);
-    }
-
-    /**
-     * Notes the provided op, but throws even if the op mode is {@link AppOpsManager.MODE_IGNORED}.
-     * <p>
-     * {@link AppOpsManager.OP_WRITE_SMS} defaults to {@link AppOpsManager.MODE_IGNORED} to avoid
-     * crashing applications written before the app op was introduced. Since this is a new API,
-     * consumers should be aware of the permission requirements, and we should be safe to throw a
-     * {@link SecurityException} instead of providing a dummy value (which could cause unexpected
-     * application behavior and possible loss of user data). {@link AppOpsManager.OP_READ_SMS} is
-     * not normally in {@link AppOpsManager.MODE_IGNORED}, but we maintain the same behavior for
-     * consistency with handling of write permissions.
-     */
-    private static void checkOp(Context context, int uid, String callingPackage, int op) {
-        AppOpsManager appOps = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE);
-
-        int mode = appOps.noteOp(op, uid, callingPackage);
-
-        if (mode != AppOpsManager.MODE_ALLOWED) {
-            throw new SecurityException(
-                    AppOpsManager.opToName(op) + " not allowed for " + callingPackage);
-        }
-    }
-}
diff --git a/src/java/com/android/internal/telephony/ims/RcsThreadQueryHelper.java b/src/java/com/android/internal/telephony/ims/RcsThreadQueryHelper.java
deleted file mode 100644
index d4bc299..0000000
--- a/src/java/com/android/internal/telephony/ims/RcsThreadQueryHelper.java
+++ /dev/null
@@ -1,151 +0,0 @@
-/*
- * Copyright (C) 2019 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.ims;
-
-import static android.provider.Telephony.RcsColumns.Rcs1To1ThreadColumns.RCS_1_TO_1_THREAD_URI;
-import static android.provider.Telephony.RcsColumns.RcsGroupThreadColumns.GROUP_ICON_COLUMN;
-import static android.provider.Telephony.RcsColumns.RcsGroupThreadColumns.GROUP_NAME_COLUMN;
-import static android.provider.Telephony.RcsColumns.RcsGroupThreadColumns.RCS_GROUP_THREAD_URI;
-import static android.provider.Telephony.RcsColumns.RcsParticipantColumns.RCS_PARTICIPANT_ID_COLUMN;
-import static android.provider.Telephony.RcsColumns.RcsParticipantColumns.RCS_PARTICIPANT_URI_PART;
-import static android.provider.Telephony.RcsColumns.RcsThreadColumns.RCS_THREAD_ID_COLUMN;
-import static android.provider.Telephony.RcsColumns.RcsThreadColumns.RCS_THREAD_URI;
-import static android.provider.Telephony.RcsColumns.RcsUnifiedThreadColumns.THREAD_TYPE_1_TO_1;
-import static android.provider.Telephony.RcsColumns.RcsUnifiedThreadColumns.THREAD_TYPE_COLUMN;
-import static android.provider.Telephony.RcsColumns.RcsUnifiedThreadColumns.THREAD_TYPE_GROUP;
-import static android.telephony.ims.RcsQueryContinuationToken.QUERY_CONTINUATION_TOKEN;
-
-import android.content.ContentResolver;
-import android.content.ContentValues;
-import android.database.Cursor;
-import android.net.Uri;
-import android.os.Bundle;
-import android.os.RemoteException;
-import android.telephony.ims.RcsQueryContinuationToken;
-import android.telephony.ims.RcsThreadQueryResultParcelable;
-
-import com.android.ims.RcsTypeIdPair;
-
-import java.util.ArrayList;
-import java.util.List;
-
-// TODO optimize considering returned threads do not contain query information
-
-/**
- * A helper class focused on querying RCS threads from the
- * {@link com.android.providers.telephony.RcsProvider}
- */
-class RcsThreadQueryHelper {
-    private static final int THREAD_ID_INDEX_IN_INSERTION_URI = 1;
-
-    private final ContentResolver mContentResolver;
-    private final RcsParticipantQueryHelper mParticipantQueryHelper;
-
-    RcsThreadQueryHelper(ContentResolver contentResolver,
-            RcsParticipantQueryHelper participantQueryHelper) {
-        mContentResolver = contentResolver;
-        mParticipantQueryHelper = participantQueryHelper;
-    }
-
-    RcsThreadQueryResultParcelable performThreadQuery(Bundle bundle) throws RemoteException {
-        RcsQueryContinuationToken continuationToken = null;
-        List<RcsTypeIdPair> rcsThreadIdList = new ArrayList<>();
-        try (Cursor cursor = mContentResolver.query(RCS_THREAD_URI, null, bundle, null)) {
-            if (cursor == null) {
-                throw new RemoteException("Could not perform thread query, bundle: " + bundle);
-            }
-
-            while (cursor.moveToNext()) {
-                boolean isGroup = cursor.getInt(cursor.getColumnIndex(THREAD_TYPE_COLUMN))
-                        == THREAD_TYPE_GROUP;
-
-                if (isGroup) {
-                    int threadId = cursor.getInt(cursor.getColumnIndex(RCS_THREAD_ID_COLUMN));
-                    rcsThreadIdList.add(new RcsTypeIdPair(THREAD_TYPE_GROUP, threadId));
-                } else {
-                    int threadId = cursor.getInt(cursor.getColumnIndex(RCS_THREAD_ID_COLUMN));
-                    rcsThreadIdList.add(new RcsTypeIdPair(THREAD_TYPE_1_TO_1, threadId));
-                }
-            }
-
-            // If there is a continuation token, add it to the query result.
-            Bundle cursorExtras = cursor.getExtras();
-            if (cursorExtras != null) {
-                continuationToken = cursorExtras.getParcelable(QUERY_CONTINUATION_TOKEN);
-            }
-        }
-        return new RcsThreadQueryResultParcelable(continuationToken, rcsThreadIdList);
-    }
-
-    int create1To1Thread(int participantId) throws RemoteException {
-        ContentValues contentValues = new ContentValues(1);
-        contentValues.put(RCS_PARTICIPANT_ID_COLUMN, participantId);
-        Uri insertionUri = mContentResolver.insert(RCS_1_TO_1_THREAD_URI, contentValues);
-
-        if (insertionUri == null) {
-            throw new RemoteException("Rcs1To1Thread creation failed");
-        }
-
-        String threadIdAsString = insertionUri.getLastPathSegment();
-        int threadId = Integer.parseInt(threadIdAsString);
-
-        if (threadId <= 0) {
-            throw new RemoteException("Rcs1To1Thread creation failed");
-        }
-
-        return threadId;
-    }
-
-    int createGroupThread(String groupName, Uri groupIcon) throws RemoteException {
-        // Create the group
-        ContentValues contentValues = new ContentValues(1);
-        contentValues.put(GROUP_NAME_COLUMN, groupName);
-        if (groupIcon != null) {
-            contentValues.put(GROUP_ICON_COLUMN, groupIcon.toString());
-        }
-
-        Uri groupUri = mContentResolver.insert(RCS_GROUP_THREAD_URI,
-                contentValues);
-        if (groupUri == null) {
-            throw new RemoteException("RcsGroupThread creation failed");
-        }
-
-        // get the thread id from group URI
-        String threadIdAsString = groupUri.getPathSegments().get(THREAD_ID_INDEX_IN_INSERTION_URI);
-        int threadId = Integer.parseInt(threadIdAsString);
-
-        return threadId;
-    }
-
-    static Uri get1To1ThreadUri(int rcsThreadId) {
-        return Uri.withAppendedPath(RCS_1_TO_1_THREAD_URI, Integer.toString(rcsThreadId));
-    }
-
-    static Uri getGroupThreadUri(int rcsThreadId) {
-        return Uri.withAppendedPath(RCS_GROUP_THREAD_URI, Integer.toString(rcsThreadId));
-    }
-
-    static Uri getAllParticipantsInThreadUri(int rcsThreadId) {
-        return RCS_GROUP_THREAD_URI.buildUpon().appendPath(Integer.toString(rcsThreadId))
-                .appendPath(RCS_PARTICIPANT_URI_PART).build();
-    }
-
-    static Uri getParticipantInThreadUri(int rcsThreadId, int participantId) {
-        return RCS_GROUP_THREAD_URI.buildUpon().appendPath(Integer.toString(rcsThreadId))
-                .appendPath(RCS_PARTICIPANT_URI_PART).appendPath(Integer.toString(
-                        participantId)).build();
-    }
-}
diff --git a/src/java/com/android/internal/telephony/imsphone/ImsExternalCall.java b/src/java/com/android/internal/telephony/imsphone/ImsExternalCall.java
index 7b6162d..8b5b4a2 100644
--- a/src/java/com/android/internal/telephony/imsphone/ImsExternalCall.java
+++ b/src/java/com/android/internal/telephony/imsphone/ImsExternalCall.java
@@ -16,16 +16,13 @@
 
 package com.android.internal.telephony.imsphone;
 
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
 import android.telephony.ims.ImsExternalCallState;
 
 import com.android.internal.telephony.Call;
 import com.android.internal.telephony.CallStateException;
-import com.android.internal.telephony.Connection;
 import com.android.internal.telephony.Phone;
 
-import java.util.List;
-
 /**
  * Companion class for {@link ImsExternalConnection}; represents an external call which was
  * received via {@link ImsExternalCallState} info.
@@ -37,12 +34,7 @@
     @UnsupportedAppUsage
     public ImsExternalCall(Phone phone, ImsExternalConnection connection) {
         mPhone = phone;
-        mConnections.add(connection);
-    }
-
-    @Override
-    public List<Connection> getConnections() {
-        return mConnections;
+        addConnection(connection);
     }
 
     @Override
@@ -60,6 +52,12 @@
 
     }
 
+    @Override
+    public void hangup(@android.telecom.Call.RejectReason int rejectReason)
+            throws CallStateException {
+        // tumbleweed
+    }
+
     /**
      * Sets the call state to active.
      */
diff --git a/src/java/com/android/internal/telephony/imsphone/ImsExternalConnection.java b/src/java/com/android/internal/telephony/imsphone/ImsExternalConnection.java
index cd5c83d..414cbf8 100644
--- a/src/java/com/android/internal/telephony/imsphone/ImsExternalConnection.java
+++ b/src/java/com/android/internal/telephony/imsphone/ImsExternalConnection.java
@@ -16,7 +16,13 @@
 
 package com.android.internal.telephony.imsphone;
 
-import com.android.internal.R;
+import android.compat.annotation.UnsupportedAppUsage;
+import android.content.Context;
+import android.net.Uri;
+import android.telecom.PhoneAccount;
+import android.telephony.PhoneNumberUtils;
+import android.telephony.ims.ImsExternalCallState;
+
 import com.android.internal.telephony.Call;
 import com.android.internal.telephony.CallStateException;
 import com.android.internal.telephony.Connection;
@@ -24,13 +30,6 @@
 import com.android.internal.telephony.PhoneConstants;
 import com.android.internal.telephony.UUSInfo;
 
-import android.annotation.UnsupportedAppUsage;
-import android.content.Context;
-import android.net.Uri;
-import android.telecom.PhoneAccount;
-import android.telephony.PhoneNumberUtils;
-import android.telephony.ims.ImsExternalCallState;
-
 import java.util.Collections;
 import java.util.Set;
 import java.util.concurrent.ConcurrentHashMap;
@@ -134,6 +133,18 @@
     }
 
     @Override
+    public void transfer(String number, boolean isConfirmationRequired) throws CallStateException {
+        // Transfer is not supported for external calls.
+        throw new CallStateException("Transfer is not supported for external calls");
+    }
+
+    @Override
+    public void consultativeTransfer(Connection other) throws CallStateException {
+        // Transfer is not supported for external calls.
+        throw new CallStateException("Transfer is not supported for external calls");
+    }
+
+    @Override
     public void separate() throws CallStateException {
         // No-op - Separate is not supported for external calls.
     }
diff --git a/src/java/com/android/internal/telephony/imsphone/ImsPhone.java b/src/java/com/android/internal/telephony/imsphone/ImsPhone.java
index 138f875..b7eb8af 100644
--- a/src/java/com/android/internal/telephony/imsphone/ImsPhone.java
+++ b/src/java/com/android/internal/telephony/imsphone/ImsPhone.java
@@ -16,6 +16,9 @@
 
 package com.android.internal.telephony.imsphone;
 
+import static android.telephony.ims.ImsManager.EXTRA_WFC_REGISTRATION_FAILURE_MESSAGE;
+import static android.telephony.ims.ImsManager.EXTRA_WFC_REGISTRATION_FAILURE_TITLE;
+
 import static com.android.internal.telephony.CommandsInterface.CB_FACILITY_BAIC;
 import static com.android.internal.telephony.CommandsInterface.CB_FACILITY_BAICr;
 import static com.android.internal.telephony.CommandsInterface.CB_FACILITY_BAOC;
@@ -24,6 +27,7 @@
 import static com.android.internal.telephony.CommandsInterface.CB_FACILITY_BA_ALL;
 import static com.android.internal.telephony.CommandsInterface.CB_FACILITY_BA_MO;
 import static com.android.internal.telephony.CommandsInterface.CB_FACILITY_BA_MT;
+import static com.android.internal.telephony.CommandsInterface.CB_FACILITY_BIC_ACR;
 import static com.android.internal.telephony.CommandsInterface.CF_ACTION_DISABLE;
 import static com.android.internal.telephony.CommandsInterface.CF_ACTION_ENABLE;
 import static com.android.internal.telephony.CommandsInterface.CF_ACTION_ERASURE;
@@ -37,16 +41,14 @@
 import static com.android.internal.telephony.CommandsInterface.SERVICE_CLASS_NONE;
 import static com.android.internal.telephony.CommandsInterface.SERVICE_CLASS_VOICE;
 
-import android.annotation.UnsupportedAppUsage;
 import android.app.Activity;
-import android.app.ActivityManager;
 import android.app.Notification;
 import android.app.NotificationManager;
 import android.app.PendingIntent;
+import android.compat.annotation.UnsupportedAppUsage;
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
-import android.net.NetworkStats;
 import android.net.Uri;
 import android.os.AsyncResult;
 import android.os.Bundle;
@@ -58,13 +60,12 @@
 import android.os.Registrant;
 import android.os.RegistrantList;
 import android.os.ResultReceiver;
-import android.os.SystemProperties;
 import android.os.UserHandle;
+import android.sysprop.TelephonyProperties;
 import android.telephony.AccessNetworkConstants;
 import android.telephony.CarrierConfigManager;
 import android.telephony.NetworkRegistrationInfo;
 import android.telephony.PhoneNumberUtils;
-import android.telephony.Rlog;
 import android.telephony.ServiceState;
 import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyManager;
@@ -72,8 +73,12 @@
 import android.telephony.ims.ImsCallForwardInfo;
 import android.telephony.ims.ImsCallProfile;
 import android.telephony.ims.ImsReasonInfo;
+import android.telephony.ims.ImsSsData;
 import android.telephony.ims.ImsSsInfo;
+import android.telephony.ims.RegistrationManager;
+import android.telephony.ims.stub.ImsUtImplBase;
 import android.text.TextUtils;
+import android.util.LocalLog;
 
 import com.android.ims.ImsEcbm;
 import com.android.ims.ImsEcbmStateListener;
@@ -82,6 +87,7 @@
 import com.android.ims.ImsUtInterface;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.telephony.Call;
+import com.android.internal.telephony.CallFailCause;
 import com.android.internal.telephony.CallForwardInfo;
 import com.android.internal.telephony.CallStateException;
 import com.android.internal.telephony.CallTracker;
@@ -96,18 +102,23 @@
 import com.android.internal.telephony.ServiceStateTracker;
 import com.android.internal.telephony.TelephonyComponentFactory;
 import com.android.internal.telephony.TelephonyIntents;
-import com.android.internal.telephony.TelephonyProperties;
 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.metrics.TelephonyMetrics;
+import com.android.internal.telephony.metrics.VoiceCallSessionStats;
+import com.android.internal.telephony.nano.TelephonyProto.ImsConnectionState;
 import com.android.internal.telephony.uicc.IccRecords;
 import com.android.internal.telephony.util.NotificationChannelController;
+import com.android.internal.util.IndentingPrintWriter;
+import com.android.telephony.Rlog;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.List;
+import java.util.function.Consumer;
 
 /**
  * {@hide}
@@ -127,17 +138,20 @@
     @VisibleForTesting
     public static final int EVENT_SERVICE_STATE_CHANGED             = EVENT_LAST + 8;
     private static final int EVENT_VOICE_CALL_ENDED                  = EVENT_LAST + 9;
+    private static final int EVENT_INITIATE_VOLTE_SILENT_REDIAL      = EVENT_LAST + 10;
 
     static final int RESTART_ECM_TIMER = 0; // restart Ecm timer
     static final int CANCEL_ECM_TIMER  = 1; // cancel Ecm timer
 
     // Default Emergency Callback Mode exit timer
-    private static final int DEFAULT_ECM_EXIT_TIMER_VALUE = 300000;
+    private static final long DEFAULT_ECM_EXIT_TIMER_VALUE = 300000;
 
     public static class ImsDialArgs extends DialArgs {
         public static class Builder extends DialArgs.Builder<ImsDialArgs.Builder> {
             private android.telecom.Connection.RttTextStream mRttTextStream;
             private int mClirMode = CommandsInterface.CLIR_DEFAULT;
+            private int mRetryCallFailCause = ImsReasonInfo.CODE_UNSPECIFIED;
+            private int mRetryCallFailNetworkType = TelephonyManager.NETWORK_TYPE_UNKNOWN;
 
             public static ImsDialArgs.Builder from(DialArgs dialArgs) {
                 return new ImsDialArgs.Builder()
@@ -152,7 +166,9 @@
                         .setVideoState(dialArgs.videoState)
                         .setIntentExtras(dialArgs.intentExtras)
                         .setRttTextStream(dialArgs.rttTextStream)
-                        .setClirMode(dialArgs.clirMode);
+                        .setClirMode(dialArgs.clirMode)
+                        .setRetryCallFailCause(dialArgs.retryCallFailCause)
+                        .setRetryCallFailNetworkType(dialArgs.retryCallFailNetworkType);
             }
 
             public ImsDialArgs.Builder setRttTextStream(
@@ -166,6 +182,16 @@
                 return this;
             }
 
+            public ImsDialArgs.Builder setRetryCallFailCause(int retryCallFailCause) {
+                this.mRetryCallFailCause = retryCallFailCause;
+                return this;
+            }
+
+            public ImsDialArgs.Builder setRetryCallFailNetworkType(int retryCallFailNetworkType) {
+                this.mRetryCallFailNetworkType = retryCallFailNetworkType;
+                return this;
+            }
+
             public ImsDialArgs build() {
                 return new ImsDialArgs(this);
             }
@@ -179,11 +205,15 @@
 
         /** The CLIR mode to use */
         public final int clirMode;
+        public final int retryCallFailCause;
+        public final int retryCallFailNetworkType;
 
         private ImsDialArgs(ImsDialArgs.Builder b) {
             super(b);
             this.rttTextStream = b.mRttTextStream;
             this.clirMode = b.mClirMode;
+            this.retryCallFailCause = b.mRetryCallFailCause;
+            this.retryCallFailNetworkType = b.mRetryCallFailNetworkType;
         }
     }
 
@@ -208,10 +238,16 @@
 
     private final RegistrantList mSilentRedialRegistrants = new RegistrantList();
 
-    private boolean mImsRegistered = false;
+    private final LocalLog mRegLocalLog = new LocalLog(100);
+    private TelephonyMetrics mMetrics;
+
+    // The helper class to receive and store the MmTel registration status updated.
+    private ImsRegistrationCallbackHelper mImsMmTelRegistrationHelper;
 
     private boolean mRoaming = false;
 
+    private boolean mIsInImsEcm = false;
+
     // List of Registrants to send supplementary service notifications to.
     private RegistrantList mSsnRegistrants = new RegistrantList();
 
@@ -235,6 +271,11 @@
     }
 
     @Override
+    public int getEmergencyNumberDbVersion() {
+        return getEmergencyNumberTracker().getEmergencyNumberDbVersion();
+    }
+
+    @Override
     public EmergencyNumberTracker getEmergencyNumberTracker() {
         return mDefaultPhone.getEmergencyNumberTracker();
     }
@@ -289,6 +330,11 @@
 
         mPhoneId = mDefaultPhone.getPhoneId();
 
+        mMetrics = TelephonyMetrics.getInstance();
+
+        mImsMmTelRegistrationHelper = new ImsRegistrationCallbackHelper(mMmTelRegistrationUpdate,
+                context.getMainExecutor());
+
         PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
         mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, LOG_TAG);
         mWakeLock.setReferenceCounted(false);
@@ -309,6 +355,8 @@
         mDefaultPhone.registerForServiceStateChanged(this, EVENT_SERVICE_STATE_CHANGED, null);
         // Force initial roaming state update later, on EVENT_CARRIER_CONFIG_CHANGED.
         // Settings provider or CarrierConfig may not be loaded now.
+
+        mDefaultPhone.registerForVolteSilentRedial(this, EVENT_INITIATE_VOLTE_SILENT_REDIAL, null);
     }
 
     //todo: get rid of this function. It is not needed since parentPhone obj never changes
@@ -331,6 +379,10 @@
             }
             mDefaultPhone.unregisterForServiceStateChanged(this);
         }
+
+        if (mDefaultPhone != null) {
+            mDefaultPhone.unregisterForVolteSilentRedial(this);
+        }
     }
 
     @UnsupportedAppUsage
@@ -345,7 +397,7 @@
         boolean isVoiceRegStateChanged = false;
 
         synchronized (this) {
-            isVoiceRegStateChanged = mSS.getVoiceRegState() != state;
+            isVoiceRegStateChanged = mSS.getState() != state;
             mSS.setVoiceRegState(state);
         }
         updateDataServiceState();
@@ -420,7 +472,7 @@
     }
 
     @Override
-    public void explicitCallTransfer() {
+    public void explicitCallTransfer() throws CallStateException {
         mCT.explicitCallTransfer();
     }
 
@@ -577,8 +629,18 @@
                 if (getRingingCall().getState() != ImsPhoneCall.State.IDLE) {
                     if (DBG) logd("MmiCode 2: accept ringing call");
                     mCT.acceptCall(ImsCallProfile.CALL_TYPE_VOICE);
-                } else {
-                    if (DBG) logd("MmiCode 2: holdActiveCall");
+                } else if (getBackgroundCall().getState() == ImsPhoneCall.State.HOLDING) {
+                    // If there's an active ongoing call as well, hold it and the background one
+                    // should automatically unhold. Otherwise just unhold the background call.
+                    if (getForegroundCall().getState() != ImsPhoneCall.State.IDLE) {
+                        if (DBG) logd("MmiCode 2: switch holding and active");
+                        mCT.holdActiveCall();
+                    } else {
+                        if (DBG) logd("MmiCode 2: unhold held call");
+                        mCT.unholdHeldCall();
+                    }
+                } else if (getForegroundCall().getState() != ImsPhoneCall.State.IDLE) {
+                    if (DBG) logd("MmiCode 2: hold active call");
                     mCT.holdActiveCall();
                 }
             } catch (CallStateException e) {
@@ -602,15 +664,17 @@
     }
 
     private boolean handleEctIncallSupplementaryService(String dialString) {
-
-        int len = dialString.length();
-
-        if (len != 1) {
+        if (dialString.length() != 1) {
             return false;
         }
 
-        if (DBG) logd("MmiCode 4: not support explicit call transfer");
-        notifySuppServiceFailed(Phone.SuppService.TRANSFER);
+        if (DBG) logd("MmiCode 4: explicit call transfer");
+        try {
+            explicitCallTransfer();
+        } catch (CallStateException e) {
+            if (DBG) Rlog.d(LOG_TAG, "explicit call transfer failed", e);
+            notifySuppServiceFailed(Phone.SuppService.TRANSFER);
+        }
         return true;
     }
 
@@ -684,12 +748,18 @@
     }
 
     @Override
+    public boolean isInImsEcm() {
+        return mIsInImsEcm;
+    }
+
+    @Override
     public boolean isInEcm() {
         return mDefaultPhone.isInEcm();
     }
 
     @Override
     public void setIsInEcm(boolean isInEcm){
+        mIsInImsEcm = isInEcm;
         mDefaultPhone.setIsInEcm(isInEcm);
     }
 
@@ -709,8 +779,22 @@
     }
 
     @Override
-    public void setRadioPower(boolean on) {
-        mDefaultPhone.setRadioPower(on);
+    public void setRadioPower(boolean on, boolean forEmergencyCall,
+            boolean isSelectedPhoneForEmergencyCall, boolean forceApply) {
+        mDefaultPhone.setRadioPower(on, forEmergencyCall, isSelectedPhoneForEmergencyCall,
+                forceApply);
+    }
+
+    @Override
+    public Connection startConference(String[] participantsToDial, DialArgs dialArgs)
+            throws CallStateException {
+         ImsDialArgs.Builder imsDialArgsBuilder;
+         if (!(dialArgs instanceof ImsDialArgs)) {
+             imsDialArgsBuilder = ImsDialArgs.Builder.from(dialArgs);
+         } else {
+             imsDialArgsBuilder = ImsDialArgs.Builder.from((ImsDialArgs) dialArgs);
+         }
+         return mCT.startConference(participantsToDial, imsDialArgsBuilder.build());
     }
 
     @Override
@@ -722,6 +806,8 @@
                                     ResultReceiver wrappedCallback)
             throws CallStateException {
 
+        mLastDialString = dialString;
+
         // Need to make sure dialString gets parsed properly
         String newDialString = PhoneNumberUtils.stripSeparators(dialString);
 
@@ -954,6 +1040,13 @@
     @Override
     public void getCallForwardingOption(int commandInterfaceCFReason,
             Message onComplete) {
+        getCallForwardingOption(commandInterfaceCFReason,
+                SERVICE_CLASS_VOICE, onComplete);
+    }
+
+    @Override
+    public void getCallForwardingOption(int commandInterfaceCFReason, int serviceClass,
+            Message onComplete) {
         if (DBG) logd("getCallForwardingOption reason=" + commandInterfaceCFReason);
         if (isValidCommandInterfaceCFReason(commandInterfaceCFReason)) {
             if (DBG) logd("requesting call forwarding query.");
@@ -982,6 +1075,7 @@
     }
 
     @UnsupportedAppUsage
+    @Override
     public void setCallForwardingOption(int commandInterfaceCFAction,
             int commandInterfaceCFReason,
             String dialingNumber,
@@ -1060,28 +1154,30 @@
 
     private int getCBTypeFromFacility(String facility) {
         if (CB_FACILITY_BAOC.equals(facility)) {
-            return ImsUtInterface.CB_BAOC;
+            return ImsUtImplBase.CALL_BARRING_ALL_OUTGOING;
         } else if (CB_FACILITY_BAOIC.equals(facility)) {
-            return ImsUtInterface.CB_BOIC;
+            return ImsUtImplBase.CALL_BARRING_OUTGOING_INTL;
         } else if (CB_FACILITY_BAOICxH.equals(facility)) {
-            return ImsUtInterface.CB_BOIC_EXHC;
+            return ImsUtImplBase.CALL_BARRING_OUTGOING_INTL_EXCL_HOME;
         } else if (CB_FACILITY_BAIC.equals(facility)) {
-            return ImsUtInterface.CB_BAIC;
+            return ImsUtImplBase.CALL_BARRING_ALL_INCOMING;
         } else if (CB_FACILITY_BAICr.equals(facility)) {
-            return ImsUtInterface.CB_BIC_WR;
+            return ImsUtImplBase.CALL_BLOCKING_INCOMING_WHEN_ROAMING;
         } else if (CB_FACILITY_BA_ALL.equals(facility)) {
-            return ImsUtInterface.CB_BA_ALL;
+            return ImsUtImplBase.CALL_BARRING_ALL;
         } else if (CB_FACILITY_BA_MO.equals(facility)) {
-            return ImsUtInterface.CB_BA_MO;
+            return ImsUtImplBase.CALL_BARRING_OUTGOING_ALL_SERVICES;
         } else if (CB_FACILITY_BA_MT.equals(facility)) {
-            return ImsUtInterface.CB_BA_MT;
+            return ImsUtImplBase.CALL_BARRING_INCOMING_ALL_SERVICES;
+        } else if (CB_FACILITY_BIC_ACR.equals(facility)) {
+            return ImsUtImplBase.CALL_BARRING_ANONYMOUS_INCOMING;
         }
 
         return 0;
     }
 
     public void getCallBarring(String facility, Message onComplete) {
-        getCallBarring(facility, onComplete, CommandsInterface.SERVICE_CLASS_NONE);
+        getCallBarring(facility, onComplete, CommandsInterface.SERVICE_CLASS_VOICE);
     }
 
     public void getCallBarring(String facility, Message onComplete, int serviceClass) {
@@ -1107,12 +1203,12 @@
     public void setCallBarring(String facility, boolean lockState, String password,
             Message onComplete) {
         setCallBarring(facility, lockState, password, onComplete,
-                CommandsInterface.SERVICE_CLASS_NONE);
+                CommandsInterface.SERVICE_CLASS_VOICE);
     }
 
     @Override
     public void setCallBarring(String facility, boolean lockState, String password,
-            Message onComplete,  int serviceClass) {
+            Message onComplete, int serviceClass) {
         if (DBG) {
             logd("setCallBarring facility=" + facility
                     + ", lockState=" + lockState + ", serviceClass = " + serviceClass);
@@ -1130,9 +1226,8 @@
 
         try {
             ImsUtInterface ut = mCT.getUtInterface();
-            // password is not required with Ut interface
             ut.updateCallBarring(getCBTypeFromFacility(facility), action,
-                    resp, null,  serviceClass);
+                    resp, null, serviceClass, password);
         } catch (ImsException e) {
             sendErrorResponse(onComplete, e);
         }
@@ -1148,6 +1243,8 @@
     }
 
     public void sendUSSD(String ussdString, Message response) {
+        Rlog.d(LOG_TAG, "sendUssd ussdString = " + ussdString);
+        mLastDialString = ussdString;
         mCT.sendUSSD(ussdString, response);
     }
 
@@ -1260,7 +1357,7 @@
             } else {
                 found.onUssdFinished(ussdMessage, isUssdRequest);
             }
-        } else if (!isUssdError && ussdMessage != null) {
+        } else if (!isUssdError && !TextUtils.isEmpty(ussdMessage)) {
                 // pending USSD not found
                 // The network may initiate its own USSD request
 
@@ -1306,11 +1403,11 @@
     public ArrayList<Connection> getHandoverConnection() {
         ArrayList<Connection> connList = new ArrayList<Connection>();
         // Add all foreground call connections
-        connList.addAll(getForegroundCall().mConnections);
+        connList.addAll(getForegroundCall().getConnections());
         // Add all background call connections
-        connList.addAll(getBackgroundCall().mConnections);
+        connList.addAll(getBackgroundCall().getConnections());
         // Add all background call connections
-        connList.addAll(getRingingCall().mConnections);
+        connList.addAll(getRingingCall().getConnections());
         if (connList.size() > 0) {
             return connList;
         } else {
@@ -1373,6 +1470,11 @@
         return cfInfo;
     }
 
+    @Override
+    public String getLine1Number() {
+        return mDefaultPhone.getLine1Number();
+    }
+
     /**
      * Used to Convert ImsCallForwardInfo[] to CallForwardInfo[].
      * Update received call forward status to default IccRecords.
@@ -1384,20 +1486,15 @@
             cfInfos = new CallForwardInfo[infos.length];
         }
 
-        IccRecords r = mDefaultPhone.getIccRecords();
         if (infos == null || infos.length == 0) {
-            if (r != null) {
-                // Assume the default is not active
-                // Set unconditional CFF in SIM to false
-                setVoiceCallForwardingFlag(r, 1, false, null);
-            }
+            // Assume the default is not active
+            // Set unconditional CFF in SIM to false
+            setVoiceCallForwardingFlag(getIccRecords(), 1, false, null);
         } else {
             for (int i = 0, s = infos.length; i < s; i++) {
                 if (infos[i].getCondition() == ImsUtInterface.CDIV_CF_UNCONDITIONAL) {
-                    if (r != null) {
-                        setVoiceCallForwardingFlag(r, 1, (infos[i].getStatus() == 1),
-                                infos[i].getNumber());
-                    }
+                    setVoiceCallForwardingFlag(getIccRecords(), 1, (infos[i].getStatus() == 1),
+                        infos[i].getNumber());
                 }
                 cfInfos[i] = getCallForwardInfo(infos[i]);
             }
@@ -1445,7 +1542,7 @@
         if (mSS != null && mDefaultPhone.getServiceStateTracker() != null
                 && mDefaultPhone.getServiceStateTracker().mSS != null) {
             ServiceState ss = mDefaultPhone.getServiceStateTracker().mSS;
-            mSS.setDataRegState(ss.getDataRegState());
+            mSS.setDataRegState(ss.getDataRegistrationState());
             List<NetworkRegistrationInfo> nriList =
                     ss.getNetworkRegistrationInfoListForDomain(NetworkRegistrationInfo.DOMAIN_PS);
             for (NetworkRegistrationInfo nri : nriList) {
@@ -1464,10 +1561,9 @@
         if (DBG) logd("handleMessage what=" + msg.what);
         switch (msg.what) {
             case EVENT_SET_CALL_FORWARD_DONE:
-                IccRecords r = mDefaultPhone.getIccRecords();
                 Cf cf = (Cf) ar.userObj;
-                if (cf.mIsCfu && ar.exception == null && r != null) {
-                    setVoiceCallForwardingFlag(r, 1, msg.arg1 == 1, cf.mSetCfNumber);
+                if (cf.mIsCfu && ar.exception == null) {
+                    setVoiceCallForwardingFlag(getIccRecords(), 1, msg.arg1 == 1, cf.mSetCfNumber);
                 }
                 sendResponse(cf.mOnComplete, null, ar.exception);
                 break;
@@ -1494,10 +1590,12 @@
                 break;
 
             case EVENT_GET_CLIR_DONE:
-                Bundle ssInfo = (Bundle) ar.result;
+                ImsSsInfo ssInfo = (ImsSsInfo) ar.result;
                 int[] clirInfo = null;
                 if (ssInfo != null) {
-                    clirInfo = ssInfo.getIntArray(ImsPhoneMmiCode.UT_BUNDLE_KEY_CLIR);
+                    // Unfortunately callers still use the old {n,m} format of ImsSsInfo, so return
+                    // that for compatibility
+                    clirInfo = ssInfo.getCompatArray(ImsSsData.SS_CLIR);
                 }
                 sendResponse((Message) ar.userObj, clirInfo, ar.exception);
                 break;
@@ -1533,6 +1631,36 @@
                     updateRoamingState(sst.mSS);
                 }
                 break;
+            case EVENT_INITIATE_VOLTE_SILENT_REDIAL: {
+                if (VDBG) logd("EVENT_INITIATE_VOLTE_SILENT_REDIAL");
+                ar = (AsyncResult) msg.obj;
+                if (ar.exception == null && ar.result != null) {
+                    SilentRedialParam result = (SilentRedialParam) ar.result;
+                    String dialString = result.dialString;
+                    int causeCode = result.causeCode;
+                    DialArgs dialArgs = result.dialArgs;
+                    if (VDBG) logd("dialString=" + dialString + " causeCode=" + causeCode);
+
+                    try {
+                        Connection cn = dial(dialString,
+                                updateDialArgsForVolteSilentRedial(dialArgs, causeCode));
+                        Rlog.d(LOG_TAG, "Notify volte redial connection changed cn: " + cn);
+                        if (mDefaultPhone != null) {
+                            // don't care it is null or not.
+                            mDefaultPhone.notifyRedialConnectionChanged(cn);
+                        }
+                    } catch (CallStateException e) {
+                        Rlog.e(LOG_TAG, "volte silent redial failed: " + e);
+                        if (mDefaultPhone != null) {
+                            mDefaultPhone.notifyRedialConnectionChanged(null);
+                        }
+                    }
+                } else {
+                    if (VDBG) logd("EVENT_INITIATE_VOLTE_SILENT_REDIAL" +
+                                   " has exception or empty result");
+                }
+                break;
+            }
 
             default:
                 super.handleMessage(msg);
@@ -1571,9 +1699,9 @@
     private void sendEmergencyCallbackModeChange() {
         // Send an Intent
         Intent intent = new Intent(TelephonyIntents.ACTION_EMERGENCY_CALLBACK_MODE_CHANGED);
-        intent.putExtra(PhoneConstants.PHONE_IN_ECM_STATE, isInEcm());
+        intent.putExtra(TelephonyManager.EXTRA_PHONE_IN_ECM_STATE, isInEcm());
         SubscriptionManager.putPhoneIdAndSubIdExtra(intent, getPhoneId());
-        ActivityManager.broadcastStickyIntent(intent, UserHandle.USER_ALL);
+        mContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL);
         if (DBG) logd("sendEmergencyCallbackModeChange: isInEcm=" + isInEcm());
     }
 
@@ -1606,8 +1734,8 @@
 
             // Post this runnable so we will automatically exit
             // if no one invokes exitEmergencyCallbackMode() directly.
-            long delayInMillis = SystemProperties.getLong(
-                    TelephonyProperties.PROPERTY_ECM_EXIT_TIMER, DEFAULT_ECM_EXIT_TIMER_VALUE);
+            long delayInMillis = TelephonyProperties.ecm_exit_timer()
+                    .orElse(DEFAULT_ECM_EXIT_TIMER_VALUE);
             postDelayed(mExitEcmRunnable, delayInMillis);
             // We don't want to go to sleep while in Ecm
             mWakeLock.acquire();
@@ -1650,12 +1778,14 @@
             case CANCEL_ECM_TIMER:
                 removeCallbacks(mExitEcmRunnable);
                 ((GsmCdmaPhone) mDefaultPhone).notifyEcbmTimerReset(Boolean.TRUE);
+                setEcmCanceledForEmergency(true /*isCanceled*/);
                 break;
             case RESTART_ECM_TIMER:
-                long delayInMillis = SystemProperties.getLong(
-                        TelephonyProperties.PROPERTY_ECM_EXIT_TIMER, DEFAULT_ECM_EXIT_TIMER_VALUE);
+                long delayInMillis = TelephonyProperties.ecm_exit_timer()
+                        .orElse(DEFAULT_ECM_EXIT_TIMER_VALUE);
                 postDelayed(mExitEcmRunnable, delayInMillis);
                 ((GsmCdmaPhone) mDefaultPhone).notifyEcbmTimerReset(Boolean.FALSE);
+                setEcmCanceledForEmergency(false /*isCanceled*/);
                 break;
             default:
                 loge("handleTimerInEmergencyCallbackMode, unsupported action " + action);
@@ -1678,7 +1808,7 @@
     }
 
     @Override
-    public boolean isImsCapabilityAvailable(int capability, int regTech) {
+    public boolean isImsCapabilityAvailable(int capability, int regTech) throws ImsException {
         return mCT.isImsCapabilityAvailable(capability, regTech);
     }
 
@@ -1704,18 +1834,36 @@
     }
 
     @Override
+    public void getImsRegistrationTech(Consumer<Integer> callback) {
+        mCT.getImsRegistrationTech(callback);
+    }
+
+    @Override
+    public void getImsRegistrationState(Consumer<Integer> callback) {
+        callback.accept(mImsMmTelRegistrationHelper.getImsRegistrationState());
+    }
+
+    @Override
     public Phone getDefaultPhone() {
         return mDefaultPhone;
     }
 
     @Override
     public boolean isImsRegistered() {
-        return mImsRegistered;
+        return mImsMmTelRegistrationHelper.isImsRegistered();
     }
 
+    // Not used, but not removed due to UnsupportedAppUsage tag.
     @UnsupportedAppUsage
-    public void setImsRegistered(boolean value) {
-        mImsRegistered = value;
+    public void setImsRegistered(boolean isRegistered) {
+        mImsMmTelRegistrationHelper.updateRegistrationState(
+                isRegistered ? RegistrationManager.REGISTRATION_STATE_REGISTERED :
+                        RegistrationManager.REGISTRATION_STATE_NOT_REGISTERED);
+    }
+
+    public void setImsRegistrationState(@RegistrationManager.ImsRegistrationState int value) {
+        if (DBG) logd("setImsRegistrationState: " + value);
+        mImsMmTelRegistrationHelper.updateRegistrationState(value);
     }
 
     @Override
@@ -1731,21 +1879,29 @@
                 // Default result code (as passed to sendOrderedBroadcast)
                 // means that intent was not received by WfcSettings.
 
-                CharSequence title = intent.getCharSequenceExtra(EXTRA_KEY_ALERT_TITLE);
-                CharSequence messageAlert = intent.getCharSequenceExtra(EXTRA_KEY_ALERT_MESSAGE);
-                CharSequence messageNotification = intent.getCharSequenceExtra(EXTRA_KEY_NOTIFICATION_MESSAGE);
+                CharSequence title =
+                        intent.getCharSequenceExtra(EXTRA_WFC_REGISTRATION_FAILURE_TITLE);
+                CharSequence messageAlert =
+                        intent.getCharSequenceExtra(EXTRA_WFC_REGISTRATION_FAILURE_MESSAGE);
+                CharSequence messageNotification =
+                        intent.getCharSequenceExtra(EXTRA_KEY_NOTIFICATION_MESSAGE);
 
                 Intent resultIntent = new Intent(Intent.ACTION_MAIN);
+                // Note: If the classname below is ever removed, the call to
+                // PendingIntent.getActivity should also specify FLAG_IMMUTABLE to ensure the
+                // pending intent cannot be tampered with.
                 resultIntent.setClassName("com.android.settings",
                         "com.android.settings.Settings$WifiCallingSettingsActivity");
                 resultIntent.putExtra(EXTRA_KEY_ALERT_SHOW, true);
-                resultIntent.putExtra(EXTRA_KEY_ALERT_TITLE, title);
-                resultIntent.putExtra(EXTRA_KEY_ALERT_MESSAGE, messageAlert);
+                resultIntent.putExtra(EXTRA_WFC_REGISTRATION_FAILURE_TITLE, title);
+                resultIntent.putExtra(EXTRA_WFC_REGISTRATION_FAILURE_MESSAGE, messageAlert);
                 PendingIntent resultPendingIntent =
                         PendingIntent.getActivity(
                                 mContext,
                                 0,
                                 resultIntent,
+                                // Note: Since resultIntent above specifies an explicit class name
+                                // we do not need to specify PendingIntent.FLAG_IMMUTABLE here.
                                 PendingIntent.FLAG_UPDATE_CURRENT
                         );
 
@@ -1865,9 +2021,10 @@
 
             // If WfcSettings are active then alert will be shown
             // otherwise notification will be added.
-            Intent intent = new Intent(ImsManager.ACTION_IMS_REGISTRATION_ERROR);
-            intent.putExtra(EXTRA_KEY_ALERT_TITLE, title);
-            intent.putExtra(EXTRA_KEY_ALERT_MESSAGE, messageAlert);
+            Intent intent = new Intent(
+                    android.telephony.ims.ImsManager.ACTION_WFC_IMS_REGISTRATION_ERROR);
+            intent.putExtra(EXTRA_WFC_REGISTRATION_FAILURE_TITLE, title);
+            intent.putExtra(EXTRA_WFC_REGISTRATION_FAILURE_MESSAGE, messageAlert);
             intent.putExtra(EXTRA_KEY_NOTIFICATION_MESSAGE, messageNotification);
             mContext.sendOrderedBroadcast(intent, null, mResultReceiver,
                     null, Activity.RESULT_OK, null, null);
@@ -1899,11 +2056,6 @@
         return mWakeLock;
     }
 
-    @Override
-    public NetworkStats getVtDataUsage(boolean perUidStats) {
-        return mCT.getVtDataUsage(perUidStats);
-    }
-
     /**
      * Update roaming state and WFC mode in the following situations:
      *     1) voice is in service.
@@ -1920,8 +2072,8 @@
         if (mRoaming == newRoamingState) {
             return;
         }
-        boolean isInService = (ss.getVoiceRegState() == ServiceState.STATE_IN_SERVICE
-                || ss.getDataRegState() == ServiceState.STATE_IN_SERVICE);
+        boolean isInService = (ss.getState() == ServiceState.STATE_IN_SERVICE
+                || ss.getDataRegistrationState() == ServiceState.STATE_IN_SERVICE);
         // If we are not IN_SERVICE for voice or data, ignore change roaming state, as we always
         // move to home in this case.
         if (!isInService) {
@@ -1940,8 +2092,15 @@
         if (mCT.getState() == PhoneConstants.State.IDLE) {
             if (DBG) logd("updateRoamingState now: " + newRoamingState);
             mRoaming = newRoamingState;
-            ImsManager imsManager = ImsManager.getInstance(mContext, mPhoneId);
-            imsManager.setWfcMode(imsManager.getWfcMode(newRoamingState), newRoamingState);
+            CarrierConfigManager configManager = (CarrierConfigManager)
+                    getContext().getSystemService(Context.CARRIER_CONFIG_SERVICE);
+            // Don't set wfc mode if carrierconfig has not loaded. It will be set by GsmCdmaPhone
+            // when receives ACTION_CARRIER_CONFIG_CHANGED broadcast.
+            if (configManager != null && CarrierConfigManager.isConfigForIdentifiedCarrier(
+                    configManager.getConfigForSubId(getSubId()))) {
+                ImsManager imsManager = ImsManager.getInstance(mContext, mPhoneId);
+                imsManager.setWfcMode(imsManager.getWfcMode(newRoamingState), newRoamingState);
+            }
         } else {
             if (DBG) logd("updateRoamingState postponed: " + newRoamingState);
             mCT.registerForVoiceCallEnded(this, EVENT_VOICE_CALL_ENDED, null);
@@ -1972,8 +2131,103 @@
                 && psInfo.getAccessNetworkTechnology() == TelephonyManager.NETWORK_TYPE_IWLAN;
     }
 
+    public RegistrationManager.RegistrationCallback getImsMmTelRegistrationCallback() {
+        return mImsMmTelRegistrationHelper.getCallback();
+    }
+
+    /**
+     * Reset the IMS registration state.
+     */
+    public void resetImsRegistrationState() {
+        if (DBG) logd("resetImsRegistrationState");
+        mImsMmTelRegistrationHelper.reset();
+    }
+
+    private ImsRegistrationCallbackHelper.ImsRegistrationUpdate mMmTelRegistrationUpdate = new
+            ImsRegistrationCallbackHelper.ImsRegistrationUpdate() {
+        @Override
+        public void handleImsRegistered(int imsRadioTech) {
+            if (DBG) {
+                logd("onImsMmTelConnected imsRadioTech="
+                        + AccessNetworkConstants.transportTypeToString(imsRadioTech));
+            }
+            mRegLocalLog.log("onImsMmTelConnected imsRadioTech="
+                    + AccessNetworkConstants.transportTypeToString(imsRadioTech));
+            setServiceState(ServiceState.STATE_IN_SERVICE);
+            mMetrics.writeOnImsConnectionState(mPhoneId, ImsConnectionState.State.CONNECTED, null);
+        }
+
+        @Override
+        public void handleImsRegistering(int imsRadioTech) {
+            if (DBG) {
+                logd("onImsMmTelProgressing imsRadioTech="
+                        + AccessNetworkConstants.transportTypeToString(imsRadioTech));
+            }
+            mRegLocalLog.log("onImsMmTelProgressing imsRadioTech="
+                    + AccessNetworkConstants.transportTypeToString(imsRadioTech));
+            setServiceState(ServiceState.STATE_OUT_OF_SERVICE);
+            mMetrics.writeOnImsConnectionState(mPhoneId, ImsConnectionState.State.PROGRESSING,
+                    null);
+        }
+
+        @Override
+        public void handleImsUnregistered(ImsReasonInfo imsReasonInfo) {
+            if (DBG) logd("onImsMmTelDisconnected imsReasonInfo=" + imsReasonInfo);
+            mRegLocalLog.log("onImsMmTelDisconnected imsRadioTech=" + imsReasonInfo);
+            setServiceState(ServiceState.STATE_OUT_OF_SERVICE);
+            processDisconnectReason(imsReasonInfo);
+            mMetrics.writeOnImsConnectionState(mPhoneId, ImsConnectionState.State.DISCONNECTED,
+                    imsReasonInfo);
+        }
+
+        @Override
+        public void handleImsSubscriberAssociatedUriChanged(Uri[] uris) {
+            if (DBG) logd("handleImsSubscriberAssociatedUriChanged");
+            setCurrentSubscriberUris(uris);
+        }
+    };
+
+    public IccRecords getIccRecords() {
+        return mDefaultPhone.getIccRecords();
+    }
+
+    public DialArgs updateDialArgsForVolteSilentRedial(DialArgs dialArgs, int causeCode) {
+        if (dialArgs != null) {
+            ImsPhone.ImsDialArgs.Builder imsDialArgsBuilder;
+            if (dialArgs instanceof ImsPhone.ImsDialArgs) {
+                imsDialArgsBuilder = ImsPhone.ImsDialArgs.Builder
+                                      .from((ImsPhone.ImsDialArgs) dialArgs);
+            } else {
+                imsDialArgsBuilder = ImsPhone.ImsDialArgs.Builder
+                                      .from(dialArgs);
+            }
+            Bundle extras = new Bundle(dialArgs.intentExtras);
+            if (causeCode == CallFailCause.EMC_REDIAL_ON_VOWIFI && isWifiCallingEnabled()) {
+                extras.putString(ImsCallProfile.EXTRA_CALL_RAT_TYPE,
+                        String.valueOf(ServiceState.RIL_RADIO_TECHNOLOGY_IWLAN));
+                logd("trigger VoWifi emergency call");
+                imsDialArgsBuilder.setIntentExtras(extras);
+            } else if (causeCode == CallFailCause.EMC_REDIAL_ON_IMS) {
+                logd("trigger VoLte emergency call");
+            }
+            return imsDialArgsBuilder.build();
+        }
+        return new DialArgs.Builder<>().build();
+    }
+
     @Override
-    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+    public VoiceCallSessionStats getVoiceCallSessionStats() {
+        return mDefaultPhone.getVoiceCallSessionStats();
+    }
+
+    public boolean hasAliveCall() {
+        return (getForegroundCall().getState() != Call.State.IDLE ||
+                getBackgroundCall().getState() != Call.State.IDLE);
+    }
+
+    @Override
+    public void dump(FileDescriptor fd, PrintWriter printWriter, String[] args) {
+        IndentingPrintWriter pw = new IndentingPrintWriter(printWriter, "  ");
         pw.println("ImsPhone extends:");
         super.dump(fd, pw, args);
         pw.flush();
@@ -1987,9 +2241,14 @@
         pw.println("  mIsPhoneInEcmState = " + isInEcm());
         pw.println("  mEcmExitRespRegistrant = " + mEcmExitRespRegistrant);
         pw.println("  mSilentRedialRegistrants = " + mSilentRedialRegistrants);
-        pw.println("  mImsRegistered = " + mImsRegistered);
+        pw.println("  mImsMmTelRegistrationState = "
+                + mImsMmTelRegistrationHelper.getImsRegistrationState());
         pw.println("  mRoaming = " + mRoaming);
         pw.println("  mSsnRegistrants = " + mSsnRegistrants);
+        pw.println(" Registration Log:");
+        pw.increaseIndent();
+        mRegLocalLog.dump(pw);
+        pw.decreaseIndent();
         pw.flush();
     }
 
diff --git a/src/java/com/android/internal/telephony/imsphone/ImsPhoneBase.java b/src/java/com/android/internal/telephony/imsphone/ImsPhoneBase.java
index 5e670f4..ceacb15 100644
--- a/src/java/com/android/internal/telephony/imsphone/ImsPhoneBase.java
+++ b/src/java/com/android/internal/telephony/imsphone/ImsPhoneBase.java
@@ -22,10 +22,9 @@
 import android.os.Handler;
 import android.os.Message;
 import android.os.RegistrantList;
-import android.os.SystemProperties;
+import android.sysprop.TelephonyProperties;
 import android.telephony.CallQuality;
 import android.telephony.NetworkScanRequest;
-import android.telephony.Rlog;
 import android.telephony.ServiceState;
 import android.telephony.SignalStrength;
 import android.telephony.ims.ImsReasonInfo;
@@ -41,9 +40,9 @@
 import com.android.internal.telephony.Phone;
 import com.android.internal.telephony.PhoneConstants;
 import com.android.internal.telephony.PhoneNotifier;
-import com.android.internal.telephony.TelephonyProperties;
 import com.android.internal.telephony.dataconnection.DataConnection;
 import com.android.internal.telephony.uicc.IccFileHandler;
+import com.android.telephony.Rlog;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -169,11 +168,6 @@
     }
 
     @Override
-    public boolean getCallForwardingIndicator() {
-        return false;
-    }
-
-    @Override
     public List<? extends MmiCode> getPendingMmiCodes() {
         return new ArrayList<MmiCode>(0);
     }
@@ -237,10 +231,9 @@
         Rlog.v(LOG_TAG, "canDial(): serviceState = " + serviceState);
         if (serviceState == ServiceState.STATE_POWER_OFF) return false;
 
-        String disableCall = SystemProperties.get(
-                TelephonyProperties.PROPERTY_DISABLE_CALL, "false");
+        boolean disableCall = TelephonyProperties.disable_call().orElse(false);
         Rlog.v(LOG_TAG, "canDial(): disableCall = " + disableCall);
-        if (disableCall.equals("true")) return false;
+        if (disableCall) return false;
 
         Rlog.v(LOG_TAG, "canDial(): ringingCall: " + getRingingCall().getState());
         Rlog.v(LOG_TAG, "canDial(): foregndCall: " + getForegroundCall().getState());
@@ -344,11 +337,6 @@
     }
 
     @Override
-    public String getLine1Number() {
-        return null;
-    }
-
-    @Override
     public String getLine1AlphaTag() {
         return null;
     }
@@ -372,12 +360,23 @@
     }
 
     @Override
+    public void getCallForwardingOption(int commandInterfaceCFReason, int serviceClass,
+            Message onComplete) {
+    }
+
+    @Override
     public void setCallForwardingOption(int commandInterfaceCFAction,
             int commandInterfaceCFReason, String dialingNumber,
             int timerSeconds, Message onComplete) {
     }
 
     @Override
+    public void setCallForwardingOption(int commandInterfaceCFAction,
+            int commandInterfaceCFReason, String dialingNumber, int serviceClass,
+            int timerSeconds, Message onComplete) {
+    }
+
+    @Override
     public void getOutgoingCallerIdDisplay(Message onComplete) {
         // FIXME: what to reply?
         AsyncResult.forMessage(onComplete, null, null);
diff --git a/src/java/com/android/internal/telephony/imsphone/ImsPhoneCall.java b/src/java/com/android/internal/telephony/imsphone/ImsPhoneCall.java
index f45bdfe..9df7215 100644
--- a/src/java/com/android/internal/telephony/imsphone/ImsPhoneCall.java
+++ b/src/java/com/android/internal/telephony/imsphone/ImsPhoneCall.java
@@ -16,21 +16,22 @@
 
 package com.android.internal.telephony.imsphone;
 
-import android.annotation.UnsupportedAppUsage;
-import android.telecom.ConferenceParticipant;
-import android.telephony.Rlog;
+import android.compat.annotation.UnsupportedAppUsage;
 import android.telephony.DisconnectCause;
+import android.telephony.ims.ImsStreamMediaProfile;
 import android.util.Log;
 
+import com.android.ims.ImsCall;
+import com.android.ims.ImsException;
+import com.android.ims.internal.ConferenceParticipant;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.telephony.Call;
 import com.android.internal.telephony.CallStateException;
 import com.android.internal.telephony.Connection;
 import com.android.internal.telephony.Phone;
-import com.android.ims.ImsCall;
-import com.android.ims.ImsException;
-import android.telephony.ims.ImsStreamMediaProfile;
+import com.android.telephony.Rlog;
 
+import java.util.ArrayList;
 import java.util.List;
 
 /**
@@ -79,9 +80,9 @@
             //Rlog.e(LOG_TAG, "dispose: unexpected error on hangup", ex);
             //while disposing, ignore the exception and clean the connections
         } finally {
-            for(int i = 0, s = mConnections.size(); i < s; i++) {
-                ImsPhoneConnection c = (ImsPhoneConnection) mConnections.get(i);
-                c.onDisconnect(DisconnectCause.LOST_SIGNAL);
+            List<Connection> connections = getConnections();
+            for (Connection conn : connections) {
+                conn.onDisconnect(DisconnectCause.LOST_SIGNAL);
             }
         }
     }
@@ -90,9 +91,8 @@
 
     @UnsupportedAppUsage
     @Override
-    public List<Connection>
-    getConnections() {
-        return mConnections;
+    public ArrayList<Connection> getConnections() {
+        return super.getConnections();
     }
 
     @Override
@@ -123,17 +123,24 @@
     }
 
     @Override
+    public void hangup(@android.telecom.Call.RejectReason int rejectReason)
+            throws CallStateException {
+        mOwner.hangup(this, rejectReason);
+    }
+
+    @Override
     public String toString() {
         StringBuilder sb = new StringBuilder();
+        List<Connection> connections = getConnections();
         sb.append("[ImsPhoneCall ");
         sb.append(mCallContext);
         sb.append(" state: ");
         sb.append(mState.toString());
         sb.append(" ");
-        if (mConnections.size() > 1) {
+        if (connections.size() > 1) {
             sb.append(" ERROR_MULTIPLE ");
         }
-        for (Connection conn : mConnections) {
+        for (Connection conn : connections) {
             sb.append(conn);
             sb.append(" ");
         }
@@ -144,6 +151,9 @@
 
     @Override
     public List<ConferenceParticipant> getConferenceParticipants() {
+         if (!mOwner.isConferenceEventPackageEnabled()) {
+             return null;
+         }
          ImsCall call = getImsCall();
          if (call == null) {
              return null;
@@ -158,7 +168,7 @@
             Rlog.v(LOG_TAG, "attach : " + mCallContext + " conn = " + conn);
         }
         clearDisconnected();
-        mConnections.add(conn);
+        addConnection(conn);
 
         mOwner.logState();
     }
@@ -187,15 +197,18 @@
 
             boolean hasOnlyDisconnectedConnections = true;
 
-            for (int i = 0, s = mConnections.size()  ; i < s; i ++) {
-                if (mConnections.get(i).getState() != State.DISCONNECTED) {
+            ArrayList<Connection> connections = getConnections();
+            for (Connection cn : connections) {
+                if (cn.getState() != State.DISCONNECTED) {
                     hasOnlyDisconnectedConnections = false;
                     break;
                 }
             }
 
             if (hasOnlyDisconnectedConnections) {
-                mState = State.DISCONNECTED;
+                synchronized(this) {
+                    mState = State.DISCONNECTED;
+                }
                 if (VDBG) {
                     Rlog.v(LOG_TAG, "connectionDisconnected : " + mCallContext + " state = " +
                             mState);
@@ -211,7 +224,7 @@
         if (VDBG) {
             Rlog.v(LOG_TAG, "detach : " + mCallContext + " conn = " + conn);
         }
-        mConnections.remove(conn);
+        removeConnection(conn);
         clearDisconnected();
 
         mOwner.logState();
@@ -223,7 +236,7 @@
      */
     /*package*/ boolean
     isFull() {
-        return mConnections.size() == ImsPhoneCallTracker.MAX_CONNECTIONS_PER_CALL;
+        return getConnectionsCount() == ImsPhoneCallTracker.MAX_CONNECTIONS_PER_CALL;
     }
 
     //***** Called from ImsPhoneCallTracker
@@ -231,13 +244,18 @@
      * Called when this Call is being hung up locally (eg, user pressed "end")
      */
     @UnsupportedAppUsage
-    void
-    onHangupLocal() {
-        for (int i = 0, s = mConnections.size(); i < s; i++) {
-            ImsPhoneConnection cn = (ImsPhoneConnection)mConnections.get(i);
-            cn.onHangupLocal();
+    @VisibleForTesting
+    public void onHangupLocal() {
+        ArrayList<Connection> connections = getConnections();
+        for (Connection conn : connections) {
+            ImsPhoneConnection imsConn = (ImsPhoneConnection) conn;
+            imsConn.onHangupLocal();
         }
-        mState = State.DISCONNECTING;
+        synchronized(this) {
+            if (mState.isAlive()) {
+                mState = State.DISCONNECTING;
+            }
+        }
         if (VDBG) {
             Rlog.v(LOG_TAG, "onHangupLocal : " + mCallContext + " state = " + mState);
         }
@@ -245,15 +263,20 @@
 
     @VisibleForTesting
     public ImsPhoneConnection getFirstConnection() {
-        if (mConnections.size() == 0) return null;
+        List<Connection> connections = getConnections();
+        if (connections.size() == 0) return null;
 
-        return (ImsPhoneConnection) mConnections.get(0);
+        return (ImsPhoneConnection) connections.get(0);
     }
 
-    /*package*/ void
-    setMute(boolean mute) {
-        ImsCall imsCall = getFirstConnection() == null ?
-                null : getFirstConnection().getImsCall();
+    /**
+     * Sets the mute state of the call.
+     * @param mute {@code true} if the call could be muted; {@code false} otherwise.
+     */
+    @VisibleForTesting
+    public void setMute(boolean mute) {
+        ImsPhoneConnection connection = getFirstConnection();
+        ImsCall imsCall = connection == null ? null : connection.getImsCall();
         if (imsCall != null) {
             try {
                 imsCall.setMute(mute);
@@ -297,9 +320,10 @@
      * @return The {@link ImsCall}.
      */
     @VisibleForTesting
-    public ImsCall
-    getImsCall() {
-        return (getFirstConnection() == null) ? null : getFirstConnection().getImsCall();
+    @UnsupportedAppUsage
+    public ImsCall getImsCall() {
+        ImsPhoneConnection connection = getFirstConnection();
+        return (connection == null) ? null : connection.getImsCall();
     }
 
     /*package*/ static boolean isLocalTone(ImsCall imsCall) {
@@ -378,9 +402,9 @@
     }
 
     private void takeOver(ImsPhoneCall that) {
-        mConnections = that.mConnections;
+        copyConnectionFrom(that);
         mState = that.mState;
-        for (Connection c : mConnections) {
+        for (Connection c : getConnections()) {
             ((ImsPhoneConnection) c).changeParent(this);
         }
     }
diff --git a/src/java/com/android/internal/telephony/imsphone/ImsPhoneCallTracker.java b/src/java/com/android/internal/telephony/imsphone/ImsPhoneCallTracker.java
old mode 100644
new mode 100755
index 718a18b..c162b7a
--- a/src/java/com/android/internal/telephony/imsphone/ImsPhoneCallTracker.java
+++ b/src/java/com/android/internal/telephony/imsphone/ImsPhoneCallTracker.java
@@ -16,10 +16,12 @@
 
 package com.android.internal.telephony.imsphone;
 
+import static com.android.internal.annotations.VisibleForTesting.Visibility.PRIVATE;
 import static com.android.internal.telephony.Phone.CS_FALLBACK;
 
 import android.annotation.NonNull;
-import android.annotation.UnsupportedAppUsage;
+import android.app.usage.NetworkStatsManager;
+import android.compat.annotation.UnsupportedAppUsage;
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
@@ -32,7 +34,7 @@
 import android.net.NetworkInfo;
 import android.net.NetworkRequest;
 import android.net.NetworkStats;
-import android.net.Uri;
+import android.net.netstats.provider.NetworkStatsProvider;
 import android.os.AsyncResult;
 import android.os.Bundle;
 import android.os.Handler;
@@ -42,23 +44,23 @@
 import android.os.RegistrantList;
 import android.os.RemoteException;
 import android.os.SystemClock;
-import android.os.SystemProperties;
 import android.preference.PreferenceManager;
 import android.provider.Settings;
-import android.telecom.ConferenceParticipant;
+import android.sysprop.TelephonyProperties;
+import android.telecom.Connection.VideoProvider;
 import android.telecom.TelecomManager;
 import android.telecom.VideoProfile;
 import android.telephony.CallQuality;
 import android.telephony.CarrierConfigManager;
 import android.telephony.DisconnectCause;
 import android.telephony.PhoneNumberUtils;
-import android.telephony.Rlog;
 import android.telephony.ServiceState;
 import android.telephony.SubscriptionInfo;
 import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyManager;
 import android.telephony.emergency.EmergencyNumber;
 import android.telephony.ims.ImsCallProfile;
+import android.telephony.ims.ImsConferenceState;
 import android.telephony.ims.ImsMmTelManager;
 import android.telephony.ims.ImsReasonInfo;
 import android.telephony.ims.ImsStreamMediaProfile;
@@ -69,9 +71,12 @@
 import android.telephony.ims.stub.ImsRegistrationImplBase;
 import android.text.TextUtils;
 import android.util.ArrayMap;
+import android.util.LocalLog;
 import android.util.Log;
 import android.util.Pair;
+import android.util.SparseIntArray;
 
+import com.android.ims.FeatureConnector;
 import com.android.ims.ImsCall;
 import com.android.ims.ImsConfig;
 import com.android.ims.ImsConfigListener;
@@ -80,6 +85,7 @@
 import com.android.ims.ImsManager;
 import com.android.ims.ImsMultiEndpoint;
 import com.android.ims.ImsUtInterface;
+import com.android.ims.internal.ConferenceParticipant;
 import com.android.ims.internal.IImsCallSession;
 import com.android.ims.internal.IImsVideoCallProvider;
 import com.android.ims.internal.ImsVideoCallProviderWrapper;
@@ -87,6 +93,7 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.os.SomeArgs;
 import com.android.internal.telephony.Call;
+import com.android.internal.telephony.CallFailCause;
 import com.android.internal.telephony.CallStateException;
 import com.android.internal.telephony.CallTracker;
 import com.android.internal.telephony.CommandException;
@@ -97,17 +104,17 @@
 import com.android.internal.telephony.PhoneConstants;
 import com.android.internal.telephony.ServiceStateTracker;
 import com.android.internal.telephony.SubscriptionController;
-import com.android.internal.telephony.TelephonyProperties;
 import com.android.internal.telephony.dataconnection.DataEnabledSettings;
 import com.android.internal.telephony.dataconnection.DataEnabledSettings.DataEnabledChangedReason;
+import com.android.internal.telephony.emergency.EmergencyNumberTracker;
 import com.android.internal.telephony.gsm.SuppServiceNotification;
 import com.android.internal.telephony.imsphone.ImsPhone.ImsDialArgs;
 import com.android.internal.telephony.metrics.CallQualityMetrics;
 import com.android.internal.telephony.metrics.TelephonyMetrics;
-import com.android.internal.telephony.nano.TelephonyProto.ImsConnectionState;
 import com.android.internal.telephony.nano.TelephonyProto.TelephonyCallSession;
 import com.android.internal.telephony.nano.TelephonyProto.TelephonyCallSession.Event.ImsCommand;
-import com.android.server.net.NetworkStatsService;
+import com.android.internal.util.IndentingPrintWriter;
+import com.android.telephony.Rlog;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
@@ -121,6 +128,7 @@
 import java.util.concurrent.Executor;
 import java.util.concurrent.LinkedBlockingQueue;
 import java.util.concurrent.atomic.AtomicInteger;
+import java.util.function.Consumer;
 import java.util.regex.Pattern;
 
 /**
@@ -165,22 +173,28 @@
         @Override
         public void onIncomingCall(IImsCallSession c, Bundle extras) {
             if (DBG) log("onReceive : incoming call intent");
+            mOperationLocalLog.log("onIncomingCall Received");
 
+            if (extras == null) extras = new Bundle();
             if (mImsManager == null) return;
 
             try {
                 // Network initiated USSD will be treated by mImsUssdListener
-                boolean isUssd = extras.getBoolean(ImsManager.EXTRA_USSD, false);
+                boolean isUssd = extras.getBoolean(MmTelFeature.EXTRA_IS_USSD, false);
+                // For compatibility purposes with older vendor implmentations.
+                isUssd |= extras.getBoolean(ImsManager.EXTRA_USSD, false);
                 if (isUssd) {
                     if (DBG) log("onReceive : USSD");
-                    mUssdSession = mImsManager.takeCall(c, extras, mImsUssdListener);
+                    mUssdSession = mImsManager.takeCall(c, mImsUssdListener);
                     if (mUssdSession != null) {
                         mUssdSession.accept(ImsCallProfile.CALL_TYPE_VOICE);
                     }
                     return;
                 }
 
-                boolean isUnknown = extras.getBoolean(ImsManager.EXTRA_IS_UNKNOWN_CALL, false);
+                boolean isUnknown = extras.getBoolean(MmTelFeature.EXTRA_IS_UNKNOWN_CALL, false);
+                // For compatibility purposes with older vendor implmentations.
+                isUnknown |= extras.getBoolean(ImsManager.EXTRA_IS_UNKNOWN_CALL, false);
                 if (DBG) {
                     log("onReceive : isUnknown = " + isUnknown
                             + " fg = " + mForegroundCall.getState()
@@ -188,7 +202,7 @@
                 }
 
                 // Normal MT/Unknown call
-                ImsCall imsCall = mImsManager.takeCall(c, extras, mImsCallListener);
+                ImsCall imsCall = mImsManager.takeCall(c, mImsCallListener);
                 ImsPhoneConnection conn = new ImsPhoneConnection(mPhone, imsCall,
                         ImsPhoneCallTracker.this,
                         (isUnknown ? mForegroundCall : mRingingCall), isUnknown);
@@ -206,12 +220,36 @@
                     }
                 }
                 conn.setAllowAddCallDuringVideoCall(mAllowAddCallDuringVideoCall);
+                conn.setAllowHoldingVideoCall(mAllowHoldingVideoCall);
+
+                if ((c != null) && (c.getCallProfile() != null)
+                        && (c.getCallProfile().getCallExtras() != null)
+                        && (c.getCallProfile().getCallExtras()
+                          .containsKey(ImsCallProfile.EXTRA_CALL_DISCONNECT_CAUSE))) {
+                    String error = c.getCallProfile()
+                            .getCallExtra(ImsCallProfile.EXTRA_CALL_DISCONNECT_CAUSE, null);
+                    if (error != null) {
+                        try {
+                            int cause = getDisconnectCauseFromReasonInfo(
+                                        new ImsReasonInfo(Integer.parseInt(error), 0, null),
+                                    conn.getState());
+                            if (cause == DisconnectCause.INCOMING_AUTO_REJECTED) {
+                                conn.setDisconnectCause(cause);
+                                if (DBG) log("onIncomingCall : incoming call auto rejected");
+                            }
+                        } catch (NumberFormatException e) {
+                            Rlog.e(LOG_TAG, "Exception in parsing Integer Data: " + e);
+                        }
+                    }
+                }
+
                 addConnection(conn);
 
                 setVideoCallProvider(conn, imsCall);
 
                 TelephonyMetrics.getInstance().writeOnImsCallReceive(mPhone.getPhoneId(),
                         imsCall.getSession());
+                mPhone.getVoiceCallSessionStats().onImsCallReceived(conn);
 
                 if (isUnknown) {
                     mPhone.notifyUnknownConnection(conn);
@@ -244,6 +282,48 @@
         }
     }
 
+    /**
+     * A class implementing {@link NetworkStatsProvider} to report VT data usage to system.
+     */
+    // TODO: Directly reports diff in updateVtDataUsage.
+    @VisibleForTesting(visibility = PRIVATE)
+    public class VtDataUsageProvider extends NetworkStatsProvider {
+        private int mToken = 0;
+        private NetworkStats mIfaceSnapshot = new NetworkStats(0L, 0);
+        private NetworkStats mUidSnapshot = new NetworkStats(0L, 0);
+        @Override
+        public void onRequestStatsUpdate(int token) {
+            // If there is an ongoing VT call, request the latest VT usage from the modem. The
+            // latest usage will return asynchronously so it won't be counted in this round, but it
+            // will be eventually counted when next requestStatsUpdate is called.
+            if (mState != PhoneConstants.State.IDLE) {
+                for (ImsPhoneConnection conn : mConnections) {
+                    final VideoProvider videoProvider = conn.getVideoProvider();
+                    if (videoProvider != null) {
+                        videoProvider.onRequestConnectionDataUsage();
+                    }
+                }
+            }
+
+            final NetworkStats ifaceDiff = mVtDataUsageSnapshot.subtract(mIfaceSnapshot);
+            final NetworkStats uidDiff = mVtDataUsageUidSnapshot.subtract(mUidSnapshot);
+            mVtDataUsageProvider.notifyStatsUpdated(mToken, ifaceDiff, uidDiff);
+            mIfaceSnapshot = mIfaceSnapshot.add(ifaceDiff);
+            mUidSnapshot = mUidSnapshot.add(uidDiff);
+            mToken = token;
+        }
+
+        @Override
+        public void onSetLimit(String iface, long quotaBytes) {
+            // No-op
+        }
+
+        @Override
+        public void onSetAlert(long quotaBytes) {
+            // No-op
+        }
+    }
+
     private BroadcastReceiver mReceiver = new BroadcastReceiver() {
         @Override
         public void onReceive(Context context, Intent intent) {
@@ -269,6 +349,12 @@
     private boolean mIsMonitoringConnectivity = false;
 
     /**
+     * A test flag which can be used to disable processing of the conference event package data
+     * received from the network.
+     */
+    private boolean mIsConferenceEventPackageEnabled = true;
+
+    /**
      * Network callback used to schedule the handover check when a wireless network connects.
      */
     private ConnectivityManager.NetworkCallback mNetworkCallback =
@@ -373,6 +459,7 @@
 
     private volatile NetworkStats mVtDataUsageSnapshot = null;
     private volatile NetworkStats mVtDataUsageUidSnapshot = null;
+    private final VtDataUsageProvider mVtDataUsageProvider = new VtDataUsageProvider();
 
     private final AtomicInteger mDefaultDialerUid = new AtomicInteger(NetworkStats.UID_ALL);
 
@@ -382,6 +469,7 @@
     @UnsupportedAppUsage
     private Object mSyncHold = new Object();
 
+    @UnsupportedAppUsage
     private ImsCall mUssdSession = null;
     @UnsupportedAppUsage
     private Message mPendingUssd = null;
@@ -397,6 +485,7 @@
 
     private PhoneConstants.State mState = PhoneConstants.State.IDLE;
 
+    @UnsupportedAppUsage
     private ImsManager mImsManager;
     private ImsUtInterface mUtInterface;
 
@@ -411,6 +500,7 @@
     private boolean pendingCallInEcm = false;
     @UnsupportedAppUsage
     private boolean mSwitchingFgAndBgCalls = false;
+    @UnsupportedAppUsage
     private ImsCall mCallExpectedToResume = null;
     @UnsupportedAppUsage
     private boolean mAllowEmergencyVideoCalls = false;
@@ -418,6 +508,7 @@
     private boolean mIsViLteDataMetered = false;
     private boolean mAlwaysPlayRemoteHoldTone = false;
     private boolean mAutoRetryFailedWifiEmergencyCall = false;
+    private boolean mSupportCepOnPeer = true;
     // Tracks the state of our background/foreground calls while a call hold/swap operation is
     // in progress. Values listed above.
     private HoldSwapState mHoldSwitchingState = HoldSwapState.INACTIVE;
@@ -450,6 +541,12 @@
     private boolean mAllowAddCallDuringVideoCall = true;
 
     /**
+     * Carrier configuration option which determines whether holding a video call
+     * should be allowed.
+     */
+    private boolean mAllowHoldingVideoCall = true;
+
+    /**
      * Carrier configuration option which determines whether to notify the connection if a handover
      * to wifi fails.
      */
@@ -462,6 +559,237 @@
     private boolean mSupportDowngradeVtToAudio = false;
 
     /**
+     * Stores the mapping of {@code ImsReasonInfo#CODE_*} to {@code CallFailCause#*}
+     */
+    private static final SparseIntArray PRECISE_CAUSE_MAP = new SparseIntArray();
+    static {
+        PRECISE_CAUSE_MAP.append(ImsReasonInfo.CODE_LOCAL_ILLEGAL_ARGUMENT,
+                CallFailCause.LOCAL_ILLEGAL_ARGUMENT);
+        PRECISE_CAUSE_MAP.append(ImsReasonInfo.CODE_LOCAL_ILLEGAL_STATE,
+                CallFailCause.LOCAL_ILLEGAL_STATE);
+        PRECISE_CAUSE_MAP.append(ImsReasonInfo.CODE_LOCAL_INTERNAL_ERROR,
+                CallFailCause.LOCAL_INTERNAL_ERROR);
+        PRECISE_CAUSE_MAP.append(ImsReasonInfo.CODE_LOCAL_IMS_SERVICE_DOWN,
+                CallFailCause.LOCAL_IMS_SERVICE_DOWN);
+        PRECISE_CAUSE_MAP.append(ImsReasonInfo.CODE_LOCAL_NO_PENDING_CALL,
+                CallFailCause.LOCAL_NO_PENDING_CALL);
+        PRECISE_CAUSE_MAP.append(ImsReasonInfo.CODE_LOCAL_ENDED_BY_CONFERENCE_MERGE,
+                CallFailCause.NORMAL_CLEARING);
+        PRECISE_CAUSE_MAP.append(ImsReasonInfo.CODE_LOCAL_POWER_OFF,
+                CallFailCause.LOCAL_POWER_OFF);
+        PRECISE_CAUSE_MAP.append(ImsReasonInfo.CODE_LOCAL_LOW_BATTERY,
+                CallFailCause.LOCAL_LOW_BATTERY);
+        PRECISE_CAUSE_MAP.append(ImsReasonInfo.CODE_LOCAL_NETWORK_NO_SERVICE,
+                CallFailCause.LOCAL_NETWORK_NO_SERVICE);
+        PRECISE_CAUSE_MAP.append(ImsReasonInfo.CODE_LOCAL_NETWORK_NO_LTE_COVERAGE,
+                CallFailCause.LOCAL_NETWORK_NO_LTE_COVERAGE);
+        PRECISE_CAUSE_MAP.append(ImsReasonInfo.CODE_LOCAL_NETWORK_ROAMING,
+                CallFailCause.LOCAL_NETWORK_ROAMING);
+        PRECISE_CAUSE_MAP.append(ImsReasonInfo.CODE_LOCAL_NETWORK_IP_CHANGED,
+                CallFailCause.LOCAL_NETWORK_IP_CHANGED);
+        PRECISE_CAUSE_MAP.append(ImsReasonInfo.CODE_LOCAL_SERVICE_UNAVAILABLE,
+                CallFailCause.LOCAL_SERVICE_UNAVAILABLE);
+        PRECISE_CAUSE_MAP.append(ImsReasonInfo.CODE_LOCAL_NOT_REGISTERED,
+                CallFailCause.LOCAL_NOT_REGISTERED);
+        PRECISE_CAUSE_MAP.append(ImsReasonInfo.CODE_LOCAL_CALL_EXCEEDED,
+                CallFailCause.LOCAL_MAX_CALL_EXCEEDED);
+        PRECISE_CAUSE_MAP.append(ImsReasonInfo.CODE_LOCAL_CALL_DECLINE,
+                CallFailCause.LOCAL_CALL_DECLINE);
+        PRECISE_CAUSE_MAP.append(ImsReasonInfo.CODE_LOCAL_CALL_VCC_ON_PROGRESSING,
+                CallFailCause.LOCAL_CALL_VCC_ON_PROGRESSING);
+        PRECISE_CAUSE_MAP.append(ImsReasonInfo.CODE_LOCAL_CALL_RESOURCE_RESERVATION_FAILED,
+                CallFailCause.LOCAL_CALL_RESOURCE_RESERVATION_FAILED);
+        PRECISE_CAUSE_MAP.append(ImsReasonInfo.CODE_LOCAL_CALL_CS_RETRY_REQUIRED,
+                CallFailCause.LOCAL_CALL_CS_RETRY_REQUIRED);
+        PRECISE_CAUSE_MAP.append(ImsReasonInfo.CODE_LOCAL_CALL_VOLTE_RETRY_REQUIRED,
+                CallFailCause.LOCAL_CALL_VOLTE_RETRY_REQUIRED);
+        PRECISE_CAUSE_MAP.append(ImsReasonInfo.CODE_LOCAL_CALL_TERMINATED,
+                CallFailCause.LOCAL_CALL_TERMINATED);
+        PRECISE_CAUSE_MAP.append(ImsReasonInfo.CODE_LOCAL_HO_NOT_FEASIBLE,
+                CallFailCause.LOCAL_HO_NOT_FEASIBLE);
+        PRECISE_CAUSE_MAP.append(ImsReasonInfo.CODE_TIMEOUT_1XX_WAITING,
+                CallFailCause.TIMEOUT_1XX_WAITING);
+        PRECISE_CAUSE_MAP.append(ImsReasonInfo.CODE_TIMEOUT_NO_ANSWER,
+                CallFailCause.TIMEOUT_NO_ANSWER);
+        PRECISE_CAUSE_MAP.append(ImsReasonInfo.CODE_TIMEOUT_NO_ANSWER_CALL_UPDATE,
+                CallFailCause.TIMEOUT_NO_ANSWER_CALL_UPDATE);
+        PRECISE_CAUSE_MAP.append(ImsReasonInfo.CODE_FDN_BLOCKED,
+                CallFailCause.FDN_BLOCKED);
+        PRECISE_CAUSE_MAP.append(ImsReasonInfo.CODE_SIP_REDIRECTED,
+                CallFailCause.SIP_REDIRECTED);
+        PRECISE_CAUSE_MAP.append(ImsReasonInfo.CODE_SIP_BAD_REQUEST,
+                CallFailCause.SIP_BAD_REQUEST);
+        PRECISE_CAUSE_MAP.append(ImsReasonInfo.CODE_SIP_FORBIDDEN,
+                CallFailCause.SIP_FORBIDDEN);
+        PRECISE_CAUSE_MAP.append(ImsReasonInfo.CODE_SIP_NOT_FOUND,
+                CallFailCause.SIP_NOT_FOUND);
+        PRECISE_CAUSE_MAP.append(ImsReasonInfo.CODE_SIP_NOT_SUPPORTED,
+                CallFailCause.SIP_NOT_SUPPORTED);
+        PRECISE_CAUSE_MAP.append(ImsReasonInfo.CODE_SIP_REQUEST_TIMEOUT,
+                CallFailCause.SIP_REQUEST_TIMEOUT);
+        PRECISE_CAUSE_MAP.append(ImsReasonInfo.CODE_SIP_TEMPRARILY_UNAVAILABLE,
+                CallFailCause.SIP_TEMPRARILY_UNAVAILABLE);
+        PRECISE_CAUSE_MAP.append(ImsReasonInfo.CODE_SIP_BAD_ADDRESS,
+                CallFailCause.SIP_BAD_ADDRESS);
+        PRECISE_CAUSE_MAP.append(ImsReasonInfo.CODE_SIP_BUSY,
+                CallFailCause.SIP_BUSY);
+        PRECISE_CAUSE_MAP.append(ImsReasonInfo.CODE_SIP_REQUEST_CANCELLED,
+                CallFailCause.SIP_REQUEST_CANCELLED);
+        PRECISE_CAUSE_MAP.append(ImsReasonInfo.CODE_SIP_NOT_ACCEPTABLE,
+                CallFailCause.SIP_NOT_ACCEPTABLE);
+        PRECISE_CAUSE_MAP.append(ImsReasonInfo.CODE_SIP_NOT_REACHABLE,
+                CallFailCause.SIP_NOT_REACHABLE);
+        PRECISE_CAUSE_MAP.append(ImsReasonInfo.CODE_SIP_CLIENT_ERROR,
+                CallFailCause.SIP_CLIENT_ERROR);
+        PRECISE_CAUSE_MAP.append(ImsReasonInfo.CODE_SIP_TRANSACTION_DOES_NOT_EXIST,
+                CallFailCause.SIP_TRANSACTION_DOES_NOT_EXIST);
+        PRECISE_CAUSE_MAP.append(ImsReasonInfo.CODE_SIP_SERVER_INTERNAL_ERROR,
+                CallFailCause.SIP_SERVER_INTERNAL_ERROR);
+        PRECISE_CAUSE_MAP.append(ImsReasonInfo.CODE_SIP_SERVICE_UNAVAILABLE,
+                CallFailCause.SIP_SERVICE_UNAVAILABLE);
+        PRECISE_CAUSE_MAP.append(ImsReasonInfo.CODE_SIP_SERVER_TIMEOUT,
+                CallFailCause.SIP_SERVER_TIMEOUT);
+        PRECISE_CAUSE_MAP.append(ImsReasonInfo.CODE_SIP_SERVER_ERROR,
+                CallFailCause.SIP_SERVER_ERROR);
+        PRECISE_CAUSE_MAP.append(ImsReasonInfo.CODE_SIP_USER_REJECTED,
+                CallFailCause.SIP_USER_REJECTED);
+        PRECISE_CAUSE_MAP.append(ImsReasonInfo.CODE_SIP_GLOBAL_ERROR,
+                CallFailCause.SIP_GLOBAL_ERROR);
+        PRECISE_CAUSE_MAP.append(ImsReasonInfo.CODE_EMERGENCY_TEMP_FAILURE,
+                CallFailCause.IMS_EMERGENCY_TEMP_FAILURE);
+        PRECISE_CAUSE_MAP.append(ImsReasonInfo.CODE_EMERGENCY_PERM_FAILURE,
+                CallFailCause.IMS_EMERGENCY_PERM_FAILURE);
+        PRECISE_CAUSE_MAP.append(ImsReasonInfo.CODE_MEDIA_INIT_FAILED,
+                CallFailCause.MEDIA_INIT_FAILED);
+        PRECISE_CAUSE_MAP.append(ImsReasonInfo.CODE_MEDIA_NO_DATA,
+                CallFailCause.MEDIA_NO_DATA);
+        PRECISE_CAUSE_MAP.append(ImsReasonInfo.CODE_MEDIA_NOT_ACCEPTABLE,
+                CallFailCause.MEDIA_NOT_ACCEPTABLE);
+        PRECISE_CAUSE_MAP.append(ImsReasonInfo.CODE_MEDIA_UNSPECIFIED,
+                CallFailCause.MEDIA_UNSPECIFIED);
+        PRECISE_CAUSE_MAP.append(ImsReasonInfo.CODE_USER_TERMINATED,
+                CallFailCause.USER_TERMINATED);
+        PRECISE_CAUSE_MAP.append(ImsReasonInfo.CODE_USER_NOANSWER,
+                CallFailCause.USER_NOANSWER);
+        PRECISE_CAUSE_MAP.append(ImsReasonInfo.CODE_USER_IGNORE,
+                CallFailCause.USER_IGNORE);
+        PRECISE_CAUSE_MAP.append(ImsReasonInfo.CODE_USER_DECLINE,
+                CallFailCause.USER_DECLINE);
+        PRECISE_CAUSE_MAP.append(ImsReasonInfo.CODE_LOW_BATTERY,
+                CallFailCause.LOW_BATTERY);
+        PRECISE_CAUSE_MAP.append(ImsReasonInfo.CODE_BLACKLISTED_CALL_ID,
+                CallFailCause.BLACKLISTED_CALL_ID);
+        PRECISE_CAUSE_MAP.append(ImsReasonInfo.CODE_USER_TERMINATED_BY_REMOTE,
+                CallFailCause.USER_TERMINATED_BY_REMOTE);
+        PRECISE_CAUSE_MAP.append(ImsReasonInfo.CODE_UT_NOT_SUPPORTED,
+                CallFailCause.UT_NOT_SUPPORTED);
+        PRECISE_CAUSE_MAP.append(ImsReasonInfo.CODE_UT_SERVICE_UNAVAILABLE,
+                CallFailCause.UT_SERVICE_UNAVAILABLE);
+        PRECISE_CAUSE_MAP.append(ImsReasonInfo.CODE_UT_OPERATION_NOT_ALLOWED,
+                CallFailCause.UT_OPERATION_NOT_ALLOWED);
+        PRECISE_CAUSE_MAP.append(ImsReasonInfo.CODE_UT_NETWORK_ERROR,
+                CallFailCause.UT_NETWORK_ERROR);
+        PRECISE_CAUSE_MAP.append(ImsReasonInfo.CODE_UT_CB_PASSWORD_MISMATCH,
+                CallFailCause.UT_CB_PASSWORD_MISMATCH);
+        PRECISE_CAUSE_MAP.append(ImsReasonInfo.CODE_ECBM_NOT_SUPPORTED,
+                CallFailCause.ECBM_NOT_SUPPORTED);
+        PRECISE_CAUSE_MAP.append(ImsReasonInfo.CODE_MULTIENDPOINT_NOT_SUPPORTED,
+                CallFailCause.MULTIENDPOINT_NOT_SUPPORTED);
+        PRECISE_CAUSE_MAP.append(ImsReasonInfo.CODE_CALL_DROP_IWLAN_TO_LTE_UNAVAILABLE,
+                CallFailCause.CALL_DROP_IWLAN_TO_LTE_UNAVAILABLE);
+        PRECISE_CAUSE_MAP.append(ImsReasonInfo.CODE_ANSWERED_ELSEWHERE,
+                CallFailCause.ANSWERED_ELSEWHERE);
+        PRECISE_CAUSE_MAP.append(ImsReasonInfo.CODE_CALL_PULL_OUT_OF_SYNC,
+                CallFailCause.CALL_PULL_OUT_OF_SYNC);
+        PRECISE_CAUSE_MAP.append(ImsReasonInfo.CODE_CALL_END_CAUSE_CALL_PULL,
+                CallFailCause.CALL_PULLED);
+        PRECISE_CAUSE_MAP.append(ImsReasonInfo.CODE_SUPP_SVC_FAILED,
+                CallFailCause.SUPP_SVC_FAILED);
+        PRECISE_CAUSE_MAP.append(ImsReasonInfo.CODE_SUPP_SVC_CANCELLED,
+                CallFailCause.SUPP_SVC_CANCELLED);
+        PRECISE_CAUSE_MAP.append(ImsReasonInfo.CODE_SUPP_SVC_REINVITE_COLLISION,
+                CallFailCause.SUPP_SVC_REINVITE_COLLISION);
+        PRECISE_CAUSE_MAP.append(ImsReasonInfo.CODE_IWLAN_DPD_FAILURE,
+                CallFailCause.IWLAN_DPD_FAILURE);
+        PRECISE_CAUSE_MAP.append(ImsReasonInfo.CODE_EPDG_TUNNEL_ESTABLISH_FAILURE,
+                CallFailCause.EPDG_TUNNEL_ESTABLISH_FAILURE);
+        PRECISE_CAUSE_MAP.append(ImsReasonInfo.CODE_EPDG_TUNNEL_REKEY_FAILURE,
+                CallFailCause.EPDG_TUNNEL_REKEY_FAILURE);
+        PRECISE_CAUSE_MAP.append(ImsReasonInfo.CODE_EPDG_TUNNEL_LOST_CONNECTION,
+                CallFailCause.EPDG_TUNNEL_LOST_CONNECTION);
+        PRECISE_CAUSE_MAP.append(ImsReasonInfo.CODE_MAXIMUM_NUMBER_OF_CALLS_REACHED,
+                CallFailCause.MAXIMUM_NUMBER_OF_CALLS_REACHED);
+        PRECISE_CAUSE_MAP.append(ImsReasonInfo.CODE_REMOTE_CALL_DECLINE,
+                CallFailCause.REMOTE_CALL_DECLINE);
+        PRECISE_CAUSE_MAP.append(ImsReasonInfo.CODE_DATA_LIMIT_REACHED,
+                CallFailCause.DATA_LIMIT_REACHED);
+        PRECISE_CAUSE_MAP.append(ImsReasonInfo.CODE_DATA_DISABLED,
+                CallFailCause.DATA_DISABLED);
+        PRECISE_CAUSE_MAP.append(ImsReasonInfo.CODE_WIFI_LOST,
+                CallFailCause.WIFI_LOST);
+        PRECISE_CAUSE_MAP.append(ImsReasonInfo.CODE_RADIO_OFF,
+                CallFailCause.RADIO_OFF);
+        PRECISE_CAUSE_MAP.append(ImsReasonInfo.CODE_NO_VALID_SIM,
+                CallFailCause.NO_VALID_SIM);
+        PRECISE_CAUSE_MAP.append(ImsReasonInfo.CODE_RADIO_INTERNAL_ERROR,
+                CallFailCause.RADIO_INTERNAL_ERROR);
+        PRECISE_CAUSE_MAP.append(ImsReasonInfo.CODE_NETWORK_RESP_TIMEOUT,
+                CallFailCause.NETWORK_RESP_TIMEOUT);
+        PRECISE_CAUSE_MAP.append(ImsReasonInfo.CODE_NETWORK_REJECT,
+                CallFailCause.NETWORK_REJECT);
+        PRECISE_CAUSE_MAP.append(ImsReasonInfo.CODE_RADIO_ACCESS_FAILURE,
+                CallFailCause.RADIO_ACCESS_FAILURE);
+        PRECISE_CAUSE_MAP.append(ImsReasonInfo.CODE_RADIO_LINK_FAILURE,
+                CallFailCause.RADIO_LINK_FAILURE);
+        PRECISE_CAUSE_MAP.append(ImsReasonInfo.CODE_RADIO_LINK_LOST,
+                CallFailCause.RADIO_LINK_LOST);
+        PRECISE_CAUSE_MAP.append(ImsReasonInfo.CODE_RADIO_UPLINK_FAILURE,
+                CallFailCause.RADIO_UPLINK_FAILURE);
+        PRECISE_CAUSE_MAP.append(ImsReasonInfo.CODE_RADIO_SETUP_FAILURE,
+                CallFailCause.RADIO_SETUP_FAILURE);
+        PRECISE_CAUSE_MAP.append(ImsReasonInfo.CODE_RADIO_RELEASE_NORMAL,
+                CallFailCause.RADIO_RELEASE_NORMAL);
+        PRECISE_CAUSE_MAP.append(ImsReasonInfo.CODE_RADIO_RELEASE_ABNORMAL,
+                CallFailCause.RADIO_RELEASE_ABNORMAL);
+        PRECISE_CAUSE_MAP.append(ImsReasonInfo.CODE_ACCESS_CLASS_BLOCKED,
+                CallFailCause.ACCESS_CLASS_BLOCKED);
+        PRECISE_CAUSE_MAP.append(ImsReasonInfo.CODE_NETWORK_DETACH,
+                CallFailCause.NETWORK_DETACH);
+        PRECISE_CAUSE_MAP.append(ImsReasonInfo.CODE_UNOBTAINABLE_NUMBER,
+                CallFailCause.UNOBTAINABLE_NUMBER);
+        PRECISE_CAUSE_MAP.append(ImsReasonInfo.CODE_OEM_CAUSE_1,
+                CallFailCause.OEM_CAUSE_1);
+        PRECISE_CAUSE_MAP.append(ImsReasonInfo.CODE_OEM_CAUSE_2,
+                CallFailCause.OEM_CAUSE_2);
+        PRECISE_CAUSE_MAP.append(ImsReasonInfo.CODE_OEM_CAUSE_3,
+                CallFailCause.OEM_CAUSE_3);
+        PRECISE_CAUSE_MAP.append(ImsReasonInfo.CODE_OEM_CAUSE_4,
+                CallFailCause.OEM_CAUSE_4);
+        PRECISE_CAUSE_MAP.append(ImsReasonInfo.CODE_OEM_CAUSE_5,
+                CallFailCause.OEM_CAUSE_5);
+        PRECISE_CAUSE_MAP.append(ImsReasonInfo.CODE_OEM_CAUSE_6,
+                CallFailCause.OEM_CAUSE_6);
+        PRECISE_CAUSE_MAP.append(ImsReasonInfo.CODE_OEM_CAUSE_7,
+                CallFailCause.OEM_CAUSE_7);
+        PRECISE_CAUSE_MAP.append(ImsReasonInfo.CODE_OEM_CAUSE_8,
+                CallFailCause.OEM_CAUSE_8);
+        PRECISE_CAUSE_MAP.append(ImsReasonInfo.CODE_OEM_CAUSE_9,
+                CallFailCause.OEM_CAUSE_9);
+        PRECISE_CAUSE_MAP.append(ImsReasonInfo.CODE_OEM_CAUSE_10,
+                CallFailCause.OEM_CAUSE_10);
+        PRECISE_CAUSE_MAP.append(ImsReasonInfo.CODE_OEM_CAUSE_11,
+                CallFailCause.OEM_CAUSE_11);
+        PRECISE_CAUSE_MAP.append(ImsReasonInfo.CODE_OEM_CAUSE_12,
+                CallFailCause.OEM_CAUSE_12);
+        PRECISE_CAUSE_MAP.append(ImsReasonInfo.CODE_OEM_CAUSE_13,
+                CallFailCause.OEM_CAUSE_13);
+        PRECISE_CAUSE_MAP.append(ImsReasonInfo.CODE_OEM_CAUSE_14,
+                CallFailCause.OEM_CAUSE_14);
+        PRECISE_CAUSE_MAP.append(ImsReasonInfo.CODE_OEM_CAUSE_15,
+                CallFailCause.OEM_CAUSE_15);
+    }
+
+    /**
      * Carrier configuration option which determines whether the carrier wants to inform the user
      * when a video call is handed over from WIFI to LTE.
      * See {@link CarrierConfigManager#KEY_NOTIFY_HANDOVER_VIDEO_FROM_WIFI_TO_LTE_BOOL} for more
@@ -528,7 +856,12 @@
         return PhoneNumberUtils.isEmergencyNumber(string);
     };
 
-    private final ImsManager.Connector mImsManagerConnector;
+    private final FeatureConnector<ImsManager> mImsManagerConnector;
+
+    // Used exclusively for IMS Registration related events for logging.
+    private final LocalLog mRegLocalLog = new LocalLog(100);
+    // Used for important operational related events for logging.
+    private final LocalLog mOperationLocalLog = new LocalLog(100);
 
     //***** Events
 
@@ -561,10 +894,20 @@
         long currentTime = SystemClock.elapsedRealtime();
         mVtDataUsageSnapshot = new NetworkStats(currentTime, 1);
         mVtDataUsageUidSnapshot = new NetworkStats(currentTime, 1);
+        final NetworkStatsManager statsManager =
+                (NetworkStatsManager) mPhone.getContext().getSystemService(
+                        Context.NETWORK_STATS_SERVICE);
+        statsManager.registerNetworkStatsProvider(LOG_TAG, mVtDataUsageProvider);
 
         // Allow the executor to be specified for testing.
-        mImsManagerConnector = new ImsManager.Connector(phone.getContext(), phone.getPhoneId(),
-                new ImsManager.Connector.Listener() {
+        mImsManagerConnector = new FeatureConnector<>(
+                phone.getContext(), phone.getPhoneId(),
+                new FeatureConnector.Listener<ImsManager>() {
+                    @Override
+                    public ImsManager getFeatureManager() {
+                        return ImsManager.getInstance(phone.getContext(), phone.getPhoneId());
+                    }
+
                     @Override
                     public void connectionReady(ImsManager manager) throws ImsException {
                         mImsManager = manager;
@@ -575,7 +918,7 @@
                     public void connectionUnavailable() {
                         stopListeningForCalls();
                     }
-                }, executor);
+                }, executor, "ImsPhoneCallTracker");
         mImsManagerConnector.connect();
     }
 
@@ -602,7 +945,7 @@
      * Test-only method used to set the ImsService retry timeout.
      */
     @VisibleForTesting
-    public void setRetryTimeout(ImsManager.Connector.RetryTimeout retryTimeout) {
+    public void setRetryTimeout(FeatureConnector.RetryTimeout retryTimeout) {
         mImsManagerConnector.mRetryTimeout = retryTimeout;
     }
 
@@ -624,8 +967,9 @@
 
     private void startListeningForCalls() throws ImsException {
         log("startListeningForCalls");
+        mOperationLocalLog.log("startListeningForCalls - Connecting to ImsService");
         mImsManager.open(mMmTelFeatureListener);
-        mImsManager.addRegistrationCallback(mImsRegistrationCallback);
+        mImsManager.addRegistrationCallback(mPhone.getImsMmTelRegistrationCallback());
         mImsManager.addCapabilitiesCallback(mImsCapabilityCallback);
 
         mImsManager.setConfigListener(mImsConfigListener);
@@ -666,6 +1010,7 @@
 
     private void stopListeningForCalls() {
         log("stopListeningForCalls");
+        mOperationLocalLog.log("stopListeningForCalls - Disconnecting from ImsService");
         resetImsCapabilities();
         // Only close on valid session.
         if (mImsManager != null) {
@@ -702,6 +1047,11 @@
         mPhone.getContext().unregisterReceiver(mReceiver);
         mPhone.getDefaultPhone().getDataEnabledSettings().unregisterForDataEnabledChanged(this);
         mImsManagerConnector.disconnect();
+
+        final NetworkStatsManager statsManager =
+                (NetworkStatsManager) mPhone.getContext().getSystemService(
+                        Context.NETWORK_STATS_SERVICE);
+        statsManager.unregisterNetworkStatsProvider(mVtDataUsageProvider);
     }
 
     @Override
@@ -738,7 +1088,7 @@
         if (mSharedPreferenceProxy != null && mPhone.getDefaultPhone() != null) {
             SharedPreferences sp = mSharedPreferenceProxy.getDefaultSharedPreferences(
                     mPhone.getContext());
-            return sp.getInt(Phone.CLIR_KEY + mPhone.getDefaultPhone().getPhoneId(),
+            return sp.getInt(Phone.CLIR_KEY + mPhone.getSubId(),
                     CommandsInterface.CLIR_DEFAULT);
         } else {
             loge("dial; could not get default CLIR mode.");
@@ -746,6 +1096,96 @@
         }
     }
 
+    private boolean prepareForDialing(ImsPhone.ImsDialArgs dialArgs) throws CallStateException {
+        boolean holdBeforeDial = false;
+        // note that this triggers call state changed notif
+        clearDisconnected();
+        if (mImsManager == null) {
+            throw new CallStateException("service not available");
+        }
+        // See if there are any issues which preclude placing a call; throw a CallStateException
+        // if there is.
+        checkForDialIssues();
+        int videoState = dialArgs.videoState;
+        if (!canAddVideoCallDuringImsAudioCall(videoState)) {
+            throw new CallStateException("cannot dial in current state");
+        }
+
+        // The new call must be assigned to the foreground call.
+        // That call must be idle, so place anything that's
+        // there on hold
+        if (mForegroundCall.getState() == ImsPhoneCall.State.ACTIVE) {
+            if (mBackgroundCall.getState() != ImsPhoneCall.State.IDLE) {
+                //we should have failed in checkForDialIssues above before we get here
+                throw new CallStateException(CallStateException.ERROR_TOO_MANY_CALLS,
+                        "Already too many ongoing calls.");
+            }
+            // foreground call is empty for the newly dialed connection
+            holdBeforeDial = true;
+            mPendingCallVideoState = videoState;
+            mPendingIntentExtras = dialArgs.intentExtras;
+            holdActiveCallForPendingMo();
+        }
+
+        ImsPhoneCall.State fgState = ImsPhoneCall.State.IDLE;
+        ImsPhoneCall.State bgState = ImsPhoneCall.State.IDLE;
+
+        synchronized (mSyncHold) {
+            if (holdBeforeDial) {
+                fgState = mForegroundCall.getState();
+                bgState = mBackgroundCall.getState();
+                //holding foreground call failed
+                if (fgState == ImsPhoneCall.State.ACTIVE) {
+                    throw new CallStateException("cannot dial in current state");
+                }
+                //holding foreground call succeeded
+                if (bgState == ImsPhoneCall.State.HOLDING) {
+                    holdBeforeDial = false;
+                }
+            }
+        }
+        return holdBeforeDial;
+    }
+
+    public Connection startConference(String[] participantsToDial, ImsPhone.ImsDialArgs dialArgs)
+            throws CallStateException {
+
+        int clirMode = dialArgs.clirMode;
+        int videoState = dialArgs.videoState;
+
+        if (DBG) log("dial clirMode=" + clirMode);
+        boolean holdBeforeDial = prepareForDialing(dialArgs);
+
+        mClirMode = clirMode;
+        ImsPhoneConnection pendingConnection;
+        synchronized (mSyncHold) {
+            mLastDialArgs = dialArgs;
+            pendingConnection = new ImsPhoneConnection(mPhone,
+                    participantsToDial, this, mForegroundCall,
+                    false);
+            // Don't rely on the mPendingMO in this method; if the modem calls back through
+            // onCallProgressing, we'll end up nulling out mPendingMO, which means that
+            // TelephonyConnectionService would treat this call as an MMI code, which it is not,
+            // which would mean that the MMI code dialog would crash.
+            mPendingMO = pendingConnection;
+            pendingConnection.setVideoState(videoState);
+            if (dialArgs.rttTextStream != null) {
+                log("startConference: setting RTT stream on mPendingMO");
+                pendingConnection.setCurrentRttTextStream(dialArgs.rttTextStream);
+            }
+        }
+        addConnection(pendingConnection);
+
+        if (!holdBeforeDial) {
+            dialInternal(pendingConnection, clirMode, videoState, dialArgs.intentExtras);
+        }
+
+        updatePhoneState();
+        mPhone.notifyPreciseCallStateChanged();
+
+        return pendingConnection;
+    }
+
     @UnsupportedAppUsage
     public Connection dial(String dialString, int videoState, Bundle intentExtras) throws
             CallStateException {
@@ -764,6 +1204,7 @@
 
         if (!shouldNumberBePlacedOnIms(isEmergencyNumber, dialString)) {
             Rlog.i(LOG_TAG, "dial: shouldNumberBePlacedOnIms = false");
+            mOperationLocalLog.log("dial: shouldNumberBePlacedOnIms = false");
             throw new CallStateException(CS_FALLBACK);
         }
 
@@ -771,24 +1212,20 @@
         int videoState = dialArgs.videoState;
 
         if (DBG) log("dial clirMode=" + clirMode);
+        mOperationLocalLog.log("dial requested.");
+        String origNumber = dialString;
         if (isEmergencyNumber) {
             clirMode = CommandsInterface.CLIR_SUPPRESSION;
             if (DBG) log("dial emergency call, set clirModIe=" + clirMode);
+        } else {
+            dialString = convertNumberIfNecessary(mPhone, dialString);
         }
 
-        // note that this triggers call state changed notif
-        clearDisconnected();
-
-        if (mImsManager == null) {
-            throw new CallStateException("service not available");
-        }
-
-        // See if there are any issues which preclude placing a call; throw a CallStateException
-        // if there is.
-        checkForDialIssues();
+        mClirMode = clirMode;
+        boolean holdBeforeDial = prepareForDialing(dialArgs);
 
         if (isPhoneInEcmMode && isEmergencyNumber) {
-            handleEcmTimer(ImsPhone.CANCEL_ECM_TIMER);
+            mPhone.handleTimerInEmergencyCallbackMode(ImsPhone.CANCEL_ECM_TIMER);
         }
 
         // If the call is to an emergency number and the carrier does not support video emergency
@@ -799,50 +1236,13 @@
             videoState = VideoProfile.STATE_AUDIO_ONLY;
         }
 
-        boolean holdBeforeDial = false;
-
-        // The new call must be assigned to the foreground call.
-        // That call must be idle, so place anything that's
-        // there on hold
-        if (mForegroundCall.getState() == ImsPhoneCall.State.ACTIVE) {
-            if (mBackgroundCall.getState() != ImsPhoneCall.State.IDLE) {
-                //we should have failed in checkForDialIssues above before we get here
-                throw new CallStateException(CallStateException.ERROR_TOO_MANY_CALLS,
-                        "Already too many ongoing calls.");
-            }
-            // foreground call is empty for the newly dialed connection
-            holdBeforeDial = true;
-            // Cache the video state for pending MO call.
-            mPendingCallVideoState = videoState;
-            mPendingIntentExtras = dialArgs.intentExtras;
-            holdActiveCallForPendingMo();
-        }
-
-        ImsPhoneCall.State fgState = ImsPhoneCall.State.IDLE;
-        ImsPhoneCall.State bgState = ImsPhoneCall.State.IDLE;
-
-        mClirMode = clirMode;
+        // Cache the video state for pending MO call.
+        mPendingCallVideoState = videoState;
 
         synchronized (mSyncHold) {
-            if (holdBeforeDial) {
-                fgState = mForegroundCall.getState();
-                bgState = mBackgroundCall.getState();
-
-                //holding foreground call failed
-                if (fgState == ImsPhoneCall.State.ACTIVE) {
-                    throw new CallStateException("cannot dial in current state");
-                }
-
-                //holding foreground call succeeded
-                if (bgState == ImsPhoneCall.State.HOLDING) {
-                    holdBeforeDial = false;
-                }
-            }
-
             mLastDialString = dialString;
             mLastDialArgs = dialArgs;
-            mPendingMO = new ImsPhoneConnection(mPhone,
-                    checkForTestEmergencyNumber(dialString), this, mForegroundCall,
+            mPendingMO = new ImsPhoneConnection(mPhone, dialString, this, mForegroundCall,
                     isEmergencyNumber);
             if (isEmergencyNumber && dialArgs != null && dialArgs.intentExtras != null) {
                 Rlog.i(LOG_TAG, "dial ims emergency dialer: " + dialArgs.intentExtras.getBoolean(
@@ -860,7 +1260,8 @@
 
         if (!holdBeforeDial) {
             if ((!isPhoneInEcmMode) || (isPhoneInEcmMode && isEmergencyNumber)) {
-                dialInternal(mPendingMO, clirMode, videoState, dialArgs.intentExtras);
+                dialInternal(mPendingMO, clirMode, videoState, dialArgs.retryCallFailCause,
+                        dialArgs.retryCallFailNetworkType, dialArgs.intentExtras);
             } else {
                 try {
                     getEcbmInterface().exitEmergencyCallbackMode();
@@ -871,10 +1272,16 @@
                 mPhone.setOnEcbModeExitResponse(this, EVENT_EXIT_ECM_RESPONSE_CDMA, null);
                 pendingCallClirMode = clirMode;
                 mPendingCallVideoState = videoState;
+                mPendingIntentExtras = dialArgs.intentExtras;
                 pendingCallInEcm = true;
             }
         }
 
+        if (mNumberConverted) {
+            mPendingMO.restoreDialedNumberAfterConversion(origNumber);
+            mNumberConverted = false;
+        }
+
         updatePhoneState();
         mPhone.notifyPreciseCallStateChanged();
 
@@ -966,6 +1373,9 @@
         mAllowAddCallDuringVideoCall =
                 carrierConfig.getBoolean(
                         CarrierConfigManager.KEY_ALLOW_ADD_CALL_DURING_VIDEO_CALL_BOOL);
+        mAllowHoldingVideoCall =
+                carrierConfig.getBoolean(
+                        CarrierConfigManager.KEY_ALLOW_HOLD_VIDEO_CALL_BOOL);
         mNotifyVtHandoverToWifiFail = carrierConfig.getBoolean(
                 CarrierConfigManager.KEY_NOTIFY_VT_HANDOVER_TO_WIFI_FAILURE_BOOL);
         mSupportDowngradeVtToAudio = carrierConfig.getBoolean(
@@ -984,6 +1394,8 @@
                 CarrierConfigManager.KEY_ALWAYS_PLAY_REMOTE_HOLD_TONE_BOOL);
         mAutoRetryFailedWifiEmergencyCall = carrierConfig.getBoolean(
                 CarrierConfigManager.KEY_AUTO_RETRY_FAILED_WIFI_EMERGENCY_CALL);
+        mSupportCepOnPeer = carrierConfig.getBoolean(
+                CarrierConfigManager.KEY_SUPPORT_IMS_CONFERENCE_EVENT_PACKAGE_ON_PEER_BOOL);
 
         String[] mappings = carrierConfig
                 .getStringArray(CarrierConfigManager.KEY_IMS_REASONINFO_MAPPING_STRING_ARRAY);
@@ -1023,25 +1435,24 @@
     @UnsupportedAppUsage
     private void handleEcmTimer(int action) {
         mPhone.handleTimerInEmergencyCallbackMode(action);
-        switch (action) {
-            case ImsPhone.CANCEL_ECM_TIMER:
-                break;
-            case ImsPhone.RESTART_ECM_TIMER:
-                break;
-            default:
-                log("handleEcmTimer, unsupported action " + action);
-        }
     }
 
     private void dialInternal(ImsPhoneConnection conn, int clirMode, int videoState,
             Bundle intentExtras) {
+        dialInternal(conn, clirMode, videoState, ImsReasonInfo.CODE_UNSPECIFIED,
+                TelephonyManager.NETWORK_TYPE_UNKNOWN, intentExtras);
+    }
+
+    private void dialInternal(ImsPhoneConnection conn, int clirMode, int videoState,
+            int retryCallFailCause, int retryCallFailNetworkType, Bundle intentExtras) {
 
         if (conn == null) {
             return;
         }
 
-        if (conn.getAddress()== null || conn.getAddress().length() == 0
-                || conn.getAddress().indexOf(PhoneNumberUtils.WILD) >= 0) {
+        if (!conn.isAdhocConference() &&
+                (conn.getAddress()== null || conn.getAddress().length() == 0
+                || conn.getAddress().indexOf(PhoneNumberUtils.WILD) >= 0)) {
             // Phone number is invalid
             conn.setDisconnectCause(DisconnectCause.INVALID_NUMBER);
             sendEmptyMessageDelayed(EVENT_HANGUP_PENDINGMO, TIMEOUT_HANGUP_PENDINGMO);
@@ -1060,7 +1471,14 @@
         try {
             String[] callees = new String[] { conn.getAddress() };
             ImsCallProfile profile = mImsManager.createCallProfile(serviceType, callType);
+            if (conn.isAdhocConference()) {
+                profile.setCallExtraBoolean(ImsCallProfile.EXTRA_CONFERENCE, true);
+            }
             profile.setCallExtraInt(ImsCallProfile.EXTRA_OIR, clirMode);
+            profile.setCallExtraInt(ImsCallProfile.EXTRA_RETRY_CALL_FAIL_REASON,
+                    retryCallFailCause);
+            profile.setCallExtraInt(ImsCallProfile.EXTRA_RETRY_CALL_FAIL_NETWORKTYPE,
+                    retryCallFailNetworkType);
 
             if (isEmergencyCall) {
                 // Set emergency call information in ImsCallProfile
@@ -1099,16 +1517,21 @@
                 // being sent to the lower layers/to the network.
             }
 
-            ImsCall imsCall = mImsManager.makeCall(profile, callees, mImsCallListener);
+            mPhone.getVoiceCallSessionStats().onImsDial(conn);
+
+            ImsCall imsCall = mImsManager.makeCall(profile,
+                    conn.isAdhocConference() ? conn.getParticipantsToDial() : callees,
+                    mImsCallListener);
             conn.setImsCall(imsCall);
 
-            mMetrics.writeOnImsCallStart(mPhone.getPhoneId(),
-                    imsCall.getSession());
+            mMetrics.writeOnImsCallStart(mPhone.getPhoneId(), imsCall.getSession());
 
             setVideoCallProvider(conn, imsCall);
             conn.setAllowAddCallDuringVideoCall(mAllowAddCallDuringVideoCall);
+            conn.setAllowHoldingVideoCall(mAllowHoldingVideoCall);
         } catch (ImsException e) {
             loge("dialInternal : " + e);
+            mOperationLocalLog.log("dialInternal exception: " + e);
             conn.setDisconnectCause(DisconnectCause.ERROR_UNSPECIFIED);
             sendEmptyMessageDelayed(EVENT_HANGUP_PENDINGMO, TIMEOUT_HANGUP_PENDINGMO);
             retryGetImsService();
@@ -1125,6 +1548,7 @@
      */
     public void acceptCall(int videoState) throws CallStateException {
         if (DBG) log("acceptCall");
+        mOperationLocalLog.log("accepted incoming call");
 
         if (mForegroundCall.getState().isAlive()
                 && mBackgroundCall.getState().isAlive()) {
@@ -1149,6 +1573,7 @@
             if (answeringWillDisconnect) {
                 // We need to disconnect the foreground call before answering the background call.
                 mForegroundCall.hangup();
+                mPhone.getVoiceCallSessionStats().onImsAcceptCall(mRingingCall.getConnections());
                 try {
                     ringingCall.accept(ImsCallProfile.getCallTypeFromVideoState(videoState));
                 } catch (ImsException e) {
@@ -1164,6 +1589,8 @@
             try {
                 ImsCall imsCall = mRingingCall.getImsCall();
                 if (imsCall != null) {
+                    mPhone.getVoiceCallSessionStats().onImsAcceptCall(
+                            mRingingCall.getConnections());
                     imsCall.accept(ImsCallProfile.getCallTypeFromVideoState(videoState));
                     mMetrics.writeOnImsCommand(mPhone.getPhoneId(), imsCall.getSession(),
                             ImsCommand.IMS_CMD_ACCEPT);
@@ -1180,6 +1607,7 @@
 
     public void rejectCall () throws CallStateException {
         if (DBG) log("rejectCall");
+        mOperationLocalLog.log("rejected incoming call");
 
         if (mRingingCall.getState().isRinging()) {
             hangup(mRingingCall);
@@ -1215,6 +1643,7 @@
             logi("Ignoring hold request while already holding or swapping");
             return;
         }
+        HoldSwapState oldHoldState = mHoldSwitchingState;
         ImsCall callToHold = mForegroundCall.getImsCall();
 
         mHoldSwitchingState = HoldSwapState.HOLDING_TO_DIAL_OUTGOING;
@@ -1227,6 +1656,8 @@
                     ImsCommand.IMS_CMD_HOLD);
         } catch (ImsException e) {
             mForegroundCall.switchWith(mBackgroundCall);
+            mHoldSwitchingState = oldHoldState;
+            logHoldSwapState("holdActiveCallForPendingMo - fail");
             throw new CallStateException(e.getMessage());
         }
     }
@@ -1241,6 +1672,7 @@
                 logi("Ignoring hold request while already holding or swapping");
                 return;
             }
+            HoldSwapState oldHoldState = mHoldSwitchingState;
             ImsCall callToHold = mForegroundCall.getImsCall();
             if (mBackgroundCall.getState().isAlive()) {
                 mCallExpectedToResume = mBackgroundCall.getImsCall();
@@ -1256,6 +1688,8 @@
                         ImsCommand.IMS_CMD_HOLD);
             } catch (ImsException e) {
                 mForegroundCall.switchWith(mBackgroundCall);
+                mHoldSwitchingState = oldHoldState;
+                logHoldSwapState("holdActiveCall - fail");
                 throw new CallStateException(e.getMessage());
             }
         }
@@ -1269,6 +1703,7 @@
                 && mRingingCall.getState() == ImsPhoneCall.State.WAITING;
         if (switchingWithWaitingCall) {
             ImsCall callToHold = mForegroundCall.getImsCall();
+            HoldSwapState oldHoldState = mHoldSwitchingState;
             mHoldSwitchingState = HoldSwapState.HOLDING_TO_ANSWER_INCOMING;
             mForegroundCall.switchWith(mBackgroundCall);
             logHoldSwapState("holdActiveCallForWaitingCall");
@@ -1278,6 +1713,8 @@
                         ImsCommand.IMS_CMD_HOLD);
             } catch (ImsException e) {
                 mForegroundCall.switchWith(mBackgroundCall);
+                mHoldSwitchingState = oldHoldState;
+                logHoldSwapState("holdActiveCallForWaitingCall - fail");
                 throw new CallStateException(e.getMessage());
             }
         }
@@ -1286,25 +1723,29 @@
     /**
      * Unhold the currently held call.
      */
-    void unholdHeldCall() throws CallStateException {
-        try {
-            ImsCall imsCall = mBackgroundCall.getImsCall();
-            if (mHoldSwitchingState == HoldSwapState.PENDING_SINGLE_CALL_UNHOLD
-                    || mHoldSwitchingState == HoldSwapState.SWAPPING_ACTIVE_AND_HELD) {
-                logi("Ignoring unhold request while already unholding or swapping");
-                return;
-            }
-            if (imsCall != null) {
-                mCallExpectedToResume = imsCall;
-                mHoldSwitchingState = HoldSwapState.PENDING_SINGLE_CALL_UNHOLD;
-                mForegroundCall.switchWith(mBackgroundCall);
-                logHoldSwapState("unholdCurrentCall");
+    public void unholdHeldCall() throws CallStateException {
+        ImsCall imsCall = mBackgroundCall.getImsCall();
+        if (mHoldSwitchingState == HoldSwapState.PENDING_SINGLE_CALL_UNHOLD
+                || mHoldSwitchingState == HoldSwapState.SWAPPING_ACTIVE_AND_HELD) {
+            logi("Ignoring unhold request while already unholding or swapping");
+            return;
+        }
+        if (imsCall != null) {
+            mCallExpectedToResume = imsCall;
+            HoldSwapState oldHoldState = mHoldSwitchingState;
+            mHoldSwitchingState = HoldSwapState.PENDING_SINGLE_CALL_UNHOLD;
+            mForegroundCall.switchWith(mBackgroundCall);
+            logHoldSwapState("unholdCurrentCall");
+            try {
                 imsCall.resume();
                 mMetrics.writeOnImsCommand(mPhone.getPhoneId(), imsCall.getSession(),
                         ImsCommand.IMS_CMD_RESUME);
+            } catch (ImsException e) {
+                mForegroundCall.switchWith(mBackgroundCall);
+                mHoldSwitchingState = oldHoldState;
+                logHoldSwapState("unholdCurrentCall - fail");
+                throw new CallStateException(e.getMessage());
             }
-        } catch (ImsException e) {
-            throw new CallStateException(e.getMessage());
         }
     }
 
@@ -1323,6 +1764,7 @@
         //accept waiting call after holding background call
         ImsCall imsCall = mRingingCall.getImsCall();
         if (imsCall != null) {
+            mPhone.getVoiceCallSessionStats().onImsAcceptCall(mRingingCall.getConnections());
             imsCall.accept(
                     ImsCallProfile.getCallTypeFromVideoState(mPendingCallVideoState));
             mMetrics.writeOnImsCommand(mPhone.getPhoneId(), imsCall.getSession(),
@@ -1462,6 +1904,8 @@
             cacheConnectionTimeWithPhoneNumber(backgroundConnection);
         }
         log("conference: fgCallId=" + foregroundId + ", bgCallId=" + backgroundId);
+        mOperationLocalLog.log("conference: fgCallId=" + foregroundId + ", bgCallId="
+                + backgroundId);
 
         try {
             fgImsCall.merge(bgImsCall);
@@ -1470,9 +1914,23 @@
         }
     }
 
-    public void
-    explicitCallTransfer() {
-        //TODO : implement
+    /**
+     * Connects the two calls and disconnects the subscriber from both calls. Throws a
+     * {@link CallStateException} if there is an issue.
+     * @throws CallStateException
+     */
+    public void explicitCallTransfer() throws CallStateException {
+        ImsCall fgImsCall = mForegroundCall.getImsCall();
+        ImsCall bgImsCall = mBackgroundCall.getImsCall();
+        if ((fgImsCall == null) || (bgImsCall == null) || !canTransfer()) {
+            throw new CallStateException("cannot transfer");
+        }
+
+        try {
+            fgImsCall.consultativeTransfer(bgImsCall);
+        } catch (ImsException e) {
+            throw new CallStateException(e.getMessage());
+        }
     }
 
     @UnsupportedAppUsage
@@ -1494,15 +1952,33 @@
             && !mForegroundCall.isFull();
     }
 
+    private boolean canAddVideoCallDuringImsAudioCall(int videoState) {
+        if (mAllowHoldingVideoCall) {
+            return true;
+        }
+
+        ImsCall call = mForegroundCall.getImsCall();
+        if (call == null) {
+            call = mBackgroundCall.getImsCall();
+        }
+
+        boolean isImsAudioCallActiveOrHolding = (mForegroundCall.getState() == Call.State.ACTIVE ||
+               mBackgroundCall.getState() == Call.State.HOLDING) && call != null &&
+               !call.isVideoCall();
+
+        /* return TRUE if there doesn't exist ims audio call in either active
+           or hold states */
+        return !isImsAudioCallActiveOrHolding || !VideoProfile.isVideo(videoState);
+    }
+
     /**
      * Determines if there are issues which would preclude dialing an outgoing call.  Throws a
      * {@link CallStateException} if there is an issue.
      * @throws CallStateException
      */
     public void checkForDialIssues() throws CallStateException {
-        String disableCall = SystemProperties.get(
-                TelephonyProperties.PROPERTY_DISABLE_CALL, "false");
-        if (disableCall.equals("true")) {
+        boolean disableCall = TelephonyProperties.disable_call().orElse(false);
+        if (disableCall) {
             throw new CallStateException(CallStateException.ERROR_CALLING_DISABLED,
                     "ro.telephony.disable-call has been used to disable calling.");
         }
@@ -1563,8 +2039,8 @@
         if (DBG) {
             log("updatePhoneState pendingMo = " + (mPendingMO == null ? "null"
                     : mPendingMO.getState()) + ", fg= " + mForegroundCall.getState() + "("
-                    + mForegroundCall.getConnections().size() + "), bg= " + mBackgroundCall
-                    .getState() + "(" + mBackgroundCall.getConnections().size() + ")");
+                    + mForegroundCall.getConnectionsCount() + "), bg= " + mBackgroundCall
+                    .getState() + "(" + mBackgroundCall.getConnectionsCount() + ")");
             log("updatePhoneState oldState=" + oldState + ", newState=" + mState);
         }
 
@@ -1717,9 +2193,14 @@
     //***** Called from ImsPhoneCall
 
     public void hangup (ImsPhoneCall call) throws CallStateException {
-        if (DBG) log("hangup call");
+        hangup(call, android.telecom.Call.REJECT_REASON_DECLINED);
+    }
 
-        if (call.getConnections().size() == 0) {
+    public void hangup (ImsPhoneCall call, @android.telecom.Call.RejectReason int rejectReason)
+            throws CallStateException {
+        if (DBG) log("hangup call - reason=" + rejectReason);
+
+        if (call.getConnectionsCount() == 0) {
             throw new CallStateException("no connections");
         }
 
@@ -1744,6 +2225,10 @@
             if (Phone.DEBUG_PHONE) {
                 log("(backgnd) hangup waiting or background");
             }
+        } else if (call == mHandoverCall) {
+            if (Phone.DEBUG_PHONE) {
+                log("(handover) hangup handover (SRVCC) call");
+            }
         } else {
             throw new CallStateException ("ImsPhoneCall " + call +
                     "does not belong to ImsPhoneCallTracker " + this);
@@ -1754,7 +2239,11 @@
         try {
             if (imsCall != null) {
                 if (rejectCall) {
-                    imsCall.reject(ImsReasonInfo.CODE_USER_DECLINE);
+                    if (rejectReason == android.telecom.Call.REJECT_REASON_UNWANTED) {
+                        imsCall.reject(ImsReasonInfo.CODE_SIP_USER_MARKED_UNWANTED);
+                    } else {
+                        imsCall.reject(ImsReasonInfo.CODE_USER_DECLINE);
+                    }
                     mMetrics.writeOnImsCommand(mPhone.getPhoneId(), imsCall.getSession(),
                             ImsCommand.IMS_CMD_REJECT);
                 } else {
@@ -1779,10 +2268,11 @@
     }
 
     void callEndCleanupHandOverCallIfAny() {
-        if (mHandoverCall.mConnections.size() > 0) {
+        List<Connection> connections = mHandoverCall.getConnections();
+        if (connections.size() > 0) {
             if (DBG) log("callEndCleanupHandOverCallIfAny, mHandoverCall.mConnections="
-                    + mHandoverCall.mConnections);
-            mHandoverCall.mConnections.clear();
+                    + mHandoverCall.getConnections());
+            mHandoverCall.clearConnections();
             mConnections.clear();
             mState = PhoneConstants.State.IDLE;
         }
@@ -1834,6 +2324,7 @@
         mUssdSession.terminate(ImsReasonInfo.CODE_USER_TERMINATED);
     }
 
+    @UnsupportedAppUsage
     private synchronized ImsPhoneConnection findConnection(final ImsCall imsCall) {
         for (ImsPhoneConnection conn : mConnections) {
             if (conn.getImsCall() == imsCall) {
@@ -1858,6 +2349,9 @@
             }
 
             if (!isEmergencyCallInList) {
+                if (mPhone.isEcmCanceledForEmergency()) {
+                    mPhone.handleTimerInEmergencyCallbackMode(ImsPhone.RESTART_ECM_TIMER);
+                }
                 mIsInEmergencyCall = false;
                 mPhone.sendEmergencyCallStateChange(false);
             }
@@ -1873,6 +2367,7 @@
         }
     }
 
+    @UnsupportedAppUsage
     private void processCallStateChange(ImsCall imsCall, ImsPhoneCall.State state, int cause) {
         if (DBG) log("processCallStateChange " + imsCall + " state=" + state + " cause=" + cause);
         // This method is called on onCallUpdate() where there is not necessarily a call state
@@ -1882,6 +2377,7 @@
         processCallStateChange(imsCall, state, cause, false /* do not ignore state update */);
     }
 
+    @UnsupportedAppUsage
     private void processCallStateChange(ImsCall imsCall, ImsPhoneCall.State state, int cause,
             boolean ignoreState) {
         if (DBG) {
@@ -2040,7 +2536,6 @@
                 return DisconnectCause.SERVER_ERROR;
 
             case ImsReasonInfo.CODE_SIP_REDIRECTED:
-            case ImsReasonInfo.CODE_SIP_BAD_REQUEST:
             case ImsReasonInfo.CODE_SIP_NOT_ACCEPTABLE:
             case ImsReasonInfo.CODE_SIP_GLOBAL_ERROR:
                 return DisconnectCause.SERVER_ERROR;
@@ -2149,6 +2644,9 @@
             case ImsReasonInfo.CODE_UNOBTAINABLE_NUMBER:
                 return DisconnectCause.UNOBTAINABLE_NUMBER;
 
+            case ImsReasonInfo.CODE_MEDIA_NO_DATA:
+                return DisconnectCause.MEDIA_TIMEOUT;
+
             case ImsReasonInfo.CODE_UNSPECIFIED:
                 if (mPhone.getDefaultPhone().getServiceStateTracker().mRestrictedState
                         .isCsRestricted()) {
@@ -2162,12 +2660,28 @@
                 }
                 break;
 
+            case ImsReasonInfo.CODE_SIP_BAD_REQUEST:
+            case ImsReasonInfo.CODE_REJECT_CALL_ON_OTHER_SUB:
+            case ImsReasonInfo.CODE_REJECT_ONGOING_E911_CALL:
+            case ImsReasonInfo.CODE_REJECT_ONGOING_CALL_SETUP:
+            case ImsReasonInfo.CODE_REJECT_MAX_CALL_LIMIT_REACHED:
+            case ImsReasonInfo.CODE_REJECT_ONGOING_CALL_TRANSFER:
+            case ImsReasonInfo.CODE_REJECT_ONGOING_CONFERENCE_CALL:
+            case ImsReasonInfo.CODE_REJECT_ONGOING_HANDOVER:
+            case ImsReasonInfo.CODE_REJECT_ONGOING_CALL_UPGRADE:
+                return DisconnectCause.INCOMING_AUTO_REJECTED;
+
             default:
         }
 
         return cause;
     }
 
+    private int getPreciseDisconnectCauseFromReasonInfo(ImsReasonInfo reasonInfo) {
+        return PRECISE_CAUSE_MAP.get(maybeRemapReasonCode(reasonInfo),
+                CallFailCause.ERROR_UNSPECIFIED);
+    }
+
     /**
      * @return true if the phone is in Emergency Callback mode, otherwise false
      */
@@ -2193,6 +2707,7 @@
     /**
      * Listen to the IMS call state change
      */
+    @UnsupportedAppUsage
     private ImsCall.Listener mImsCallListener = new ImsCall.Listener() {
         @Override
         public void onCallProgressing(ImsCall imsCall) {
@@ -2253,6 +2768,7 @@
                         DisconnectCause.NOT_DISCONNECTED, true /*ignore state update*/);
                 mMetrics.writeImsCallState(mPhone.getPhoneId(),
                         imsCall.getCallSession(), conn.getCall().mState);
+                mPhone.getVoiceCallSessionStats().onCallStateChanged(conn.getCall());
             }
         }
 
@@ -2349,7 +2865,8 @@
                 } else if (conn.isIncoming() && conn.getConnectTime() == 0
                         && cause != DisconnectCause.ANSWERED_ELSEWHERE) {
                     // Missed
-                    if (cause == DisconnectCause.NORMAL) {
+                    if (cause == DisconnectCause.NORMAL
+                            || cause == DisconnectCause.INCOMING_AUTO_REJECTED) {
                         cause = DisconnectCause.INCOMING_MISSED;
                     } else {
                         cause = DisconnectCause.INCOMING_REJECTED;
@@ -2365,9 +2882,13 @@
             }
 
             String callId = imsCall.getSession().getCallId();
+            EmergencyNumberTracker emergencyNumberTracker = conn.getEmergencyNumberTracker();
             mMetrics.writeOnImsCallTerminated(mPhone.getPhoneId(), imsCall.getCallSession(),
                     reasonInfo, mCallQualityMetrics.get(callId), conn.getEmergencyNumberInfo(),
-                    getNetworkCountryIso());
+                    getNetworkCountryIso(), emergencyNumberTracker != null
+                    ? emergencyNumberTracker.getEmergencyNumberDbVersion()
+                    : TelephonyManager.INVALID_EMERGENCY_NUMBER_DB_VERSION);
+            mPhone.getVoiceCallSessionStats().onImsCallTerminated(conn, reasonInfo);
             // Remove info for the callId from the current calls and add it to the history
             CallQualityMetrics lastCallMetrics = mCallQualityMetrics.remove(callId);
             if (lastCallMetrics != null) {
@@ -2376,11 +2897,15 @@
             pruneCallQualityMetricsHistory();
             mPhone.notifyImsReason(reasonInfo);
 
+            if (conn != null) {
+                conn.setPreciseDisconnectCause(getPreciseDisconnectCauseFromReasonInfo(reasonInfo));
+            }
+
             if (reasonInfo.getCode() == ImsReasonInfo.CODE_SIP_ALTERNATE_EMERGENCY_CALL
                     && mAutoRetryFailedWifiEmergencyCall) {
                 Pair<ImsCall, ImsReasonInfo> callInfo = new Pair<>(imsCall, reasonInfo);
-                mPhone.getDefaultPhone().getServiceStateTracker().registerForNetworkAttached(
-                        ImsPhoneCallTracker.this, EVENT_REDIAL_WIFI_E911_CALL, callInfo);
+                mPhone.getDefaultPhone().mCi.registerForOn(ImsPhoneCallTracker.this,
+                        EVENT_REDIAL_WIFI_E911_CALL, callInfo);
                 sendMessageDelayed(obtainMessage(EVENT_REDIAL_WIFI_E911_TIMEOUT, callInfo),
                         TIMEOUT_REDIAL_WIFI_E911_MS);
                 final ConnectivityManager mgr = (ConnectivityManager) mPhone.getContext()
@@ -2402,6 +2927,10 @@
                 }
             }
 
+            if (conn != null) {
+                conn.onRemoteDisconnect(reasonInfo.getExtraMessage());
+            }
+
             if (mHoldSwitchingState == HoldSwapState.SWAPPING_ACTIVE_AND_HELD) {
                 if (DBG) {
                     log("onCallTerminated: Call terminated in the midst of Switching " +
@@ -2569,7 +3098,7 @@
                     mHoldSwitchingState = HoldSwapState.INACTIVE;
                 }
                 ImsPhoneConnection conn = findConnection(imsCall);
-                if (conn != null) {
+                if (conn != null && conn.getState() != ImsPhoneCall.State.DISCONNECTED) {
                     conn.onConnectionEvent(android.telecom.Connection.EVENT_CALL_HOLD_FAILED, null);
                 }
                 mPhone.notifySuppServiceFailed(Phone.SuppService.HOLD);
@@ -2722,8 +3251,8 @@
             }
             foregroundImsPhoneCall.merge(peerImsPhoneCall, ImsPhoneCall.State.ACTIVE);
 
+            final ImsPhoneConnection conn = findConnection(call);
             try {
-                final ImsPhoneConnection conn = findConnection(call);
                 log("onCallMerged: ImsPhoneConnection=" + conn);
                 log("onCallMerged: CurrentVideoProvider=" + conn.getVideoProvider());
                 setVideoCallProvider(conn, call);
@@ -2751,6 +3280,11 @@
                 // Reset the flag.
                 call.resetIsMergeRequestedByConf(false);
             }
+
+            // Notify completion of merge
+            if (conn != null) {
+                conn.handleMergeComplete();
+            }
             logState();
         }
 
@@ -2767,10 +3301,16 @@
 
             // Start plumbing this even through Telecom so other components can take
             // appropriate action.
-            ImsPhoneConnection conn = findConnection(call);
-            if (conn != null) {
-                conn.onConferenceMergeFailed();
-                conn.handleMergeComplete();
+            ImsPhoneConnection foregroundConnection = mForegroundCall.getFirstConnection();
+            if (foregroundConnection != null) {
+                foregroundConnection.onConferenceMergeFailed();
+                foregroundConnection.handleMergeComplete();
+            }
+
+            ImsPhoneConnection backgroundConnection = mBackgroundCall.getFirstConnection();
+            if (backgroundConnection != null) {
+                backgroundConnection.onConferenceMergeFailed();
+                backgroundConnection.handleMergeComplete();
             }
         }
 
@@ -2797,6 +3337,16 @@
                 List<ConferenceParticipant> participants) {
             if (DBG) log("onConferenceParticipantsStateChanged");
 
+            if (!mIsConferenceEventPackageEnabled) {
+                logi("onConferenceParticipantsStateChanged - CEP handling disabled");
+                return;
+            }
+
+            if (!mSupportCepOnPeer && !call.isConferenceHost()) {
+                logi("onConferenceParticipantsStateChanged - ignore CEP on peer");
+                return;
+            }
+
             ImsPhoneConnection conn = findConnection(call);
             if (conn != null) {
                 updateConferenceParticipantsTiming(participants);
@@ -2979,6 +3529,17 @@
             }
         }
 
+        @Override
+        public void onCallSessionTransferred(ImsCall imsCall) {
+            if (DBG) log("onCallSessionTransferred success");
+        }
+
+        @Override
+        public void onCallSessionTransferFailed(ImsCall imsCall, ImsReasonInfo reasonInfo) {
+            if (DBG) log("onCallSessionTransferFailed reasonInfo=" + reasonInfo);
+            mPhone.notifySuppServiceFailed(Phone.SuppService.TRANSFER);
+        }
+
         /**
          * Handles a change to the multiparty state for an {@code ImsCall}.  Notifies the associated
          * {@link ImsPhoneConnection} of the change.
@@ -3004,8 +3565,7 @@
         @Override
         public void onCallQualityChanged(ImsCall imsCall, CallQuality callQuality) {
             // convert ServiceState.radioTech to TelephonyManager.NetworkType constant
-            mPhone.onCallQualityChanged(callQuality,
-                    ServiceState.rilRadioTechnologyToNetworkType(imsCall.getRadioTechnology()));
+            mPhone.onCallQualityChanged(callQuality, imsCall.getNetworkType());
             String callId = imsCall.getSession().getCallId();
             CallQualityMetrics cqm = mCallQualityMetrics.get(callId);
             if (cqm == null) {
@@ -3037,6 +3597,20 @@
         public void onCallStartFailed(ImsCall imsCall, ImsReasonInfo reasonInfo) {
             if (DBG) log("mImsUssdListener onCallStartFailed reasonCode=" + reasonInfo.getCode());
 
+            if (mUssdSession != null) {
+                if (DBG) log("mUssdSession is not null");
+                // To initiate sending Ussd under circuit-switched call
+                if (reasonInfo.getCode() == ImsReasonInfo.CODE_LOCAL_CALL_CS_RETRY_REQUIRED) {
+                    mUssdSession = null;
+                    mPhone.getPendingMmiCodes().clear();
+                    mPhone.initiateSilentRedial();
+                    if (DBG) log("Initiated sending ussd by using silent redial.");
+                    return;
+                } else {
+                    if (DBG) log("Failed to start sending ussd by using silent resendUssd.!!");
+                }
+            }
+
             onCallTerminated(imsCall, reasonInfo);
         }
 
@@ -3081,46 +3655,6 @@
         }
     };
 
-    private final ImsMmTelManager.RegistrationCallback mImsRegistrationCallback =
-            new ImsMmTelManager.RegistrationCallback() {
-
-                @Override
-                public void onRegistered(
-                        @ImsRegistrationImplBase.ImsRegistrationTech int imsRadioTech) {
-                    if (DBG) log("onImsConnected imsRadioTech=" + imsRadioTech);
-                    mPhone.setServiceState(ServiceState.STATE_IN_SERVICE);
-                    mPhone.setImsRegistered(true);
-                    mMetrics.writeOnImsConnectionState(mPhone.getPhoneId(),
-                            ImsConnectionState.State.CONNECTED, null);
-                }
-
-                @Override
-                public void onRegistering(
-                        @ImsRegistrationImplBase.ImsRegistrationTech int imsRadioTech) {
-                    if (DBG) log("onImsProgressing imsRadioTech=" + imsRadioTech);
-                    mPhone.setServiceState(ServiceState.STATE_OUT_OF_SERVICE);
-                    mPhone.setImsRegistered(false);
-                    mMetrics.writeOnImsConnectionState(mPhone.getPhoneId(),
-                            ImsConnectionState.State.PROGRESSING, null);
-                }
-
-                @Override
-                public void onUnregistered(ImsReasonInfo imsReasonInfo) {
-                    if (DBG) log("onImsDisconnected imsReasonInfo=" + imsReasonInfo);
-                    mPhone.setServiceState(ServiceState.STATE_OUT_OF_SERVICE);
-                    mPhone.setImsRegistered(false);
-                    mPhone.processDisconnectReason(imsReasonInfo);
-                    mMetrics.writeOnImsConnectionState(mPhone.getPhoneId(),
-                            ImsConnectionState.State.DISCONNECTED, imsReasonInfo);
-                }
-
-                @Override
-                public void onSubscriberAssociatedUriChanged(Uri[] uris) {
-                    if (DBG) log("registrationAssociatedUriChanged");
-                    mPhone.setCurrentSubscriberUris(uris);
-                }
-            };
-
     private final ImsMmTelManager.CapabilityCallback mImsCapabilityCallback =
             new ImsMmTelManager.CapabilityCallback() {
                 @Override
@@ -3158,6 +3692,14 @@
         @Override
         public void onProvisioningIntChanged(int item, int value) {
             sendConfigChangedIntent(item, Integer.toString(value));
+            if ((mImsManager != null)
+                    && (item == ImsConfig.ConfigConstants.VOICE_OVER_WIFI_SETTING_ENABLED
+                    || item == ImsConfig.ConfigConstants.VLT_SETTING_ENABLED
+                    || item == ImsConfig.ConfigConstants.LVC_SETTING_ENABLED)) {
+                // Update Ims Service state to make sure updated provisioning values take effect
+                // immediately.
+                mImsManager.updateImsServiceConfig(true);
+            }
         }
 
         @Override
@@ -3192,8 +3734,17 @@
             callState = Call.State.DIALING;
         }
         int cause = getDisconnectCauseFromReasonInfo(reasonInfo, callState);
+        if (conn != null) {
+            conn.onRemoteDisconnect(reasonInfo.getExtraMessage());
+        }
 
         processCallStateChange(imsCall, ImsPhoneCall.State.DISCONNECTED, cause);
+
+        if (conn != null) {
+            conn.setPreciseDisconnectCause(
+                    getPreciseDisconnectCauseFromReasonInfo(reasonInfo));
+        }
+
         mPhone.notifyImsReason(reasonInfo);
     }
 
@@ -3208,22 +3759,23 @@
     }
 
     private void transferHandoverConnections(ImsPhoneCall call) {
-        if (call.mConnections != null) {
-            for (Connection c : call.mConnections) {
+        if (call.getConnections() != null) {
+            for (Connection c : call.getConnections()) {
                 c.mPreHandoverState = call.mState;
                 log ("Connection state before handover is " + c.getStateBeforeHandover());
             }
         }
-        if (mHandoverCall.mConnections == null ) {
+        if (mHandoverCall.getConnections() == null) {
             mHandoverCall.mConnections = call.mConnections;
         } else { // Multi-call SRVCC
             mHandoverCall.mConnections.addAll(call.mConnections);
         }
-        if (mHandoverCall.mConnections != null) {
+        mHandoverCall.copyConnectionFrom(call);
+        if (mHandoverCall.getConnections() != null) {
             if (call.getImsCall() != null) {
                 call.getImsCall().close();
             }
-            for (Connection c : mHandoverCall.mConnections) {
+            for (Connection c : mHandoverCall.getConnections()) {
                 ((ImsPhoneConnection)c).changeParent(mHandoverCall);
                 ((ImsPhoneConnection)c).releaseWakeLock();
             }
@@ -3232,7 +3784,7 @@
             log ("Call is alive and state is " + call.mState);
             mHandoverCall.mState = call.mState;
         }
-        call.mConnections.clear();
+        call.clearConnections();
         call.mState = ImsPhoneCall.State.IDLE;
         if (mPendingMO != null) {
             // If the call is handed over before moving to alerting (i.e. e911 CSFB redial), clear
@@ -3242,8 +3794,11 @@
         }
     }
 
-    /* package */
-    void notifySrvccState(Call.SrvccState state) {
+    /**
+     * Notify of a change to SRVCC state
+     * @param state the new SRVCC state.
+     */
+    public void notifySrvccState(Call.SrvccState state) {
         if (DBG) log("notifySrvccState state=" + state);
 
         mSrvccState = state;
@@ -3253,11 +3808,13 @@
             transferHandoverConnections(mForegroundCall);
             transferHandoverConnections(mBackgroundCall);
             transferHandoverConnections(mRingingCall);
+            updatePhoneState();
         }
     }
 
     private void resetState() {
         mIsInEmergencyCall = false;
+        mPhone.setEcmCanceledForEmergency(false);
     }
 
     //****** Overridden from Handler
@@ -3396,8 +3953,7 @@
                 Pair<ImsCall, ImsReasonInfo> callInfo =
                         (Pair<ImsCall, ImsReasonInfo>) ((AsyncResult) msg.obj).userObj;
                 removeMessages(EVENT_REDIAL_WIFI_E911_TIMEOUT);
-                mPhone.getDefaultPhone().getServiceStateTracker()
-                        .unregisterForNetworkAttached(this);
+                mPhone.getDefaultPhone().mCi.unregisterForOn(this);
                 ImsPhoneConnection oldConnection = findConnection(callInfo.first);
                 if (oldConnection == null) {
                     sendCallStartFailedDisconnect(callInfo.first, callInfo.second);
@@ -3409,6 +3965,15 @@
                     Connection newConnection =
                             mPhone.getDefaultPhone().dial(mLastDialString, mLastDialArgs);
                     oldConnection.onOriginalConnectionReplaced(newConnection);
+
+                    final ImsCall imsCall = mForegroundCall.getImsCall();
+                    final ImsCallProfile callProfile = imsCall.getCallProfile();
+                    /* update EXTRA_EMERGENCY_CALL for clients to infer
+                       from this extra that the call is emergency call */
+                    callProfile.setCallExtraBoolean(
+                            ImsCallProfile.EXTRA_EMERGENCY_CALL, true);
+                    ImsPhoneConnection conn = findConnection(imsCall);
+                    conn.updateExtras(imsCall);
                 } catch (CallStateException e) {
                     sendCallStartFailedDisconnect(callInfo.first, callInfo.second);
                 }
@@ -3416,8 +3981,7 @@
             }
             case EVENT_REDIAL_WIFI_E911_TIMEOUT: {
                 Pair<ImsCall, ImsReasonInfo> callInfo = (Pair<ImsCall, ImsReasonInfo>) msg.obj;
-                mPhone.getDefaultPhone().getServiceStateTracker()
-                        .unregisterForNetworkAttached(this);
+                mPhone.getDefaultPhone().mCi.unregisterForOn(this);
                 removeMessages(EVENT_REDIAL_WIFI_E911_CALL);
                 sendCallStartFailedDisconnect(callInfo.first, callInfo.second);
                 break;
@@ -3437,7 +4001,12 @@
                     mPendingMO = null;
                     ImsDialArgs newDialArgs = ImsDialArgs.Builder.from(mLastDialArgs)
                             .setRttTextStream(null)
+                            .setRetryCallFailCause(ImsReasonInfo.CODE_RETRY_ON_IMS_WITHOUT_RTT)
+                            .setRetryCallFailNetworkType(
+                                    ServiceState.rilRadioTechnologyToNetworkType(
+                                    oldConnection.getCallRadioTech()))
                             .build();
+
                     Connection newConnection =
                             mPhone.getDefaultPhone().dial(mLastDialString, newDialArgs);
                     oldConnection.onOriginalConnectionReplaced(newConnection);
@@ -3455,7 +4024,8 @@
      * @param call The IMS call
      * @param dataUsage The aggregated data usage for the call
      */
-    private void updateVtDataUsage(ImsCall call, long dataUsage) {
+    @VisibleForTesting(visibility = PRIVATE)
+    public void updateVtDataUsage(ImsCall call, long dataUsage) {
         long oldUsage = 0L;
         if (mVtDataUsageMap.containsKey(call.uniqueId)) {
             oldUsage = mVtDataUsageMap.get(call.uniqueId);
@@ -3471,12 +4041,12 @@
 
         // Create the snapshot of total video call data usage.
         NetworkStats vtDataUsageSnapshot = new NetworkStats(currentTime, 1);
-        vtDataUsageSnapshot.combineAllValues(mVtDataUsageSnapshot);
+        vtDataUsageSnapshot = vtDataUsageSnapshot.add(mVtDataUsageSnapshot);
         // Since the modem only reports the total vt data usage rather than rx/tx separately,
         // the only thing we can do here is splitting the usage into half rx and half tx.
         // Uid -1 indicates this is for the overall device data usage.
         vtDataUsageSnapshot.combineValues(new NetworkStats.Entry(
-                NetworkStatsService.VT_INTERFACE, -1, NetworkStats.SET_FOREGROUND,
+                NetworkStats.IFACE_VT, -1, NetworkStats.SET_FOREGROUND,
                 NetworkStats.TAG_NONE, NetworkStats.METERED_YES, isRoaming,
                 NetworkStats.DEFAULT_NETWORK_YES, delta / 2, 0, delta / 2, 0, 0));
         mVtDataUsageSnapshot = vtDataUsageSnapshot;
@@ -3499,7 +4069,7 @@
         // Since the modem only reports the total vt data usage rather than rx/tx separately,
         // the only thing we can do here is splitting the usage into half rx and half tx.
         vtDataUsageUidSnapshot.combineValues(new NetworkStats.Entry(
-                NetworkStatsService.VT_INTERFACE, mDefaultDialerUid.get(),
+                NetworkStats.IFACE_VT, mDefaultDialerUid.get(),
                 NetworkStats.SET_FOREGROUND, NetworkStats.TAG_NONE, NetworkStats.METERED_YES,
                 isRoaming, NetworkStats.DEFAULT_NETWORK_YES, delta / 2, 0, delta / 2, 0, 0));
         mVtDataUsageUidSnapshot = vtDataUsageUidSnapshot;
@@ -3576,9 +4146,12 @@
     }
 
     @Override
-    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+    public void dump(FileDescriptor fd, PrintWriter printWriter, String[] args) {
+        IndentingPrintWriter pw = new IndentingPrintWriter(printWriter, "  ");
         pw.println("ImsPhoneCallTracker extends:");
+        pw.increaseIndent();
         super.dump(fd, pw, args);
+        pw.decreaseIndent();
         pw.println(" mVoiceCallEndedRegistrants=" + mVoiceCallEndedRegistrants);
         pw.println(" mVoiceCallStartedRegistrants=" + mVoiceCallStartedRegistrants);
         pw.println(" mRingingCall=" + mRingingCall);
@@ -3586,7 +4159,6 @@
         pw.println(" mBackgroundCall=" + mBackgroundCall);
         pw.println(" mHandoverCall=" + mHandoverCall);
         pw.println(" mPendingMO=" + mPendingMO);
-        //pw.println(" mHangupPendingMO=" + mHangupPendingMO);
         pw.println(" mPhone=" + mPhone);
         pw.println(" mDesiredMute=" + mDesiredMute);
         pw.println(" mState=" + mState);
@@ -3596,7 +4168,12 @@
         pw.println(" mVtDataUsageUidSnapshot=" + mVtDataUsageUidSnapshot);
         pw.println(" mCallQualityMetrics=" + mCallQualityMetrics);
         pw.println(" mCallQualityMetricsHistory=" + mCallQualityMetricsHistory);
-
+        pw.println(" mIsConferenceEventPackageHandlingEnabled=" + mIsConferenceEventPackageEnabled);
+        pw.println(" mSupportCepOnPeer=" + mSupportCepOnPeer);
+        pw.println(" Event Log:");
+        pw.increaseIndent();
+        mOperationLocalLog.dump(pw);
+        pw.decreaseIndent();
         pw.flush();
         pw.println("++++++++++++++++++++++++++++++++");
 
@@ -3620,6 +4197,7 @@
     protected void handlePollCalls(AsyncResult ar) {
     }
 
+    @UnsupportedAppUsage
     /* package */
     ImsEcbm getEcbmInterface() throws ImsException {
         if (mImsManager == null) {
@@ -3653,31 +4231,37 @@
     }
 
     /**
+     * Contacts the ImsService directly for capability information.  May be slow.
      * @return true if the IMS capability for the specified registration technology is currently
      * available.
      */
-    public boolean isImsCapabilityAvailable(int capability, int regTech) {
-        return (getImsRegistrationTech() == regTech) && mMmTelCapabilities.isCapable(capability);
+    public boolean isImsCapabilityAvailable(int capability, int regTech) throws ImsException {
+        if (mImsManager != null) {
+            return mImsManager.queryMmTelCapabilityStatus(capability, regTech);
+        } else {
+            return false;
+        }
     }
 
     public boolean isVolteEnabled() {
-        boolean isRadioTechLte = getImsRegistrationTech()
-                == ImsRegistrationImplBase.REGISTRATION_TECH_LTE;
-        return isRadioTechLte && mMmTelCapabilities.isCapable(
-                MmTelFeature.MmTelCapabilities.CAPABILITY_TYPE_VOICE);
+        return isImsCapabilityInCacheAvailable(MmTelFeature.MmTelCapabilities.CAPABILITY_TYPE_VOICE,
+                ImsRegistrationImplBase.REGISTRATION_TECH_LTE);
     }
 
     public boolean isVowifiEnabled() {
-        boolean isRadioTechIwlan = getImsRegistrationTech()
-                == ImsRegistrationImplBase.REGISTRATION_TECH_IWLAN;
-        return isRadioTechIwlan && mMmTelCapabilities.isCapable(
-                MmTelFeature.MmTelCapabilities.CAPABILITY_TYPE_VOICE);
+        return isImsCapabilityInCacheAvailable(MmTelFeature.MmTelCapabilities.CAPABILITY_TYPE_VOICE,
+                ImsRegistrationImplBase.REGISTRATION_TECH_IWLAN);
     }
 
     public boolean isVideoCallEnabled() {
+        // Currently no reliance on transport technology.
         return mMmTelCapabilities.isCapable(MmTelFeature.MmTelCapabilities.CAPABILITY_TYPE_VIDEO);
     }
 
+    private boolean isImsCapabilityInCacheAvailable(int capability, int regTech) {
+        return (getImsRegistrationTech() == regTech) && mMmTelCapabilities.isCapable(capability);
+    }
+
     @Override
     public PhoneConstants.State getState() {
         return mState;
@@ -3690,6 +4274,17 @@
         return ImsRegistrationImplBase.REGISTRATION_TECH_NONE;
     }
 
+    /**
+     * Asynchronously gets the IMS registration technology for MMTEL.
+     */
+    public void getImsRegistrationTech(Consumer<Integer> callback) {
+        if (mImsManager != null) {
+            mImsManager.getRegistrationTech(callback);
+        } else {
+            callback.accept(ImsRegistrationImplBase.REGISTRATION_TECH_NONE);
+        }
+    }
+
     private void retryGetImsService() {
         // The binder connection is already up. Do not try to get it again.
         if (mImsManager.isServiceAvailable()) {
@@ -3699,6 +4294,7 @@
         mImsManagerConnector.connect();
     }
 
+    @UnsupportedAppUsage
     private void setVideoCallProvider(ImsPhoneConnection conn, ImsCall imsCall)
             throws RemoteException {
         IImsVideoCallProvider imsVideoCallProvider =
@@ -3721,6 +4317,7 @@
     }
 
     public boolean isUtEnabled() {
+        // Currently no reliance on transport technology
         return mMmTelCapabilities.isCapable(MmTelFeature.MmTelCapabilities.CAPABILITY_TYPE_UT);
     }
 
@@ -3857,29 +4454,6 @@
         return isActiveCallVideo && isActiveCallOnWifi && isIncomingCallAudio && !isVoWifiEnabled;
     }
 
-    /**
-     * Get aggregated video call data usage since boot.
-     *
-     * @param perUidStats True if requesting data usage per uid, otherwise overall usage.
-     * @return Snapshot of video call data usage
-     */
-    public NetworkStats getVtDataUsage(boolean perUidStats) {
-
-        // If there is an ongoing VT call, request the latest VT usage from the modem. The latest
-        // usage will return asynchronously so it won't be counted in this round, but it will be
-        // eventually counted when next getVtDataUsage is called.
-        if (mState != PhoneConstants.State.IDLE) {
-            for (ImsPhoneConnection conn : mConnections) {
-                android.telecom.Connection.VideoProvider videoProvider = conn.getVideoProvider();
-                if (videoProvider != null) {
-                    videoProvider.onRequestConnectionDataUsage();
-                }
-            }
-        }
-
-        return perUidStats ? mVtDataUsageUidSnapshot : mVtDataUsageSnapshot;
-    }
-
     public void registerPhoneStateListener(PhoneStateListener listener) {
         mPhoneStateListeners.add(listener);
     }
@@ -4080,7 +4654,10 @@
         log("Resetting Capabilities...");
         boolean tmpIsVideoCallEnabled = isVideoCallEnabled();
         mMmTelCapabilities = new MmTelFeature.MmTelCapabilities();
-
+        mPhone.setServiceState(ServiceState.STATE_OUT_OF_SERVICE);
+        mPhone.resetImsRegistrationState();
+        mPhone.processDisconnectReason(new ImsReasonInfo(ImsReasonInfo.CODE_LOCAL_IMS_SERVICE_DOWN,
+                ImsReasonInfo.CODE_UNSPECIFIED));
         boolean isVideoEnabled = isVideoCallEnabled();
         if (tmpIsVideoCallEnabled != isVideoEnabled) {
             mPhone.notifyForVideoCapabilityChanged(isVideoEnabled);
@@ -4114,10 +4691,8 @@
                 .getSystemService(Context.CONNECTIVITY_SERVICE);
         if (cm != null) {
             Rlog.i(LOG_TAG, "registerForConnectivityChanges");
-            NetworkCapabilities capabilities = new NetworkCapabilities();
-            capabilities.addTransportType(NetworkCapabilities.TRANSPORT_WIFI);
             NetworkRequest.Builder builder = new NetworkRequest.Builder();
-            builder.setCapabilities(capabilities);
+            builder.addTransportType(NetworkCapabilities.TRANSPORT_WIFI);
             cm.registerNetworkCallback(builder.build(), mNetworkCallback);
             mIsMonitoringConnectivity = true;
         }
@@ -4205,12 +4780,14 @@
 
         if (DBG) log(sb.toString());
 
+        String logMessage = "handleFeatureCapabilityChanged: isVolteEnabled=" + isVolteEnabled()
+                + ", isVideoCallEnabled=" + isVideoCallEnabled()
+                + ", isVowifiEnabled=" + isVowifiEnabled()
+                + ", isUtEnabled=" + isUtEnabled();
         if (DBG) {
-            log("handleFeatureCapabilityChanged: isVolteEnabled=" + isVolteEnabled()
-                    + ", isVideoCallEnabled=" + isVideoCallEnabled()
-                    + ", isVowifiEnabled=" + isVowifiEnabled()
-                    + ", isUtEnabled=" + isUtEnabled());
+            log(logMessage);
         }
+        mRegLocalLog.log(logMessage);
 
         mPhone.onFeatureCapabilityChanged();
 
@@ -4276,4 +4853,47 @@
     public ImsPhone getPhone() {
         return mPhone;
     }
+
+    @VisibleForTesting
+    public void setSupportCepOnPeer(boolean isSupported) {
+        mSupportCepOnPeer = isSupported;
+    }
+
+    /**
+     * Injects a test conference state into an ongoing IMS call.
+     * @param state The injected state.
+     */
+    public void injectTestConferenceState(@NonNull ImsConferenceState state) {
+        List<ConferenceParticipant> participants = ImsCall.parseConferenceState(state);
+        for (ImsPhoneConnection connection : getConnections()) {
+            connection.updateConferenceParticipants(participants);
+        }
+    }
+
+    /**
+     * Sets whether CEP handling is enabled or disabled.
+     * @param isEnabled
+     */
+    public void setConferenceEventPackageEnabled(boolean isEnabled) {
+        log("setConferenceEventPackageEnabled isEnabled=" + isEnabled);
+        mIsConferenceEventPackageEnabled = isEnabled;
+    }
+
+    /**
+     * @return {@code true} is conference event package handling is enabled, {@code false}
+     * otherwise.
+     */
+    public boolean isConferenceEventPackageEnabled() {
+        return mIsConferenceEventPackageEnabled;
+    }
+
+    @VisibleForTesting
+    public ImsCall.Listener getImsCallListener() {
+        return mImsCallListener;
+    }
+
+    @VisibleForTesting
+    public ArrayList<ImsPhoneConnection> getConnections() {
+        return mConnections;
+    }
 }
diff --git a/src/java/com/android/internal/telephony/imsphone/ImsPhoneCommandInterface.java b/src/java/com/android/internal/telephony/imsphone/ImsPhoneCommandInterface.java
index 5ebd904..9dcb06f 100644
--- a/src/java/com/android/internal/telephony/imsphone/ImsPhoneCommandInterface.java
+++ b/src/java/com/android/internal/telephony/imsphone/ImsPhoneCommandInterface.java
@@ -23,6 +23,7 @@
 import android.os.Message;
 import android.telephony.ImsiEncryptionInfo;
 import android.telephony.NetworkScanRequest;
+import android.telephony.SignalThresholdInfo;
 import android.telephony.data.DataProfile;
 import android.telephony.emergency.EmergencyNumber;
 
@@ -30,6 +31,7 @@
 import com.android.internal.telephony.CommandsInterface;
 import com.android.internal.telephony.RadioCapability;
 import com.android.internal.telephony.UUSInfo;
+import com.android.internal.telephony.uicc.IccCardApplicationStatus.PersoSubState;
 import com.android.internal.telephony.cdma.CdmaSmsBroadcastConfigInfo;
 import com.android.internal.telephony.gsm.SmsBroadcastConfigInfo;
 
@@ -91,6 +93,11 @@
     }
 
     @Override
+    public void supplySimDepersonalization(PersoSubState persoType,
+            String controlKey, Message result) {
+    }
+
+    @Override
     public void getCurrentCalls(Message result) {
     }
 
@@ -240,6 +247,10 @@
     }
 
     @Override
+    public void sendCdmaSMSExpectMore(byte[] pdu, Message result) {
+    }
+
+    @Override
     public void sendImsGsmSms (String smscPDU, String pdu,
             int retry, int messageRef, Message response) {
     }
@@ -266,7 +277,7 @@
     }
 
     @Override
-    public void writeSmsToRuim(int status, String pdu, Message response) {
+    public void writeSmsToRuim(int status, byte[] pdu, Message response) {
     }
 
     @Override
@@ -333,8 +344,7 @@
     }
 
     @Override
-    public void setNetworkSelectionModeManual(
-            String operatorNumeric, Message response) {
+    public void setNetworkSelectionModeManual(String operatorNumeric, int ran, Message response) {
     }
 
     @Override
@@ -635,8 +645,8 @@
     }
 
     @Override
-    public void setSignalStrengthReportingCriteria(int hysteresisMs, int hysteresisDb,
-            int[] thresholdsDbm, int ran, Message result) {
+    public void setSignalStrengthReportingCriteria(
+            SignalThresholdInfo signalThresholdInfo, int ran, Message result) {
     }
 
     @Override
diff --git a/src/java/com/android/internal/telephony/imsphone/ImsPhoneConnection.java b/src/java/com/android/internal/telephony/imsphone/ImsPhoneConnection.java
old mode 100644
new mode 100755
index e33bd6d..7d4a36f
--- a/src/java/com/android/internal/telephony/imsphone/ImsPhoneConnection.java
+++ b/src/java/com/android/internal/telephony/imsphone/ImsPhoneConnection.java
@@ -16,7 +16,7 @@
 
 package com.android.internal.telephony.imsphone;
 
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
 import android.content.Context;
 import android.net.Uri;
 import android.os.AsyncResult;
@@ -33,8 +33,8 @@
 import android.telephony.CarrierConfigManager;
 import android.telephony.DisconnectCause;
 import android.telephony.PhoneNumberUtils;
-import android.telephony.Rlog;
 import android.telephony.ServiceState;
+import android.telephony.TelephonyManager;
 import android.telephony.ims.ImsCallProfile;
 import android.telephony.ims.ImsStreamMediaProfile;
 import android.text.TextUtils;
@@ -47,7 +47,9 @@
 import com.android.internal.telephony.Phone;
 import com.android.internal.telephony.PhoneConstants;
 import com.android.internal.telephony.UUSInfo;
+import com.android.internal.telephony.emergency.EmergencyNumberTracker;
 import com.android.internal.telephony.metrics.TelephonyMetrics;
+import com.android.telephony.Rlog;
 
 import java.util.Objects;
 
@@ -66,6 +68,7 @@
     private ImsPhoneCallTracker mOwner;
     @UnsupportedAppUsage
     private ImsPhoneCall mParent;
+    @UnsupportedAppUsage
     private ImsCall mImsCall;
     private Bundle mExtras = new Bundle();
     private TelephonyMetrics mMetrics = TelephonyMetrics.getInstance();
@@ -121,6 +124,8 @@
      */
     private boolean mIsMergeInProcess = false;
 
+    private String mVendorCause;
+
     /**
      * Used as an override to determine whether video is locally available for this call.
      * This allows video availability to be overridden in the case that the modem says video is
@@ -129,9 +134,6 @@
      */
     private boolean mIsLocalVideoCapable = true;
 
-    // Store the current audio codec
-    private int mAudioCodec = ImsStreamMediaProfile.AUDIO_QUALITY_NONE;
-
     //***** Event Constants
     private static final int EVENT_DTMF_DONE = 1;
     private static final int EVENT_PAUSE_DONE = 2;
@@ -184,6 +186,7 @@
         mHandler = new MyHandler(mOwner.getLooper());
         mHandlerMessenger = new Messenger(mHandler);
         mImsCall = imsCall;
+        mIsAdhocConference = isMultiparty();
 
         if ((imsCall != null) && (imsCall.getCallProfile() != null)) {
             mAddress = imsCall.getCallProfile().getCallExtra(ImsCallProfile.EXTRA_OI);
@@ -192,6 +195,8 @@
                     imsCall.getCallProfile().getCallExtraInt(ImsCallProfile.EXTRA_OIR));
             mCnapNamePresentation = ImsCallProfile.OIRToPresentation(
                     imsCall.getCallProfile().getCallExtraInt(ImsCallProfile.EXTRA_CNAP));
+            setNumberVerificationStatus(toTelecomVerificationStatus(
+                    imsCall.getCallProfile().getCallerNumberVerificationStatus()));
             updateMediaCapabilities(imsCall);
         } else {
             mNumberPresentation = PhoneConstants.PRESENTATION_UNKNOWN;
@@ -259,6 +264,37 @@
         }
     }
 
+    /** This is an MO conference call, created when dialing */
+    public ImsPhoneConnection(Phone phone, String[] participantsToDial, ImsPhoneCallTracker ct,
+            ImsPhoneCall parent, boolean isEmergency) {
+        super(PhoneConstants.PHONE_TYPE_IMS);
+        createWakeLock(phone.getContext());
+        acquireWakeLock();
+
+        mOwner = ct;
+        mHandler = new MyHandler(mOwner.getLooper());
+        mHandlerMessenger = new Messenger(mHandler);
+
+        mDialString = mAddress = Connection.ADHOC_CONFERENCE_ADDRESS;
+        mParticipantsToDial = participantsToDial;
+        mIsAdhocConference = true;
+
+        mIsIncoming = false;
+        mCnapName = null;
+        mCnapNamePresentation = PhoneConstants.PRESENTATION_ALLOWED;
+        mNumberPresentation = PhoneConstants.PRESENTATION_ALLOWED;
+        mCreateTime = System.currentTimeMillis();
+
+        mParent = parent;
+        parent.attachFake(this, ImsPhoneCall.State.DIALING);
+
+        if (phone.getContext().getResources().getBoolean(
+                com.android.internal.R.bool.config_use_voip_mode_for_ims)) {
+            setAudioModeIsVoip(true);
+        }
+    }
+
+
     public void dispose() {
     }
 
@@ -340,12 +376,23 @@
     }
 
     public void setDisconnectCause(int cause) {
+        Rlog.d(LOG_TAG, "setDisconnectCause: cause=" + cause);
         mCause = cause;
     }
 
+    /** Get the disconnect cause for connection*/
+    public int getDisconnectCause() {
+        Rlog.d(LOG_TAG, "getDisconnectCause: cause=" + mCause);
+        return mCause;
+    }
+
+    public boolean isIncomingCallAutoRejected() {
+        return mCause == DisconnectCause.INCOMING_AUTO_REJECTED ? true : false;
+    }
+
     @Override
     public String getVendorDisconnectCause() {
-      return null;
+      return mVendorCause;
     }
 
     @UnsupportedAppUsage
@@ -380,6 +427,32 @@
     }
 
     @Override
+    public void transfer(String number, boolean isConfirmationRequired) throws CallStateException {
+        try {
+            if (mImsCall != null) {
+                mImsCall.transfer(number, isConfirmationRequired);
+            } else {
+                throw new CallStateException("no valid ims call to transfer");
+            }
+        } catch (ImsException e) {
+            throw new CallStateException("cannot transfer call");
+        }
+    }
+
+    @Override
+    public void consultativeTransfer(Connection other) throws CallStateException {
+        try {
+            if (mImsCall != null) {
+                mImsCall.consultativeTransfer(((ImsPhoneConnection) other).getImsCall());
+            } else {
+                throw new CallStateException("no valid ims call to transfer");
+            }
+        } catch (ImsException e) {
+            throw new CallStateException("cannot transfer call");
+        }
+    }
+
+    @Override
     public void hangup() throws CallStateException {
         if (!mDisconnected) {
             mOwner.hangup(this);
@@ -442,6 +515,7 @@
     void
     onHangupLocal() {
         mCause = DisconnectCause.LOCAL;
+        mVendorCause = null;
     }
 
     /** Called when the connection has been disconnected */
@@ -479,12 +553,20 @@
                 }
                 if (mImsCall != null) mImsCall.close();
                 mImsCall = null;
+                if (mImsVideoCallProviderWrapper != null) {
+                    mImsVideoCallProviderWrapper.tearDown();
+                }
             }
         }
         releaseWakeLock();
         return changed;
     }
 
+    void
+    onRemoteDisconnect(String vendorCause) {
+        this.mVendorCause = vendorCause;
+    }
+
     /**
      * An incoming or outgoing call has connected
      */
@@ -719,6 +801,7 @@
      * @return {@code true} if the {@link ImsPhoneConnection} or its media capabilities have been
      *     changed, and {@code false} otherwise.
      */
+    @UnsupportedAppUsage
     public boolean update(ImsCall imsCall, ImsPhoneCall.State state) {
         if (state == ImsPhoneCall.State.ACTIVE) {
             // If the state of the call is active, but there is a pending request to the RIL to hold
@@ -930,11 +1013,13 @@
                         startRttTextProcessing();
                         onRttInitiated();
                         changed = true;
+                        mOwner.getPhone().getVoiceCallSessionStats().onRttStarted(this);
                     } else if (!mIsRttEnabledForCall && mRttTextHandler != null) {
                         Rlog.d(LOG_TAG, "updateMediaCapabilities -- turning RTT off, profile="
                                 + negotiatedCallProfile);
                         mRttTextHandler.tearDown();
                         mRttTextHandler = null;
+                        mRttTextStream = null;
                         onRttTerminated();
                         changed = true;
                     }
@@ -988,6 +1073,7 @@
                     && localCallProfile.mMediaProfile.mAudioQuality != mAudioCodec) {
                 mAudioCodec = localCallProfile.mMediaProfile.mAudioQuality;
                 mMetrics.writeAudioCodecIms(mOwner.mPhone.getPhoneId(), imsCall.getCallSession());
+                mOwner.getPhone().getVoiceCallSessionStats().onAudioCodecChanged(this, mAudioCodec);
             }
 
             int newAudioQuality =
@@ -1084,6 +1170,20 @@
         }
     }
 
+    /**
+     * Get the corresponding EmergencyNumberTracker associated with the connection.
+     * @return the EmergencyNumberTracker
+     */
+    public EmergencyNumberTracker getEmergencyNumberTracker() {
+        if (mOwner != null) {
+            Phone phone = mOwner.getPhone();
+            if (phone != null) {
+                return phone.getEmergencyNumberTracker();
+            }
+        }
+        return null;
+    }
+
     public boolean hasRttTextStream() {
         return mRttTextStream != null;
     }
@@ -1124,17 +1224,18 @@
      * @param extras The ImsCallProfile extras.
      */
     private void updateImsCallRatFromExtras(Bundle extras) {
-        if (extras.containsKey(ImsCallProfile.EXTRA_CALL_RAT_TYPE) ||
-                extras.containsKey(ImsCallProfile.EXTRA_CALL_RAT_TYPE_ALT)) {
+        if (extras.containsKey(ImsCallProfile.EXTRA_CALL_NETWORK_TYPE)
+                || extras.containsKey(ImsCallProfile.EXTRA_CALL_RAT_TYPE)
+                || extras.containsKey(ImsCallProfile.EXTRA_CALL_RAT_TYPE_ALT)) {
 
             ImsCall call = getImsCall();
-            int callTech = ServiceState.RIL_RADIO_TECHNOLOGY_UNKNOWN;
+            int networkType = TelephonyManager.NETWORK_TYPE_UNKNOWN;
             if (call != null) {
-                callTech = call.getRadioTechnology();
+                networkType = call.getNetworkType();
             }
 
-            // Report any changes for call tech change
-            setCallRadioTech(callTech);
+            // Report any changes for network type change
+            setCallRadioTech(ServiceState.networkTypeToRilRadioTechnology(networkType));
         }
     }
 
@@ -1242,6 +1343,8 @@
         sb.append(getTelecomCallId());
         sb.append(" address: ");
         sb.append(Rlog.pii(LOG_TAG, getAddress()));
+        sb.append(" isAdhocConf: ");
+        sb.append(isAdhocConference() ? "Y" : "N");
         sb.append(" ImsCall: ");
         synchronized (this) {
             if (mImsCall == null) {
@@ -1395,4 +1498,24 @@
                 + "; updating local video availability.");
         updateMediaCapabilities(getImsCall());
     }
+
+    /**
+     * Converts an {@link ImsCallProfile} verification status to a
+     * {@link android.telecom.Connection} verification status.
+     * @param verificationStatus The {@link ImsCallProfile} verification status.
+     * @return The telecom verification status.
+     */
+    public static @android.telecom.Connection.VerificationStatus int toTelecomVerificationStatus(
+            @ImsCallProfile.VerificationStatus int verificationStatus) {
+        switch (verificationStatus) {
+            case ImsCallProfile.VERIFICATION_STATUS_PASSED:
+                return android.telecom.Connection.VERIFICATION_STATUS_PASSED;
+            case ImsCallProfile.VERIFICATION_STATUS_FAILED:
+                return android.telecom.Connection.VERIFICATION_STATUS_FAILED;
+            case ImsCallProfile.VERIFICATION_STATUS_NOT_VERIFIED:
+                // fall through on purpose
+            default:
+                return android.telecom.Connection.VERIFICATION_STATUS_NOT_VERIFIED;
+        }
+    }
 }
diff --git a/src/java/com/android/internal/telephony/imsphone/ImsPhoneFactory.java b/src/java/com/android/internal/telephony/imsphone/ImsPhoneFactory.java
index 9f81a69..8f82328 100644
--- a/src/java/com/android/internal/telephony/imsphone/ImsPhoneFactory.java
+++ b/src/java/com/android/internal/telephony/imsphone/ImsPhoneFactory.java
@@ -16,11 +16,11 @@
 
 package com.android.internal.telephony.imsphone;
 
+import android.content.Context;
+
 import com.android.internal.telephony.Phone;
 import com.android.internal.telephony.PhoneNotifier;
-
-import android.content.Context;
-import android.telephony.Rlog;
+import com.android.telephony.Rlog;
 
 /**
  * {@hide}
diff --git a/src/java/com/android/internal/telephony/imsphone/ImsPhoneMmiCode.java b/src/java/com/android/internal/telephony/imsphone/ImsPhoneMmiCode.java
index b1d524d..e2b1534 100644
--- a/src/java/com/android/internal/telephony/imsphone/ImsPhoneMmiCode.java
+++ b/src/java/com/android/internal/telephony/imsphone/ImsPhoneMmiCode.java
@@ -16,6 +16,8 @@
 
 package com.android.internal.telephony.imsphone;
 
+import static android.telephony.ServiceState.STATE_IN_SERVICE;
+
 import static com.android.internal.telephony.CommandsInterface.SERVICE_CLASS_DATA;
 import static com.android.internal.telephony.CommandsInterface.SERVICE_CLASS_DATA_ASYNC;
 import static com.android.internal.telephony.CommandsInterface.SERVICE_CLASS_DATA_SYNC;
@@ -27,32 +29,33 @@
 import static com.android.internal.telephony.CommandsInterface.SERVICE_CLASS_SMS;
 import static com.android.internal.telephony.CommandsInterface.SERVICE_CLASS_VOICE;
 
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
 import android.content.Context;
 import android.content.res.Resources;
 import android.os.AsyncResult;
-import android.os.Bundle;
 import android.os.Handler;
 import android.os.Message;
 import android.os.ResultReceiver;
 import android.telephony.PhoneNumberUtils;
-import android.telephony.Rlog;
 import android.telephony.ims.ImsCallForwardInfo;
 import android.telephony.ims.ImsReasonInfo;
 import android.telephony.ims.ImsSsData;
 import android.telephony.ims.ImsSsInfo;
+import android.telephony.ims.ImsUtListener;
+import android.telephony.ims.stub.ImsUtImplBase;
 import android.text.SpannableStringBuilder;
 import android.text.TextUtils;
 
 import com.android.ims.ImsException;
-import com.android.ims.ImsUtInterface;
 import com.android.internal.telephony.CallForwardInfo;
 import com.android.internal.telephony.CallStateException;
 import com.android.internal.telephony.CommandException;
 import com.android.internal.telephony.CommandsInterface;
 import com.android.internal.telephony.MmiCode;
 import com.android.internal.telephony.Phone;
+import com.android.internal.telephony.gsm.GsmMmiCode;
 import com.android.internal.telephony.uicc.IccRecords;
+import com.android.telephony.Rlog;
 
 import java.util.Arrays;
 import java.util.List;
@@ -155,7 +158,19 @@
     //***** Supplementary Service Query Bundle Keys
     // Used by IMS Service layer to put supp. serv. query
     // responses into the ssInfo Bundle.
+    /**
+     * @deprecated Use {@link ImsUtListener#onLineIdentificationSupplementaryServiceResponse(int,
+     * ImsSsInfo)} API instead.
+     */
+    @Deprecated
+    // Not used, only kept around to not break vendors using this key.
     public static final String UT_BUNDLE_KEY_CLIR = "queryClir";
+    /**
+     * @deprecated Use {@link ImsUtListener#onLineIdentificationSupplementaryServiceResponse(int,
+     * ImsSsInfo)} API instead.
+     */
+    @Deprecated
+    // Not used, only kept around to not break vendors using this key.
     public static final String UT_BUNDLE_KEY_SSINFO = "imsSsInfo";
 
     //***** Instance Variables
@@ -179,6 +194,9 @@
     private boolean mIsUssdRequest;
 
     private boolean mIsCallFwdReg;
+
+    private boolean mIsNetworkInitiatedUSSD;
+
     private State mState = State.PENDING;
     private CharSequence mMessage;
     private boolean mIsSsInfo = false;
@@ -282,7 +300,8 @@
 
             ret = new ImsPhoneMmiCode(phone);
             ret.mPoundString = dialString;
-        } else if (isTwoDigitShortCode(phone.getContext(), dialString)) {
+        } else if (GsmMmiCode.isTwoDigitShortCode(phone.getContext(), phone.getSubId(),
+                dialString)) {
             //Is a country-specific exception to short codes as defined in TS 22.030, 6.5.3.2
             ret = null;
         } else if (isShortCode(dialString, phone)) {
@@ -321,6 +340,7 @@
 
         ret.mMessage = ussdMessage;
         ret.mIsUssdRequest = isUssdRequest;
+        ret.mIsNetworkInitiatedUSSD = true;
 
         // If it's a request, set to PENDING so that it's cancelable.
         if (isUssdRequest) {
@@ -456,7 +476,7 @@
         Resources resource = Resources.getSystem();
         if (sc != null) {
             String[] barringMMI = resource.getStringArray(
-                com.android.internal.R.array.config_callBarringMMI);
+                com.android.internal.R.array.config_callBarringMMI_for_ims);
             if (barringMMI != null) {
                 for (String match : barringMMI) {
                     if (sc.equals(match)) return true;
@@ -466,6 +486,60 @@
         return false;
     }
 
+    static boolean isPinPukCommand(String sc) {
+        return sc != null && (sc.equals(SC_PIN) || sc.equals(SC_PIN2)
+                || sc.equals(SC_PUK) || sc.equals(SC_PUK2));
+    }
+
+    /**
+     * Whether the dial string is supplementary service code.
+     *
+     * @param dialString The dial string.
+     * @return true if the dial string is supplementary service code, and {@code false} otherwise.
+     */
+    public static boolean isSuppServiceCodes(String dialString, Phone phone) {
+        if (phone != null && phone.getServiceState().getVoiceRoaming()
+                && phone.getDefaultPhone().supportsConversionOfCdmaCallerIdMmiCodesWhileRoaming()) {
+            /* The CDMA MMI coded dialString will be converted to a 3GPP MMI Coded dialString
+               so that it can be processed by the matcher and code below
+             */
+            dialString = convertCdmaMmiCodesTo3gppMmiCodes(dialString);
+        }
+
+        Matcher m = sPatternSuppService.matcher(dialString);
+        if (m.matches()) {
+            String sc = makeEmptyNull(m.group(MATCH_GROUP_SERVICE_CODE));
+            if (isServiceCodeCallForwarding(sc)) {
+                return true;
+            } else if (isServiceCodeCallBarring(sc)) {
+                return true;
+            } else if (sc != null && sc.equals(SC_CFUT)) {
+                return true;
+            } else if (sc != null && sc.equals(SC_CLIP)) {
+                return true;
+            } else if (sc != null && sc.equals(SC_CLIR)) {
+                return true;
+            } else if (sc != null && sc.equals(SC_COLP)) {
+                return true;
+            } else if (sc != null && sc.equals(SC_COLR)) {
+                return true;
+            } else if (sc != null && sc.equals(SC_CNAP)) {
+                return true;
+            } else if (sc != null && sc.equals(SC_BS_MT)) {
+                return true;
+            } else if (sc != null && sc.equals(SC_BAICa)) {
+                return true;
+            } else if (sc != null && sc.equals(SC_PWD)) {
+                return true;
+            } else if (sc != null && sc.equals(SC_WAIT)) {
+                return true;
+            } else if (isPinPukCommand(sc)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
     static String
     scToBarringFacility(String sc) {
         if (sc == null) {
@@ -572,28 +646,6 @@
         return mPoundString;
     }
 
-    static private boolean
-    isTwoDigitShortCode(Context context, String dialString) {
-        Rlog.d(LOG_TAG, "isTwoDigitShortCode");
-
-        if (dialString == null || dialString.length() > 2) return false;
-
-        if (sTwoDigitNumberPattern == null) {
-            sTwoDigitNumberPattern = context.getResources().getStringArray(
-                    com.android.internal.R.array.config_twoDigitNumberPattern);
-        }
-
-        for (String dialnumber : sTwoDigitNumberPattern) {
-            Rlog.d(LOG_TAG, "Two Digit Number Pattern " + dialnumber);
-            if (dialString.equals(dialnumber)) {
-                Rlog.d(LOG_TAG, "Two Digit Number Pattern -true");
-                return true;
-            }
-        }
-        Rlog.d(LOG_TAG, "Two Digit Number Pattern -false");
-        return false;
-    }
-
     /**
      * Helper function for newFromDialString. Returns true if dialString appears
      * to be a short code AND conditions are correct for it to be treated as
@@ -653,8 +705,7 @@
      * @return true if the Service Code is PIN/PIN2/PUK/PUK2-related
      */
     public boolean isPinPukCommand() {
-        return mSc != null && (mSc.equals(SC_PIN) || mSc.equals(SC_PIN2)
-                              || mSc.equals(SC_PUK) || mSc.equals(SC_PUK2));
+        return isPinPukCommand(mSc);
     }
 
     /**
@@ -806,7 +857,7 @@
                 int time = siToTime(mSic);
 
                 if (isInterrogate()) {
-                    mPhone.getCallForwardingOption(reason,
+                    mPhone.getCallForwardingOption(reason, serviceClass,
                             obtainMessage(EVENT_QUERY_CF_COMPLETE, this));
                 } else {
                     int cfAction;
@@ -870,14 +921,16 @@
                 // NOTE: Since these supplementary services are accessed only
                 //       via MMI codes, methods have not been added to ImsPhone.
                 //       Only the UT interface handle is used.
-                if (isActivate()) {
+                if (isActivate()
+                        && !mPhone.getDefaultPhone().isClirActivationAndDeactivationPrevented()) {
                     try {
                         mPhone.mCT.getUtInterface().updateCLIR(CommandsInterface.CLIR_INVOCATION,
                             obtainMessage(EVENT_SET_COMPLETE, this));
                     } catch (ImsException e) {
                         Rlog.d(LOG_TAG, "processCode: Could not get UT handle for updateCLIR.");
                     }
-                } else if (isDeactivate()) {
+                } else if (isDeactivate()
+                        && !mPhone.getDefaultPhone().isClirActivationAndDeactivationPrevented()) {
                     try {
                         mPhone.mCT.getUtInterface().updateCLIR(CommandsInterface.CLIR_SUPPRESSION,
                             obtainMessage(EVENT_SET_COMPLETE, this));
@@ -961,9 +1014,9 @@
             } else if (mSc != null && (mSc.equals(SC_BS_MT))) {
                 try {
                     if (isInterrogate()) {
-                        mPhone.mCT.getUtInterface()
-                        .queryCallBarring(ImsUtInterface.CB_BS_MT,
-                                          obtainMessage(EVENT_QUERY_ICB_COMPLETE,this));
+                        mPhone.mCT.getUtInterface().queryCallBarring(
+                                ImsUtImplBase.CALL_BARRING_SPECIFIC_INCOMING_CALLS,
+                                obtainMessage(EVENT_QUERY_ICB_COMPLETE, this));
                     } else {
                         processIcbMmiCodeForUpdate();
                     }
@@ -976,9 +1029,9 @@
                 // TODO: Should we route through queryCallBarring() here?
                 try {
                     if (isInterrogate()) {
-                        mPhone.mCT.getUtInterface()
-                        .queryCallBarring(ImsUtInterface.CB_BIC_ACR,
-                                          obtainMessage(EVENT_QUERY_ICB_COMPLETE,this));
+                        mPhone.mCT.getUtInterface().queryCallBarring(
+                                ImsUtImplBase.CALL_BARRING_ANONYMOUS_INCOMING,
+                                obtainMessage(EVENT_QUERY_ICB_COMPLETE, this));
                     } else {
                         if (isActivate()) {
                             callAction = CommandsInterface.CF_ACTION_ENABLE;
@@ -986,7 +1039,7 @@
                             callAction = CommandsInterface.CF_ACTION_DISABLE;
                         }
                         mPhone.mCT.getUtInterface()
-                                .updateCallBarring(ImsUtInterface.CB_BIC_ACR,
+                                .updateCallBarring(ImsUtImplBase.CALL_BARRING_ANONYMOUS_INCOMING,
                                 callAction,
                                 obtainMessage(EVENT_SET_COMPLETE,this),
                                 null);
@@ -996,7 +1049,7 @@
                 }
             } else if (mSc != null && mSc.equals(SC_WAIT)) {
                 // sia = basic service group
-                int serviceClass = siToServiceClass(mSib);
+                int serviceClass = siToServiceClass(mSia);
 
                 if (isActivate() || isDeactivate()) {
                     mPhone.setCallWaiting(isActivate(), serviceClass,
@@ -1007,11 +1060,28 @@
                     throw new RuntimeException ("Invalid or Unsupported MMI Code");
                 }
             } else if (mPoundString != null) {
-                // USSD codes are not supported over IMS due to modem limitations; send over the CS
-                // pipe instead.  This should be fixed in the future.
-                Rlog.i(LOG_TAG, "processCode: Sending ussd string '"
-                        + Rlog.pii(LOG_TAG, mPoundString) + "' over CS pipe.");
-                throw new CallStateException(Phone.CS_FALLBACK);
+                if (mContext.getResources().getBoolean(
+                        com.android.internal.R.bool.config_allow_ussd_over_ims)) {
+                    // We'll normally send USSD over the CS pipe, but if it happens that
+                    // the CS phone is out of service, we'll just try over IMS instead.
+                    if (mPhone.getDefaultPhone().getServiceStateTracker().mSS.getState()
+                            == STATE_IN_SERVICE) {
+                        Rlog.i(LOG_TAG, "processCode: Sending ussd string '"
+                                + Rlog.pii(LOG_TAG, mPoundString) + "' over CS pipe "
+                                + "(allowed over ims).");
+                        throw new CallStateException(Phone.CS_FALLBACK);
+                    } else {
+                        Rlog.i(LOG_TAG, "processCode: CS is out of service, sending ussd string '"
+                                + Rlog.pii(LOG_TAG, mPoundString) + "' over IMS pipe.");
+                        sendUssd(mPoundString);
+                    }
+                } else {
+                    // USSD codes are not supported over IMS due to modem limitations; send over
+                    // the CS pipe instead.  This should be fixed in the future.
+                    Rlog.i(LOG_TAG, "processCode: Sending ussd string '"
+                            + Rlog.pii(LOG_TAG, mPoundString) + "' over CS pipe.");
+                    throw new CallStateException(Phone.CS_FALLBACK);
+                }
             } else {
                 Rlog.d(LOG_TAG, "processCode: invalid or unsupported MMI");
                 throw new RuntimeException ("Invalid or Unsupported MMI Code");
@@ -1104,7 +1174,8 @@
                 if ((ar.exception == null) && (msg.arg1 == 1)) {
                     boolean cffEnabled = (msg.arg2 == 1);
                     if (mIccRecords != null) {
-                        mPhone.setVoiceCallForwardingFlag(1, cffEnabled, mDialingNumber);
+                        mPhone.setVoiceCallForwardingFlag(mIccRecords,
+                                1, cffEnabled, mDialingNumber);
                     }
                 }
 
@@ -1175,11 +1246,11 @@
         callAction = callBarAction(dialingNumber);
 
         try {
-            mPhone.mCT.getUtInterface()
-            .updateCallBarring(ImsUtInterface.CB_BS_MT,
-                               callAction,
-                               obtainMessage(EVENT_SET_COMPLETE,this),
-                               icbNum);
+            mPhone.mCT.getUtInterface().updateCallBarring(
+                    ImsUtImplBase.CALL_BARRING_SPECIFIC_INCOMING_CALLS,
+                    callAction,
+                    obtainMessage(EVENT_SET_COMPLETE, this),
+                    icbNum);
         } catch (ImsException e) {
             Rlog.d(LOG_TAG, "processIcbMmiCodeForUpdate:Could not get UT handle for updating ICB.");
         }
@@ -1400,9 +1471,7 @@
                 (info.serviceClass & serviceClassMask)
                         == CommandsInterface.SERVICE_CLASS_VOICE) {
             boolean cffEnabled = (info.status == 1);
-            if (mIccRecords != null) {
-                mPhone.setVoiceCallForwardingFlag(1, cffEnabled, info.number);
-            }
+            mPhone.setVoiceCallForwardingFlag(mIccRecords, 1, cffEnabled, info.number);
         }
 
         return TextUtils.replace(template, sources, destinations);
@@ -1433,9 +1502,7 @@
                 sb.append(mContext.getText(com.android.internal.R.string.serviceDisabled));
 
                 // Set unconditional CFF in SIM to false
-                if (mIccRecords != null) {
-                    mPhone.setVoiceCallForwardingFlag(1, false, null);
-                }
+                mPhone.setVoiceCallForwardingFlag(mIccRecords, 1, false, null);
             } else {
 
                 SpannableStringBuilder tb = new SpannableStringBuilder();
@@ -1482,12 +1549,10 @@
                 sb.append(getErrorMessage(ar));
             }
         } else {
-            ImsSsInfo ssInfo = null;
-            if (ar.result instanceof Bundle) {
+            if (ar.result instanceof ImsSsInfo) {
                 Rlog.d(LOG_TAG, "onSuppSvcQueryComplete: Received CLIP/COLP/COLR Response.");
                 // Response for CLIP, COLP and COLR queries.
-                Bundle ssInfoResp = (Bundle) ar.result;
-                ssInfo = (ImsSsInfo) ssInfoResp.getParcelable(UT_BUNDLE_KEY_SSINFO);
+                ImsSsInfo ssInfo = (ImsSsInfo) ar.result;
                 if (ssInfo != null) {
                     Rlog.d(LOG_TAG,
                             "onSuppSvcQueryComplete: ImsSsInfo mStatus = " + ssInfo.getStatus());
@@ -1582,15 +1647,14 @@
                 sb.append(getImsErrorMessage(ar));
             }
         } else {
-            Bundle ssInfo = (Bundle) ar.result;
-            int[] clirInfo = ssInfo.getIntArray(UT_BUNDLE_KEY_CLIR);
-            // clirInfo[0] = The 'n' parameter from TS 27.007 7.7
-            // clirInfo[1] = The 'm' parameter from TS 27.007 7.7
-            Rlog.d(LOG_TAG, "onQueryClirComplete: CLIR param n=" + clirInfo[0]
-                    + " m=" + clirInfo[1]);
+            ImsSsInfo ssInfo = (ImsSsInfo) ar.result;
+            // ssInfo.getClirOutgoingState() = The 'n' parameter from TS 27.007 7.7
+            // ssInfo.getClirInterrogationStatus() = The 'm' parameter from TS 27.007 7.7
+            Rlog.d(LOG_TAG, "onQueryClirComplete: CLIR param n=" + ssInfo.getClirOutgoingState()
+                    + " m=" + ssInfo.getClirInterrogationStatus());
 
             // 'm' parameter.
-            switch (clirInfo[1]) {
+            switch (ssInfo.getClirInterrogationStatus()) {
                 case ImsSsInfo.CLIR_STATUS_NOT_PROVISIONED:
                     sb.append(mContext.getText(
                             com.android.internal.R.string.serviceNotProvisioned));
@@ -1603,7 +1667,7 @@
                     break;
                 case ImsSsInfo.CLIR_STATUS_TEMPORARILY_RESTRICTED:
                     // 'n' parameter.
-                    switch (clirInfo[0]) {
+                    switch (ssInfo.getClirOutgoingState()) {
                         case ImsSsInfo.CLIR_OUTGOING_DEFAULT:
                             sb.append(mContext.getText(
                                     com.android.internal.R.string.CLIRDefaultOnNextCallOn));
@@ -1627,7 +1691,7 @@
                     break;
                 case ImsSsInfo.CLIR_STATUS_TEMPORARILY_ALLOWED:
                     // 'n' parameter.
-                    switch (clirInfo[0]) {
+                    switch (ssInfo.getClirOutgoingState()) {
                         case ImsSsInfo.CLIR_OUTGOING_DEFAULT:
                             sb.append(mContext.getText(
                                     com.android.internal.R.string.CLIRDefaultOffNextCallOff));
@@ -1666,9 +1730,8 @@
         StringBuilder sb = new StringBuilder(getScString());
         sb.append("\n");
 
+        mState = State.FAILED;
         if (ar.exception != null) {
-            mState = State.FAILED;
-
             if (ar.exception instanceof ImsException) {
                 sb.append(getImsErrorMessage(ar));
             } else {
@@ -1681,19 +1744,21 @@
             if (ints.length != 0) {
                 if (ints[0] == 0) {
                     sb.append(mContext.getText(com.android.internal.R.string.serviceDisabled));
+                    mState = State.COMPLETE;
                 } else if (mSc.equals(SC_WAIT)) {
                     // Call Waiting includes additional data in the response.
                     sb.append(createQueryCallWaitingResultMessage(ints[1]));
+                    mState = State.COMPLETE;
                 } else if (ints[0] == 1) {
                     // for all other services, treat it as a boolean
                     sb.append(mContext.getText(com.android.internal.R.string.serviceEnabled));
+                    mState = State.COMPLETE;
                 } else {
                     sb.append(mContext.getText(com.android.internal.R.string.mmiError));
                 }
             } else {
                 sb.append(mContext.getText(com.android.internal.R.string.mmiError));
             }
-            mState = State.COMPLETE;
         }
 
         mMessage = sb;
@@ -1785,8 +1850,7 @@
             case ImsSsData.SS_INTERROGATION:
                 if (ssData.isTypeClir()) {
                     Rlog.d(LOG_TAG, "CLIR INTERROGATION");
-                    Bundle clirInfo = new Bundle();
-                    clirInfo.putIntArray(UT_BUNDLE_KEY_CLIR, ssData.getSuppServiceInfoCompat());
+                    ImsSsInfo clirInfo = ssData.getSuppServiceInfo().get(0);
                     onQueryClirComplete(new AsyncResult(null, clirInfo, ex));
                 } else if (ssData.isTypeCF()) {
                     Rlog.d(LOG_TAG, "CALL FORWARD INTERROGATION");
@@ -1804,11 +1868,8 @@
                     onSuppSvcQueryComplete(new AsyncResult(null, ssData.getSuppServiceInfoCompat(),
                             ex));
                 } else if (ssData.isTypeColr() || ssData.isTypeClip() || ssData.isTypeColp()) {
-                    int[] suppServiceInfo = ssData.getSuppServiceInfoCompat();
-                    ImsSsInfo ssInfo = new ImsSsInfo.Builder(suppServiceInfo[0]).build();
-                    Bundle clInfo = new Bundle();
-                    clInfo.putParcelable(UT_BUNDLE_KEY_SSINFO, ssInfo);
-                    onSuppSvcQueryComplete(new AsyncResult(null, clInfo, ex));
+                    onSuppSvcQueryComplete(new AsyncResult(null, ssData.getSuppServiceInfo().get(0),
+                            ex));
                 } else if (ssData.isTypeIcb()) {
                     onIcbQueryComplete(new AsyncResult(null, ssData.getSuppServiceInfo(), ex));
                 } else {
@@ -1927,4 +1988,9 @@
         sb.append("}");
         return sb.toString();
     }
+
+    @Override
+    public boolean isNetworkInitiatedUssd() {
+        return mIsNetworkInitiatedUSSD;
+    }
 }
diff --git a/src/java/com/android/internal/telephony/imsphone/ImsRcsStatusListener.java b/src/java/com/android/internal/telephony/imsphone/ImsRcsStatusListener.java
new file mode 100644
index 0000000..9fa2f41
--- /dev/null
+++ b/src/java/com/android/internal/telephony/imsphone/ImsRcsStatusListener.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2020 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.imsphone;
+
+import com.android.ims.RcsFeatureManager;
+
+/**
+ * A listener to receive IMS RCS status changed.
+ */
+public interface ImsRcsStatusListener {
+    /**
+     * It is called when IMS RCS is connected.
+     */
+    void onRcsConnected(int phoneId, RcsFeatureManager rcsFeatureManager);
+
+    /**
+     * It is called when IMS RCS is disconnected
+     */
+    void onRcsDisconnected(int phoneId);
+}
diff --git a/src/java/com/android/internal/telephony/imsphone/ImsRegistrationCallbackHelper.java b/src/java/com/android/internal/telephony/imsphone/ImsRegistrationCallbackHelper.java
new file mode 100644
index 0000000..33c16e2
--- /dev/null
+++ b/src/java/com/android/internal/telephony/imsphone/ImsRegistrationCallbackHelper.java
@@ -0,0 +1,135 @@
+/*
+ * Copyright (C) 2020 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.imsphone;
+
+import android.annotation.AnyThread;
+import android.annotation.NonNull;
+import android.net.Uri;
+import android.telephony.ims.ImsReasonInfo;
+import android.telephony.ims.RegistrationManager;
+import android.telephony.ims.aidl.IImsRegistrationCallback;
+import android.util.Log;
+
+import java.util.concurrent.Executor;
+
+/**
+ * A helper class to manager the ImsRegistrationCallback can notify the state changed to listener.
+ */
+@AnyThread
+public class ImsRegistrationCallbackHelper {
+    private static final String TAG = "ImsRegCallbackHelper";
+
+    /**
+     * The interface to receive IMS registration updated.
+     */
+    public interface ImsRegistrationUpdate {
+        /**
+         * Handle the callback when IMS is registered.
+         */
+        void handleImsRegistered(int imsRadioTech);
+
+        /**
+         * Handle the callback when IMS is registering.
+         */
+        void handleImsRegistering(int imsRadioTech);
+
+        /**
+         * Handle the callback when IMS is unregistered.
+         */
+        void handleImsUnregistered(ImsReasonInfo imsReasonInfo);
+
+        /**
+         * Handle the callback when the list of subscriber {@link Uri}s associated with this IMS
+         * subscription changed.
+         */
+        void handleImsSubscriberAssociatedUriChanged(Uri[] uris);
+    }
+
+    private ImsRegistrationUpdate mImsRegistrationUpdate;
+    private int mRegistrationState = RegistrationManager.REGISTRATION_STATE_NOT_REGISTERED;
+    private final Object mLock = new Object();
+
+    private final RegistrationManager.RegistrationCallback mImsRegistrationCallback =
+            new RegistrationManager.RegistrationCallback() {
+                @Override
+                public void onRegistered(int imsRadioTech) {
+                    updateRegistrationState(RegistrationManager.REGISTRATION_STATE_REGISTERED);
+                    mImsRegistrationUpdate.handleImsRegistered(imsRadioTech);
+                }
+
+                @Override
+                public void onRegistering(int imsRadioTech) {
+                    updateRegistrationState(RegistrationManager.REGISTRATION_STATE_REGISTERING);
+                    mImsRegistrationUpdate.handleImsRegistering(imsRadioTech);
+                }
+
+                @Override
+                public void onUnregistered(ImsReasonInfo imsReasonInfo) {
+                    updateRegistrationState(RegistrationManager.REGISTRATION_STATE_NOT_REGISTERED);
+                    mImsRegistrationUpdate.handleImsUnregistered(imsReasonInfo);
+                }
+
+                @Override
+                public void onSubscriberAssociatedUriChanged(Uri[] uris) {
+                    mImsRegistrationUpdate.handleImsSubscriberAssociatedUriChanged(uris);
+                }
+            };
+
+    public ImsRegistrationCallbackHelper(@NonNull ImsRegistrationUpdate registrationUpdate,
+            Executor executor) {
+        mImsRegistrationCallback.setExecutor(executor);
+        mImsRegistrationUpdate = registrationUpdate;
+    }
+
+    /**
+     * Reset the IMS registration state.
+     */
+    public void reset() {
+        Log.d(TAG, "reset");
+        updateRegistrationState(RegistrationManager.REGISTRATION_STATE_NOT_REGISTERED);
+    }
+
+    /**
+     * Update the latest IMS registration state.
+     */
+    public synchronized void updateRegistrationState(
+            @RegistrationManager.ImsRegistrationState int newState) {
+        synchronized (mLock) {
+            Log.d(TAG, "updateRegistrationState: registration state from " + mRegistrationState
+                    + " to " + newState);
+            mRegistrationState = newState;
+        }
+    }
+
+    public int getImsRegistrationState() {
+        synchronized (mLock) {
+            return mRegistrationState;
+        }
+    }
+
+    public boolean isImsRegistered() {
+        return getImsRegistrationState() == RegistrationManager.REGISTRATION_STATE_REGISTERED;
+    }
+
+    public RegistrationManager.RegistrationCallback getCallback() {
+        return mImsRegistrationCallback;
+    }
+
+    public IImsRegistrationCallback getCallbackBinder() {
+        return mImsRegistrationCallback.getBinder();
+    }
+}
diff --git a/src/java/com/android/internal/telephony/imsphone/ImsRttTextHandler.java b/src/java/com/android/internal/telephony/imsphone/ImsRttTextHandler.java
index eff2499..3955ea7 100644
--- a/src/java/com/android/internal/telephony/imsphone/ImsRttTextHandler.java
+++ b/src/java/com/android/internal/telephony/imsphone/ImsRttTextHandler.java
@@ -20,9 +20,9 @@
 import android.os.Looper;
 import android.os.Message;
 import android.telecom.Connection;
-import android.telephony.Rlog;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.telephony.Rlog;
 
 import java.io.IOException;
 import java.nio.channels.ClosedByInterruptException;
diff --git a/src/java/com/android/internal/telephony/metrics/CallQualityMetrics.java b/src/java/com/android/internal/telephony/metrics/CallQualityMetrics.java
index a3f287c..cf98acb 100644
--- a/src/java/com/android/internal/telephony/metrics/CallQualityMetrics.java
+++ b/src/java/com/android/internal/telephony/metrics/CallQualityMetrics.java
@@ -18,19 +18,20 @@
 
 import static com.android.internal.telephony.metrics.TelephonyMetrics.toCallQualityProto;
 
-import android.os.Build;
 import android.telephony.CallQuality;
 import android.telephony.CellInfo;
 import android.telephony.CellSignalStrengthLte;
-import android.telephony.Rlog;
 import android.telephony.SignalStrength;
 import android.util.Pair;
 
 import com.android.internal.telephony.Phone;
 import com.android.internal.telephony.ServiceStateTracker;
 import com.android.internal.telephony.nano.TelephonyProto.TelephonyCallSession;
+import com.android.internal.telephony.util.TelephonyUtils;
+import com.android.telephony.Rlog;
 
 import java.util.ArrayList;
+import java.util.Collections;
 
 /**
  * CallQualityMetrics is a utility for tracking the CallQuality during an ongoing call session. It
@@ -42,7 +43,7 @@
     private static final String TAG = CallQualityMetrics.class.getSimpleName();
 
     // certain metrics are only logged on userdebug
-    private static final boolean USERDEBUG_MODE = Build.IS_USERDEBUG;
+    private static final boolean IS_DEBUGGABLE = TelephonyUtils.IS_DEBUGGABLE;
 
     // We only log the first MAX_SNAPSHOTS changes to CallQuality
     private static final int MAX_SNAPSHOTS = 5;
@@ -63,6 +64,12 @@
     // the first MAX_SNAPSHOTS transitions between good and bad quality
     private ArrayList<Pair<CallQuality, Integer>> mDlSnapshots = new ArrayList<>();
 
+    // holds lightweight history of call quality and durations, used for calculating total time
+    // spent with bad and good quality for metrics and bugreports. This is separate from the
+    // snapshots because those are capped at MAX_SNAPSHOTS to avoid excessive memory use.
+    private ArrayList<TimestampedQualitySnapshot> mFullUplinkQuality = new ArrayList<>();
+    private ArrayList<TimestampedQualitySnapshot> mFullDownlinkQuality = new ArrayList<>();
+
     // Current downlink call quality
     private int mDlCallQualityState = GOOD_QUALITY;
 
@@ -72,7 +79,7 @@
     // The last logged CallQuality
     private CallQuality mLastCallQuality;
 
-    /** Snapshots taken at best and worst SignalStrengths*/
+    /** Snapshots taken at best and worst SignalStrengths */
     private Pair<CallQuality, Integer> mWorstSsWithGoodDlQuality;
     private Pair<CallQuality, Integer> mBestSsWithGoodDlQuality;
     private Pair<CallQuality, Integer> mWorstSsWithBadDlQuality;
@@ -82,12 +89,6 @@
     private Pair<CallQuality, Integer> mWorstSsWithBadUlQuality;
     private Pair<CallQuality, Integer> mBestSsWithBadUlQuality;
 
-    /** Total durations of good and bad quality time for uplink and downlink */
-    private int mTotalDlGoodQualityTimeMs = 0;
-    private int mTotalDlBadQualityTimeMs = 0;
-    private int mTotalUlGoodQualityTimeMs = 0;
-    private int mTotalUlBadQualityTimeMs = 0;
-
     /**
      * Construct a CallQualityMetrics object to be used to keep track of call quality for a single
      * call session.
@@ -116,22 +117,32 @@
             newDlCallQualityState = GOOD_QUALITY;
         }
 
-        if (USERDEBUG_MODE) {
+        if (IS_DEBUGGABLE) {
             if (newUlCallQualityState != mUlCallQualityState) {
-                mUlSnapshots = addSnapshot(cq, mUlSnapshots);
+                addSnapshot(cq, mUlSnapshots);
             }
             if (newDlCallQualityState != mDlCallQualityState) {
-                mDlSnapshots = addSnapshot(cq, mDlSnapshots);
+                addSnapshot(cq, mDlSnapshots);
             }
         }
 
-        updateTotalDurations(newDlCallQualityState, newUlCallQualityState, cq);
+        updateTotalDurations(cq);
 
         updateMinAndMaxSignalStrengthSnapshots(newDlCallQualityState, newUlCallQualityState, cq);
 
         mUlCallQualityState = newUlCallQualityState;
         mDlCallQualityState = newDlCallQualityState;
-        mLastCallQuality = cq;
+        // call duration updates sometimes come out of order
+        if (cq.getCallDuration() > mLastCallQuality.getCallDuration()) {
+            mLastCallQuality = cq;
+        }
+    }
+
+    private void updateTotalDurations(CallQuality cq) {
+        mFullDownlinkQuality.add(new TimestampedQualitySnapshot(cq.getCallDuration(),
+                cq.getDownlinkCallQualityLevel()));
+        mFullUplinkQuality.add(new TimestampedQualitySnapshot(cq.getCallDuration(),
+                cq.getUplinkCallQualityLevel()));
     }
 
     private static boolean isGoodQuality(int callQualityLevel) {
@@ -142,32 +153,11 @@
      * Save a snapshot of the call quality and signal strength. This can be called with uplink or
      * downlink call quality level.
      */
-    private ArrayList<Pair<CallQuality, Integer>> addSnapshot(CallQuality cq,
-            ArrayList<Pair<CallQuality, Integer>> snapshots) {
+    private void addSnapshot(CallQuality cq, ArrayList<Pair<CallQuality, Integer>> snapshots) {
         if (snapshots.size() < MAX_SNAPSHOTS) {
             Integer ss = getLteSnr();
             snapshots.add(Pair.create(cq, ss));
         }
-        return snapshots;
-    }
-
-    /**
-     * Updates the running total duration of good and bad call quality for uplink and downlink.
-     */
-    private void updateTotalDurations(int newDlCallQualityState, int newUlCallQualityState,
-            CallQuality cq) {
-        int timePassed = cq.getCallDuration() - mLastCallQuality.getCallDuration();
-        if (newDlCallQualityState == GOOD_QUALITY) {
-            mTotalDlGoodQualityTimeMs += timePassed;
-        } else {
-            mTotalDlBadQualityTimeMs += timePassed;
-        }
-
-        if (newUlCallQualityState == GOOD_QUALITY) {
-            mTotalUlGoodQualityTimeMs += timePassed;
-        } else {
-            mTotalUlBadQualityTimeMs += timePassed;
-        }
     }
 
     /**
@@ -261,8 +251,10 @@
     public TelephonyCallSession.Event.CallQualitySummary getCallQualitySummaryDl() {
         TelephonyCallSession.Event.CallQualitySummary summary =
                 new TelephonyCallSession.Event.CallQualitySummary();
-        summary.totalGoodQualityDurationInSeconds = mTotalDlGoodQualityTimeMs / 1000;
-        summary.totalBadQualityDurationInSeconds = mTotalDlBadQualityTimeMs / 1000;
+        Pair<Integer, Integer> totalGoodAndBadDurations = getTotalGoodAndBadQualityTimeMs(
+                mFullDownlinkQuality);
+        summary.totalGoodQualityDurationInSeconds = totalGoodAndBadDurations.first / 1000;
+        summary.totalBadQualityDurationInSeconds = totalGoodAndBadDurations.second / 1000;
         // This value could be different from mLastCallQuality.getCallDuration if we support
         // handover from IMS->CS->IMS, but this is currently not possible
         // TODO(b/130302396) this also may be possible when we put a call on hold and continue with
@@ -299,8 +291,10 @@
     public TelephonyCallSession.Event.CallQualitySummary getCallQualitySummaryUl() {
         TelephonyCallSession.Event.CallQualitySummary summary =
                 new TelephonyCallSession.Event.CallQualitySummary();
-        summary.totalGoodQualityDurationInSeconds = mTotalUlGoodQualityTimeMs / 1000;
-        summary.totalBadQualityDurationInSeconds = mTotalUlBadQualityTimeMs / 1000;
+        Pair<Integer, Integer> totalGoodAndBadDurations = getTotalGoodAndBadQualityTimeMs(
+                mFullUplinkQuality);
+        summary.totalGoodQualityDurationInSeconds = totalGoodAndBadDurations.first / 1000;
+        summary.totalBadQualityDurationInSeconds = totalGoodAndBadDurations.second / 1000;
         // This value could be different from mLastCallQuality.getCallDuration if we support
         // handover from IMS->CS->IMS, but this is currently not possible
         // TODO(b/130302396) this also may be possible when we put a call on hold and continue with
@@ -331,6 +325,60 @@
         return summary;
     }
 
+
+    /**
+     * Container class for call quality level and signal strength at the time of snapshot. This
+     * class implements compareTo so that it can be sorted by timestamp
+     */
+    private class TimestampedQualitySnapshot implements Comparable<TimestampedQualitySnapshot> {
+        int mTimestampMs;
+        int mCallQualityLevel;
+
+        TimestampedQualitySnapshot(int timestamp, int cq) {
+            mTimestampMs = timestamp;
+            mCallQualityLevel = cq;
+        }
+
+        @Override
+        public int compareTo(TimestampedQualitySnapshot o) {
+            return this.mTimestampMs - o.mTimestampMs;
+        }
+
+        @Override
+        public String toString() {
+            return "mTimestampMs=" + mTimestampMs + " mCallQualityLevel=" + mCallQualityLevel;
+        }
+    }
+
+    /**
+     * Use a list of snapshots to calculate and return the total time spent in a call with good
+     * quality and bad quality.
+     * This is slightly expensive since it involves sorting the snapshots by timestamp.
+     *
+     * @param snapshots a list of uplink or downlink snapshots
+     * @return a pair where the first element is the total good quality time and the second element
+     * is the total bad quality time
+     */
+    private Pair<Integer, Integer> getTotalGoodAndBadQualityTimeMs(
+            ArrayList<TimestampedQualitySnapshot> snapshots) {
+        int totalGoodQualityTime = 0;
+        int totalBadQualityTime = 0;
+        int lastTimestamp = 0;
+        // sort by timestamp using TimestampedQualitySnapshot.compareTo
+        Collections.sort(snapshots);
+        for (TimestampedQualitySnapshot snapshot : snapshots) {
+            int timeSinceLastSnapshot = snapshot.mTimestampMs - lastTimestamp;
+            if (isGoodQuality(snapshot.mCallQualityLevel)) {
+                totalGoodQualityTime += timeSinceLastSnapshot;
+            } else {
+                totalBadQualityTime += timeSinceLastSnapshot;
+            }
+            lastTimestamp = snapshot.mTimestampMs;
+        }
+
+        return Pair.create(totalGoodQualityTime, totalBadQualityTime);
+    }
+
     @Override
     public String toString() {
         StringBuilder sb = new StringBuilder();
@@ -355,14 +403,16 @@
         }
         sb.append("}");
         sb.append(" ");
-        sb.append(" mTotalDlGoodQualityTimeMs: ");
-        sb.append(mTotalDlGoodQualityTimeMs);
-        sb.append(" mTotalDlBadQualityTimeMs: ");
-        sb.append(mTotalDlBadQualityTimeMs);
-        sb.append(" mTotalUlGoodQualityTimeMs: ");
-        sb.append(mTotalUlGoodQualityTimeMs);
-        sb.append(" mTotalUlBadQualityTimeMs: ");
-        sb.append(mTotalUlBadQualityTimeMs);
+        Pair<Integer, Integer> dlTotals = getTotalGoodAndBadQualityTimeMs(mFullDownlinkQuality);
+        Pair<Integer, Integer> ulTotals = getTotalGoodAndBadQualityTimeMs(mFullUplinkQuality);
+        sb.append(" TotalDlGoodQualityTimeMs: ");
+        sb.append(dlTotals.first);
+        sb.append(" TotalDlBadQualityTimeMs: ");
+        sb.append(dlTotals.second);
+        sb.append(" TotalUlGoodQualityTimeMs: ");
+        sb.append(ulTotals.first);
+        sb.append(" TotalUlBadQualityTimeMs: ");
+        sb.append(ulTotals.second);
         sb.append("]");
         return sb.toString();
     }
diff --git a/src/java/com/android/internal/telephony/metrics/CallSessionEventBuilder.java b/src/java/com/android/internal/telephony/metrics/CallSessionEventBuilder.java
index d4bacc6..0aa5fcf 100644
--- a/src/java/com/android/internal/telephony/metrics/CallSessionEventBuilder.java
+++ b/src/java/com/android/internal/telephony/metrics/CallSessionEventBuilder.java
@@ -164,6 +164,13 @@
         return this;
     }
 
+    /** Set the emergency number database version in Ims emergency call information. */
+    public CallSessionEventBuilder setEmergencyNumberDatabaseVersion(
+            int emergencyNumberDatabaseVersion) {
+        mEvent.emergencyNumberDatabaseVersion = emergencyNumberDatabaseVersion;
+        return this;
+    }
+
     /** Set the Ims emergency call information. */
     public CallSessionEventBuilder setImsEmergencyNumberInfo(
             EmergencyNumberInfo imsEmergencyNumberInfo) {
diff --git a/src/java/com/android/internal/telephony/metrics/MetricsCollector.java b/src/java/com/android/internal/telephony/metrics/MetricsCollector.java
new file mode 100644
index 0000000..af71c54
--- /dev/null
+++ b/src/java/com/android/internal/telephony/metrics/MetricsCollector.java
@@ -0,0 +1,263 @@
+/*
+ * Copyright (C) 2020 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.metrics;
+
+import static android.text.format.DateUtils.HOUR_IN_MILLIS;
+import static android.text.format.DateUtils.MINUTE_IN_MILLIS;
+import static android.text.format.DateUtils.SECOND_IN_MILLIS;
+
+import static com.android.internal.telephony.TelephonyStatsLog.SIM_SLOT_STATE;
+import static com.android.internal.telephony.TelephonyStatsLog.SUPPORTED_RADIO_ACCESS_FAMILY;
+import static com.android.internal.telephony.TelephonyStatsLog.VOICE_CALL_RAT_USAGE;
+import static com.android.internal.telephony.TelephonyStatsLog.VOICE_CALL_SESSION;
+
+import android.annotation.Nullable;
+import android.app.StatsManager;
+import android.content.Context;
+import android.util.StatsEvent;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.telephony.Phone;
+import com.android.internal.telephony.PhoneFactory;
+import com.android.internal.telephony.nano.PersistAtomsProto.RawVoiceCallRatUsage;
+import com.android.internal.telephony.nano.PersistAtomsProto.VoiceCallSession;
+import com.android.internal.util.ConcurrentUtils;
+import com.android.telephony.Rlog;
+
+import java.util.Arrays;
+import java.util.Comparator;
+import java.util.List;
+import java.util.Random;
+
+/**
+ * Implements statsd pullers for Telephony.
+ *
+ * <p>This class registers pullers to statsd, which will be called once a day to obtain telephony
+ * statistics that cannot be sent to statsd in real time.
+ */
+public class MetricsCollector implements StatsManager.StatsPullAtomCallback {
+    private static final String TAG = MetricsCollector.class.getSimpleName();
+
+    /** Disables various restrictions to ease debugging during development. */
+    private static final boolean DBG = false; // STOPSHIP if true
+
+    /**
+     * Sets atom pull cool down to 23 hours to help enforcing privacy requirement.
+     *
+     * <p>Applies to certain atoms. The interval of 23 hours leaves some margin for pull operations
+     * that occur once a day.
+     */
+    private static final long MIN_COOLDOWN_MILLIS =
+            DBG ? 10L * SECOND_IN_MILLIS : 23L * HOUR_IN_MILLIS;
+
+    /**
+     * Buckets with less than these many calls will be dropped.
+     *
+     * <p>Applies to metrics with duration fields. Currently used by voice call RAT usages.
+     */
+    private static final long MIN_CALLS_PER_BUCKET = DBG ? 0L : 5L;
+
+    /** Bucket size in milliseconds to round call durations into. */
+    private static final long DURATION_BUCKET_MILLIS =
+            DBG ? 2L * SECOND_IN_MILLIS : 5L * MINUTE_IN_MILLIS;
+
+    private static final StatsManager.PullAtomMetadata POLICY_PULL_DAILY =
+            new StatsManager.PullAtomMetadata.Builder()
+                    .setCoolDownMillis(MIN_COOLDOWN_MILLIS)
+                    .build();
+
+    private PersistAtomsStorage mStorage;
+    private final StatsManager mStatsManager;
+    private static final Random sRandom = new Random();
+
+    public MetricsCollector(Context context) {
+        mStorage = new PersistAtomsStorage(context);
+        mStatsManager = (StatsManager) context.getSystemService(Context.STATS_MANAGER);
+        if (mStatsManager != null) {
+            registerAtom(SIM_SLOT_STATE, null);
+            registerAtom(SUPPORTED_RADIO_ACCESS_FAMILY, null);
+            registerAtom(VOICE_CALL_RAT_USAGE, POLICY_PULL_DAILY);
+            registerAtom(VOICE_CALL_SESSION, POLICY_PULL_DAILY);
+            Rlog.d(TAG, "registered");
+        } else {
+            Rlog.e(TAG, "could not get StatsManager, atoms not registered");
+        }
+    }
+
+    /** Replaces the {@link PersistAtomsStorage} backing the puller. Used during unit tests. */
+    @VisibleForTesting
+    public void setPersistAtomsStorage(PersistAtomsStorage storage) {
+        mStorage = storage;
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * @return {@link StatsManager#PULL_SUCCESS} with list of atoms (potentially empty) if pull
+     *     succeeded, {@link StatsManager#PULL_SKIP} if pull was too frequent or atom ID is
+     *     unexpected.
+     */
+    @Override
+    public int onPullAtom(int atomTag, List<StatsEvent> data) {
+        switch (atomTag) {
+            case SIM_SLOT_STATE:
+                return pullSimSlotState(data);
+            case SUPPORTED_RADIO_ACCESS_FAMILY:
+                return pullSupportedRadioAccessFamily(data);
+            case VOICE_CALL_RAT_USAGE:
+                return pullVoiceCallRatUsages(data);
+            case VOICE_CALL_SESSION:
+                return pullVoiceCallSessions(data);
+            default:
+                Rlog.e(TAG, String.format("unexpected atom ID %d", atomTag));
+                return StatsManager.PULL_SKIP;
+        }
+    }
+
+    /** Returns the {@link PersistAtomsStorage} backing the puller. */
+    public PersistAtomsStorage getAtomsStorage() {
+        return mStorage;
+    }
+
+    private static int pullSimSlotState(List<StatsEvent> data) {
+        SimSlotState state;
+        try {
+            state = SimSlotState.getCurrentState();
+        } catch (RuntimeException e) {
+            // UiccController has not been made yet
+            return StatsManager.PULL_SKIP;
+        }
+
+        StatsEvent e =
+                StatsEvent.newBuilder()
+                        .setAtomId(SIM_SLOT_STATE)
+                        .writeInt(state.numActiveSlots)
+                        .writeInt(state.numActiveSims)
+                        .writeInt(state.numActiveEsims)
+                        .build();
+        data.add(e);
+        return StatsManager.PULL_SUCCESS;
+    }
+
+    private static int pullSupportedRadioAccessFamily(List<StatsEvent> data) {
+        long rafSupported = 0L;
+        try {
+            // The bitmask is defined in android.telephony.TelephonyManager.NetworkTypeBitMask
+            for (Phone phone : PhoneFactory.getPhones()) {
+                rafSupported |= phone.getRadioAccessFamily();
+            }
+        } catch (IllegalStateException e) {
+            // Phones have not been made yet
+            return StatsManager.PULL_SKIP;
+        }
+
+        StatsEvent e =
+                StatsEvent.newBuilder()
+                        .setAtomId(SUPPORTED_RADIO_ACCESS_FAMILY)
+                        .writeLong(rafSupported)
+                        .build();
+        data.add(e);
+        return StatsManager.PULL_SUCCESS;
+    }
+
+    private int pullVoiceCallRatUsages(List<StatsEvent> data) {
+        RawVoiceCallRatUsage[] usages = mStorage.getVoiceCallRatUsages(MIN_COOLDOWN_MILLIS);
+        if (usages != null) {
+            // sort by carrier/RAT and remove buckets with insufficient number of calls
+            Arrays.stream(usages)
+                    .sorted(
+                            Comparator.comparingLong(
+                                    usage -> ((long) usage.carrierId << 32) | usage.rat))
+                    .filter(usage -> usage.callCount >= MIN_CALLS_PER_BUCKET)
+                    .forEach(usage -> data.add(buildStatsEvent(usage)));
+            Rlog.d(
+                    TAG,
+                    String.format(
+                            "%d out of %d VOICE_CALL_RAT_USAGE pulled",
+                            data.size(), usages.length));
+            return StatsManager.PULL_SUCCESS;
+        } else {
+            Rlog.w(TAG, "VOICE_CALL_RAT_USAGE pull too frequent, skipping");
+            return StatsManager.PULL_SKIP;
+        }
+    }
+
+    private int pullVoiceCallSessions(List<StatsEvent> data) {
+        VoiceCallSession[] calls = mStorage.getVoiceCallSessions(MIN_COOLDOWN_MILLIS);
+        if (calls != null) {
+            // call session list is already shuffled when calls inserted
+            Arrays.stream(calls).forEach(call -> data.add(buildStatsEvent(call)));
+            return StatsManager.PULL_SUCCESS;
+        } else {
+            Rlog.w(TAG, "VOICE_CALL_SESSION pull too frequent, skipping");
+            return StatsManager.PULL_SKIP;
+        }
+    }
+
+    /** Registers a pulled atom ID {@code atomId} with optional {@code policy} for pulling. */
+    private void registerAtom(int atomId, @Nullable StatsManager.PullAtomMetadata policy) {
+        mStatsManager.setPullAtomCallback(atomId, policy, ConcurrentUtils.DIRECT_EXECUTOR, this);
+    }
+
+    private static StatsEvent buildStatsEvent(RawVoiceCallRatUsage usage) {
+        return StatsEvent.newBuilder()
+                .setAtomId(VOICE_CALL_RAT_USAGE)
+                .writeInt(usage.carrierId)
+                .writeInt(usage.rat)
+                .writeLong(
+                        round(usage.totalDurationMillis, DURATION_BUCKET_MILLIS) / SECOND_IN_MILLIS)
+                .writeLong(usage.callCount)
+                .build();
+    }
+
+    private static StatsEvent buildStatsEvent(VoiceCallSession session) {
+        return StatsEvent.newBuilder()
+                .setAtomId(VOICE_CALL_SESSION)
+                .writeInt(session.bearerAtStart)
+                .writeInt(session.bearerAtEnd)
+                .writeInt(session.direction)
+                .writeInt(session.setupDuration)
+                .writeBoolean(session.setupFailed)
+                .writeInt(session.disconnectReasonCode)
+                .writeInt(session.disconnectExtraCode)
+                .writeString(session.disconnectExtraMessage)
+                .writeInt(session.ratAtStart)
+                .writeInt(session.ratAtEnd)
+                .writeLong(session.ratSwitchCount)
+                .writeLong(session.codecBitmask)
+                .writeInt(session.concurrentCallCountAtStart)
+                .writeInt(session.concurrentCallCountAtEnd)
+                .writeInt(session.simSlotIndex)
+                .writeBoolean(session.isMultiSim)
+                .writeBoolean(session.isEsim)
+                .writeInt(session.carrierId)
+                .writeBoolean(session.srvccCompleted)
+                .writeLong(session.srvccFailureCount)
+                .writeLong(session.srvccCancellationCount)
+                .writeBoolean(session.rttEnabled)
+                .writeBoolean(session.isEmergency)
+                .writeBoolean(session.isRoaming)
+                // workaround: dimension required for keeping multiple pulled atoms
+                .writeInt(sRandom.nextInt())
+                .build();
+    }
+
+    /** Returns the value rounded to the bucket. */
+    private static long round(long value, long bucket) {
+        return ((value + bucket / 2) / bucket) * bucket;
+    }
+}
diff --git a/src/java/com/android/internal/telephony/metrics/ModemPowerMetrics.java b/src/java/com/android/internal/telephony/metrics/ModemPowerMetrics.java
index f9f90fa..67816c9 100644
--- a/src/java/com/android/internal/telephony/metrics/ModemPowerMetrics.java
+++ b/src/java/com/android/internal/telephony/metrics/ModemPowerMetrics.java
@@ -15,27 +15,35 @@
  */
 package com.android.internal.telephony.metrics;
 
-import android.os.BatteryStats;
-import android.os.RemoteException;
-import android.os.ServiceManager;
+import android.os.BatteryStatsManager;
 import android.os.connectivity.CellularBatteryStats;
+import android.telephony.CellSignalStrength;
+import android.telephony.ModemActivityInfo;
+import android.telephony.TelephonyManager;
 import android.text.format.DateUtils;
 
-import com.android.internal.app.IBatteryStats;
 import com.android.internal.telephony.nano.TelephonyProto.ModemPowerStats;
 
+import java.util.ArrayList;
+import java.util.List;
+
 /**
  * ModemPowerMetrics holds the modem power metrics and converts them to ModemPowerStats proto buf.
  * This proto buf is included in the Telephony proto buf.
  */
 public class ModemPowerMetrics {
 
-    /* BatteryStats API */
-    private final IBatteryStats mBatteryStats;
 
-    public ModemPowerMetrics() {
-        mBatteryStats = IBatteryStats.Stub.asInterface(ServiceManager.getService(
-            BatteryStats.SERVICE_NAME));
+    private static final int DATA_CONNECTION_EMERGENCY_SERVICE =
+            TelephonyManager.getAllNetworkTypes().length + 1;
+    private static final int DATA_CONNECTION_OTHER = DATA_CONNECTION_EMERGENCY_SERVICE + 1;
+    private static final int NUM_DATA_CONNECTION_TYPES = DATA_CONNECTION_OTHER + 1;
+
+    /* BatteryStatsManager API */
+    private BatteryStatsManager mBatteryStatsManager;
+
+    public ModemPowerMetrics(BatteryStatsManager batteryStatsManager) {
+        mBatteryStatsManager = batteryStatsManager;
     }
 
     /**
@@ -46,45 +54,67 @@
         ModemPowerStats m = new ModemPowerStats();
         CellularBatteryStats stats = getStats();
         if (stats != null) {
-            m.loggingDurationMs = stats.getLoggingDurationMs();
-            m.energyConsumedMah = stats.getEnergyConsumedMaMs()
+            m.loggingDurationMs = stats.getLoggingDurationMillis();
+            m.energyConsumedMah = stats.getEnergyConsumedMaMillis()
                 / ((double) DateUtils.HOUR_IN_MILLIS);
             m.numPacketsTx = stats.getNumPacketsTx();
-            m.cellularKernelActiveTimeMs = stats.getKernelActiveTimeMs();
-            if (stats.getTimeInRxSignalStrengthLevelMs() != null
-                && stats.getTimeInRxSignalStrengthLevelMs().length > 0) {
-                m.timeInVeryPoorRxSignalLevelMs = stats.getTimeInRxSignalStrengthLevelMs()[0];
+            m.cellularKernelActiveTimeMs = stats.getKernelActiveTimeMillis();
+
+            long timeInVeryPoorRxSignalLevelMs = stats.getTimeInRxSignalStrengthLevelMicros(
+                    CellSignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN);
+            if (timeInVeryPoorRxSignalLevelMs >= 0) {
+                m.timeInVeryPoorRxSignalLevelMs = timeInVeryPoorRxSignalLevelMs;
             }
-            m.sleepTimeMs = stats.getSleepTimeMs();
-            m.idleTimeMs = stats.getIdleTimeMs();
-            m.rxTimeMs = stats.getRxTimeMs();
-            long[] t = stats.getTxTimeMs();
-            m.txTimeMs = new long[t.length];
-            System.arraycopy(t, 0, m.txTimeMs, 0, t.length);
+
+            m.sleepTimeMs = stats.getSleepTimeMillis();
+            m.idleTimeMs = stats.getIdleTimeMillis();
+            m.rxTimeMs = stats.getRxTimeMillis();
+
+            List<Long> txTimeMillis = new ArrayList<>();
+            for (int i = 0; i < ModemActivityInfo.TX_POWER_LEVELS; i++) {
+                long t = stats.getTxTimeMillis(i);
+                if (t >= 0) {
+                    txTimeMillis.add(t);
+                }
+            }
+            m.txTimeMs = txTimeMillis.stream().mapToLong(Long::longValue).toArray();
+
             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);
-            m.monitoredRailEnergyConsumedMah = stats.getMonitoredRailChargeConsumedMaMs()
+            List<Long> timeInRatMicros = new ArrayList<>();
+            for (int i = 0; i < NUM_DATA_CONNECTION_TYPES; i++) {
+                long tr = stats.getTimeInRatMicros(i);
+                if (tr >= 0) {
+                    timeInRatMicros.add(tr);
+                }
+            }
+            m.timeInRatMs = timeInRatMicros.stream().mapToLong(Long::longValue).toArray();
+
+            List<Long> rxSignalStrengthLevelMicros = new ArrayList<>();
+            for (int i = 0; i < CellSignalStrength.getNumSignalStrengthLevels(); i++) {
+                long rx = stats.getTimeInRxSignalStrengthLevelMicros(i);
+                if (rx >= 0) {
+                    rxSignalStrengthLevelMicros.add(rx);
+                }
+            }
+            m.timeInRxSignalStrengthLevelMs = rxSignalStrengthLevelMicros.stream().mapToLong(
+                    Long::longValue).toArray();
+
+            m.monitoredRailEnergyConsumedMah = stats.getMonitoredRailChargeConsumedMaMillis()
                 / ((double) DateUtils.HOUR_IN_MILLIS);
         }
         return m;
     }
 
     /**
-     * Get cellular stats from batterystats
+     * Get cellular stats from BatteryStatsManager
      * @return CellularBatteryStats
      */
     private CellularBatteryStats getStats() {
-        try {
-            return mBatteryStats.getCellularBatteryStats();
-        } catch (RemoteException e) {
+        if (mBatteryStatsManager == null) {
+            return null;
         }
-        return null;
+        return mBatteryStatsManager.getCellularBatteryStats();
     }
 }
diff --git a/src/java/com/android/internal/telephony/metrics/PersistAtomsStorage.java b/src/java/com/android/internal/telephony/metrics/PersistAtomsStorage.java
new file mode 100644
index 0000000..f208369
--- /dev/null
+++ b/src/java/com/android/internal/telephony/metrics/PersistAtomsStorage.java
@@ -0,0 +1,182 @@
+/*
+ * Copyright (C) 2020 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.metrics;
+
+import android.annotation.Nullable;
+import android.content.Context;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.telephony.nano.PersistAtomsProto.PersistAtoms;
+import com.android.internal.telephony.nano.PersistAtomsProto.RawVoiceCallRatUsage;
+import com.android.internal.telephony.nano.PersistAtomsProto.VoiceCallSession;
+import com.android.telephony.Rlog;
+
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.security.SecureRandom;
+import java.util.Arrays;
+
+/**
+ * Stores and aggregates metrics that should not be pulled at arbitrary frequency.
+ *
+ * <p>NOTE: while this class checks timestamp against {@code minIntervalMillis}, it is {@link
+ * MetricsCollector}'s responsibility to ensure {@code minIntervalMillis} is set correctly.
+ */
+public class PersistAtomsStorage {
+    private static final String TAG = PersistAtomsStorage.class.getSimpleName();
+
+    /** Name of the file where cached statistics are saved to. */
+    private static final String FILENAME = "persist_atoms.pb";
+
+    /** Maximum number of call sessions to store during pulls. */
+    private static final int MAX_NUM_CALL_SESSIONS = 50;
+
+    /** Stores persist atoms and persist states of the puller. */
+    @VisibleForTesting protected final PersistAtoms mAtoms;
+
+    /** Aggregates RAT duration and call count. */
+    private final VoiceCallRatTracker mVoiceCallRatTracker;
+
+    private final Context mContext;
+    private static final SecureRandom sRandom = new SecureRandom();
+
+    public PersistAtomsStorage(Context context) {
+        mContext = context;
+        mAtoms = loadAtomsFromFile();
+        mVoiceCallRatTracker = VoiceCallRatTracker.fromProto(mAtoms.rawVoiceCallRatUsage);
+    }
+
+    /** Adds a call to the storage. */
+    public synchronized void addVoiceCallSession(VoiceCallSession call) {
+        int newLength = mAtoms.voiceCallSession.length + 1;
+        if (newLength > MAX_NUM_CALL_SESSIONS) {
+            // will evict one previous call randomly instead of making the array larger
+            newLength = MAX_NUM_CALL_SESSIONS;
+        } else {
+            mAtoms.voiceCallSession = Arrays.copyOf(mAtoms.voiceCallSession, newLength);
+        }
+        int insertAt = 0;
+        if (newLength > 1) {
+            // shuffle when each call is added, or randomly replace a previous call instead if
+            // MAX_NUM_CALL_SESSIONS is reached (call at the last index is evicted).
+            insertAt = sRandom.nextInt(newLength);
+            mAtoms.voiceCallSession[newLength - 1] = mAtoms.voiceCallSession[insertAt];
+        }
+        mAtoms.voiceCallSession[insertAt] = call;
+        saveAtomsToFile();
+    }
+
+    /** Adds RAT usages to the storage when a call session ends. */
+    public synchronized void addVoiceCallRatUsage(VoiceCallRatTracker ratUsages) {
+        mVoiceCallRatTracker.mergeWith(ratUsages);
+        mAtoms.rawVoiceCallRatUsage = mVoiceCallRatTracker.toProto();
+        saveAtomsToFile();
+    }
+
+    /**
+     * Returns and clears the voice call sessions if last pulled longer than {@code
+     * minIntervalMillis} ago, otherwise returns {@code null}.
+     */
+    @Nullable
+    public synchronized VoiceCallSession[] getVoiceCallSessions(long minIntervalMillis) {
+        if (getWallTimeMillis() - mAtoms.voiceCallSessionPullTimestampMillis > minIntervalMillis) {
+            mAtoms.voiceCallSessionPullTimestampMillis = getWallTimeMillis();
+            VoiceCallSession[] previousCalls = mAtoms.voiceCallSession;
+            mAtoms.voiceCallSession = new VoiceCallSession[0];
+            saveAtomsToFile();
+            return previousCalls;
+        } else {
+            return null;
+        }
+    }
+
+    /**
+     * Returns and clears the voice call RAT usages if last pulled longer than {@code
+     * minIntervalMillis} ago, otherwise returns {@code null}.
+     */
+    @Nullable
+    public synchronized RawVoiceCallRatUsage[] getVoiceCallRatUsages(long minIntervalMillis) {
+        if (getWallTimeMillis() - mAtoms.rawVoiceCallRatUsagePullTimestampMillis
+                > minIntervalMillis) {
+            mAtoms.rawVoiceCallRatUsagePullTimestampMillis = getWallTimeMillis();
+            RawVoiceCallRatUsage[] previousUsages = mAtoms.rawVoiceCallRatUsage;
+            mVoiceCallRatTracker.clear();
+            mAtoms.rawVoiceCallRatUsage = new RawVoiceCallRatUsage[0];
+            saveAtomsToFile();
+            return previousUsages;
+        } else {
+            return null;
+        }
+    }
+
+    /** Loads {@link PersistAtoms} from a file in private storage. */
+    private PersistAtoms loadAtomsFromFile() {
+        try {
+            PersistAtoms atomsFromFile =
+                    PersistAtoms.parseFrom(
+                            Files.readAllBytes(mContext.getFileStreamPath(FILENAME).toPath()));
+            // check all the fields in case of situations such as OTA or crash during saving
+            if (atomsFromFile.rawVoiceCallRatUsage == null) {
+                atomsFromFile.rawVoiceCallRatUsage = new RawVoiceCallRatUsage[0];
+            }
+            if (atomsFromFile.voiceCallSession == null) {
+                atomsFromFile.voiceCallSession = new VoiceCallSession[0];
+            }
+            if (atomsFromFile.voiceCallSession.length > MAX_NUM_CALL_SESSIONS) {
+                atomsFromFile.voiceCallSession =
+                        Arrays.copyOf(atomsFromFile.voiceCallSession, MAX_NUM_CALL_SESSIONS);
+            }
+            // out of caution, set timestamps to now if they are missing
+            if (atomsFromFile.rawVoiceCallRatUsagePullTimestampMillis == 0L) {
+                atomsFromFile.rawVoiceCallRatUsagePullTimestampMillis = getWallTimeMillis();
+            }
+            if (atomsFromFile.voiceCallSessionPullTimestampMillis == 0L) {
+                atomsFromFile.voiceCallSessionPullTimestampMillis = getWallTimeMillis();
+            }
+            return atomsFromFile;
+        } catch (IOException | NullPointerException e) {
+            Rlog.e(TAG, "cannot load/parse PersistAtoms", e);
+            return makeNewPersistAtoms();
+        }
+    }
+
+    /** Saves a copy of {@link PersistAtoms} to a file in private storage. */
+    private void saveAtomsToFile() {
+        try (FileOutputStream stream = mContext.openFileOutput(FILENAME, Context.MODE_PRIVATE)) {
+            stream.write(PersistAtoms.toByteArray(mAtoms));
+        } catch (IOException e) {
+            Rlog.e(TAG, "cannot save PersistAtoms", e);
+        }
+    }
+
+    /** Returns an empty PersistAtoms with pull timestamp set to current time. */
+    private PersistAtoms makeNewPersistAtoms() {
+        PersistAtoms atoms = new PersistAtoms();
+        // allow pulling only after some time so data are sufficiently aggregated
+        atoms.rawVoiceCallRatUsagePullTimestampMillis = getWallTimeMillis();
+        atoms.voiceCallSessionPullTimestampMillis = getWallTimeMillis();
+        Rlog.d(TAG, "created new PersistAtoms");
+        return atoms;
+    }
+
+    @VisibleForTesting
+    protected long getWallTimeMillis() {
+        // epoch time in UTC, preserved across reboots, but can be adjusted e.g. by the user or NTP
+        return System.currentTimeMillis();
+    }
+}
diff --git a/src/java/com/android/internal/telephony/metrics/SimSlotState.java b/src/java/com/android/internal/telephony/metrics/SimSlotState.java
new file mode 100644
index 0000000..3c3ae62
--- /dev/null
+++ b/src/java/com/android/internal/telephony/metrics/SimSlotState.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2020 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.metrics;
+
+import com.android.internal.telephony.uicc.IccCardStatus.CardState;
+import com.android.internal.telephony.uicc.UiccCard;
+import com.android.internal.telephony.uicc.UiccController;
+import com.android.internal.telephony.uicc.UiccSlot;
+
+/** Snapshots and stores the current SIM state. */
+public class SimSlotState {
+    public final int numActiveSlots;
+    public final int numActiveSims;
+    public final int numActiveEsims;
+
+    /** Returns the current SIM state. */
+    public static SimSlotState getCurrentState() {
+        int numActiveSlots = 0;
+        int numActiveSims = 0;
+        int numActiveEsims = 0;
+        UiccController uiccController = UiccController.getInstance();
+        // since we cannot hold lock insider UiccController, using getUiccSlots() for length only
+        for (int i = 0; i < uiccController.getUiccSlots().length; i++) {
+            UiccSlot slot = uiccController.getUiccSlot(i);
+            if (slot != null && slot.isActive()) {
+                numActiveSlots++;
+                // avoid CardState.isCardPresent() since this should not include restricted cards
+                if (slot.getCardState() == CardState.CARDSTATE_PRESENT) {
+                    if (slot.isEuicc()) {
+                        // need to check active profiles besides the presence of eSIM cards
+                        UiccCard card = slot.getUiccCard();
+                        if (card != null && card.getNumApplications() > 0) {
+                            numActiveSims++;
+                            numActiveEsims++;
+                        }
+                    } else {
+                        // physical SIMs do not always have non-null card
+                        numActiveSims++;
+                    }
+                }
+            }
+        }
+        return new SimSlotState(numActiveSlots, numActiveSims, numActiveEsims);
+    }
+
+    private SimSlotState(int numActiveSlots, int numActiveSims, int numActiveEsims) {
+        this.numActiveSlots = numActiveSlots;
+        this.numActiveSims = numActiveSims;
+        this.numActiveEsims = numActiveEsims;
+    }
+}
diff --git a/src/java/com/android/internal/telephony/metrics/SmsSessionEventBuilder.java b/src/java/com/android/internal/telephony/metrics/SmsSessionEventBuilder.java
index 2587fa4..0ba5395 100644
--- a/src/java/com/android/internal/telephony/metrics/SmsSessionEventBuilder.java
+++ b/src/java/com/android/internal/telephony/metrics/SmsSessionEventBuilder.java
@@ -116,4 +116,10 @@
         mEvent.smsType = type;
         return this;
     }
+
+    /** Set message id */
+    public SmsSessionEventBuilder setMessageId(long messageId) {
+        mEvent.messageId = messageId;
+        return this;
+    }
 }
diff --git a/src/java/com/android/internal/telephony/metrics/TelephonyEventBuilder.java b/src/java/com/android/internal/telephony/metrics/TelephonyEventBuilder.java
index 22fc562..8a423ef 100644
--- a/src/java/com/android/internal/telephony/metrics/TelephonyEventBuilder.java
+++ b/src/java/com/android/internal/telephony/metrics/TelephonyEventBuilder.java
@@ -25,6 +25,7 @@
 import static com.android.internal.telephony.nano.TelephonyProto.TelephonyEvent.CarrierIdMatching;
 import static com.android.internal.telephony.nano.TelephonyProto.TelephonyEvent.CarrierKeyChange;
 import static com.android.internal.telephony.nano.TelephonyProto.TelephonyEvent.ModemRestart;
+import static com.android.internal.telephony.nano.TelephonyProto.TelephonyEvent.NetworkCapabilitiesInfo;
 import static com.android.internal.telephony.nano.TelephonyProto.TelephonyEvent.RilDeactivateDataCall;
 import static com.android.internal.telephony.nano.TelephonyProto.TelephonyEvent.RilSetupDataCall;
 import static com.android.internal.telephony.nano.TelephonyProto.TelephonyEvent.RilSetupDataCallResponse;
@@ -154,9 +155,11 @@
      * Set and build EMERGENCY_NUMBER_REPORT event
      */
     public TelephonyEventBuilder setUpdatedEmergencyNumber(
-            EmergencyNumberInfo emergencyNumberInfo) {
+            EmergencyNumberInfo emergencyNumberInfo,
+            int emergencyNumberDbVersion) {
         mEvent.type = TelephonyEvent.Type.EMERGENCY_NUMBER_REPORT;
         mEvent.updatedEmergencyNumber = emergencyNumberInfo;
+        mEvent.emergencyNumberDatabaseVersion = emergencyNumberDbVersion;
         return this;
     }
 
@@ -215,4 +218,12 @@
         mEvent.onDemandDataSwitch = onDemandDataSwitch;
         return this;
     }
+
+    /** Set and build network capabilities changed event. */
+    public TelephonyEventBuilder setNetworkCapabilities(
+            NetworkCapabilitiesInfo networkCapabilities) {
+        mEvent.type = TelephonyEvent.Type.NETWORK_CAPABILITIES_CHANGED;
+        mEvent.networkCapabilities = networkCapabilities;
+        return this;
+    }
 }
diff --git a/src/java/com/android/internal/telephony/metrics/TelephonyMetrics.java b/src/java/com/android/internal/telephony/metrics/TelephonyMetrics.java
index 044fb08..367b3b2 100644
--- a/src/java/com/android/internal/telephony/metrics/TelephonyMetrics.java
+++ b/src/java/com/android/internal/telephony/metrics/TelephonyMetrics.java
@@ -37,6 +37,9 @@
 import static com.android.internal.telephony.nano.TelephonyProto.PdpType.PDP_TYPE_UNSTRUCTURED;
 import static com.android.internal.telephony.nano.TelephonyProto.PdpType.PDP_UNKNOWN;
 
+import android.content.Context;
+import android.net.NetworkCapabilities;
+import android.os.BatteryStatsManager;
 import android.os.Build;
 import android.os.SystemClock;
 import android.os.SystemProperties;
@@ -45,7 +48,6 @@
 import android.telephony.CallQuality;
 import android.telephony.DisconnectCause;
 import android.telephony.NetworkRegistrationInfo;
-import android.telephony.Rlog;
 import android.telephony.ServiceState;
 import android.telephony.SmsManager;
 import android.telephony.SmsMessage;
@@ -75,6 +77,7 @@
 import com.android.internal.telephony.RILConstants;
 import com.android.internal.telephony.SmsResponse;
 import com.android.internal.telephony.UUSInfo;
+import com.android.internal.telephony.emergency.EmergencyNumberTracker;
 import com.android.internal.telephony.imsphone.ImsPhoneCall;
 import com.android.internal.telephony.nano.TelephonyProto;
 import com.android.internal.telephony.nano.TelephonyProto.ActiveSubscriptionInfo;
@@ -95,6 +98,7 @@
 import com.android.internal.telephony.nano.TelephonyProto.TelephonyEvent.CarrierKeyChange;
 import com.android.internal.telephony.nano.TelephonyProto.TelephonyEvent.DataSwitch;
 import com.android.internal.telephony.nano.TelephonyProto.TelephonyEvent.ModemRestart;
+import com.android.internal.telephony.nano.TelephonyProto.TelephonyEvent.NetworkCapabilitiesInfo;
 import com.android.internal.telephony.nano.TelephonyProto.TelephonyEvent.OnDemandDataSwitch;
 import com.android.internal.telephony.nano.TelephonyProto.TelephonyEvent.RilDeactivateDataCall;
 import com.android.internal.telephony.nano.TelephonyProto.TelephonyEvent.RilDeactivateDataCall.DeactivateReason;
@@ -106,7 +110,9 @@
 import com.android.internal.telephony.nano.TelephonyProto.TelephonySettings;
 import com.android.internal.telephony.nano.TelephonyProto.TimeInterval;
 import com.android.internal.telephony.protobuf.nano.MessageNano;
+import com.android.internal.telephony.util.TelephonyUtils;
 import com.android.internal.util.IndentingPrintWriter;
+import com.android.telephony.Rlog;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
@@ -208,6 +214,12 @@
     private final SparseArray<CarrierIdMatching> mLastCarrierId = new SparseArray<>();
 
     /**
+     * Last NetworkCapabilitiesInfo, indexed by phone id.
+     */
+    private final SparseArray<NetworkCapabilitiesInfo> mLastNetworkCapabilitiesInfos =
+            new SparseArray<>();
+
+    /**
      * Last RilDataCall Events (indexed by cid), indexed by phone id
      */
     private final SparseArray<SparseArray<RilDataCall>> mLastRilDataCallEvents =
@@ -222,6 +234,8 @@
     /** Indicating if some of the telephony events are dropped in this log */
     private boolean mTelephonyEventsDropped = false;
 
+    private Context mContext;
+
     public TelephonyMetrics() {
         mStartSystemTimeMs = System.currentTimeMillis();
         mStartElapsedTimeMs = SystemClock.elapsedRealtime();
@@ -241,6 +255,16 @@
     }
 
     /**
+     * Set the context for telephony metrics.
+     *
+     * @param context Context
+     * @hide
+     */
+    public void setContext(Context context) {
+        mContext = context;
+    }
+
+    /**
      * Dump the state of various objects, add calls to other objects as desired.
      *
      * @param fd File descriptor
@@ -315,6 +339,8 @@
                 return "NITZ_TIME";
             case TelephonyEvent.Type.EMERGENCY_NUMBER_REPORT:
                 return "EMERGENCY_NUMBER_REPORT";
+            case TelephonyEvent.Type.NETWORK_CAPABILITIES_CHANGED:
+                return "NETWORK_CAPABILITIES_CHANGED";
             default:
                 return Integer.toString(event);
         }
@@ -435,6 +461,8 @@
                         + "(" + "Data RAT " + event.serviceState.dataRat
                         + " Voice RAT " + event.serviceState.voiceRat
                         + " Channel Number " + event.serviceState.channelNumber
+                        + " NR Frequency Range " + event.serviceState.nrFrequencyRange
+                        + " NR State " + event.serviceState.nrState
                         + ")");
                 for (int i = 0; i < event.serviceState.networkRegistrationInfo.length; i++) {
                     pw.print("reg info: domain="
@@ -469,6 +497,8 @@
                             + "(" + "Data RAT " + event.serviceState.dataRat
                             + " Voice RAT " + event.serviceState.voiceRat
                             + " Channel Number " + event.serviceState.channelNumber
+                            + " NR Frequency Range " + event.serviceState.nrFrequencyRange
+                            + " NR State " + event.serviceState.nrState
                             + ")");
                 } else if (event.type == TelephonyCallSession.Event.Type.RIL_CALL_LIST_CHANGED) {
                     pw.println(callSessionEventToString(event.type));
@@ -555,7 +585,11 @@
         pw.decreaseIndent();
         pw.println("Modem power stats:");
         pw.increaseIndent();
-        ModemPowerStats s = new ModemPowerMetrics().buildProto();
+
+        BatteryStatsManager batteryStatsManager = mContext == null ? null :
+                (BatteryStatsManager) mContext.getSystemService(Context.BATTERY_STATS_SERVICE);
+        ModemPowerStats s = new ModemPowerMetrics(batteryStatsManager).buildProto();
+
         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);
@@ -654,6 +688,13 @@
             addTelephonyEvent(event);
         }
 
+        for (int i = 0; i < mLastNetworkCapabilitiesInfos.size(); i++) {
+            final int key = mLastNetworkCapabilitiesInfos.keyAt(i);
+            TelephonyEvent event = new TelephonyEventBuilder(mStartElapsedTimeMs, key)
+                    .setNetworkCapabilities(mLastNetworkCapabilitiesInfos.get(key)).build();
+            addTelephonyEvent(event);
+        }
+
         for (int i = 0; i < mLastRilDataCallEvents.size(); i++) {
             final int key = mLastRilDataCallEvents.keyAt(i);
             for (int j = 0; j < mLastRilDataCallEvents.get(key).size(); j++) {
@@ -707,7 +748,9 @@
         }
 
         // Build modem power metrics
-        log.modemPowerStats = new ModemPowerMetrics().buildProto();
+        BatteryStatsManager batteryStatsManager = mContext == null ? null :
+                (BatteryStatsManager) mContext.getSystemService(Context.BATTERY_STATS_SERVICE);
+        log.modemPowerStats = new ModemPowerMetrics(batteryStatsManager).buildProto();
 
         // Log the hardware revision
         log.hardwareRevision = SystemProperties.get("ro.boot.revision", "");
@@ -763,6 +806,9 @@
             activeSubscriptionInfo.slotIndex = phoneId;
             activeSubscriptionInfo.isOpportunistic = info.isOpportunistic() ? 1 : 0;
             activeSubscriptionInfo.carrierId = info.getCarrierId();
+            if (info.getMccString() != null && info.getMncString() != null) {
+                activeSubscriptionInfo.simMccmnc = info.getMccString() + info.getMncString();
+            }
             if (!MessageNano.messageNanoEquals(
                     mLastActiveSubscriptionInfos.get(phoneId), activeSubscriptionInfo)) {
                 addTelephonyEvent(new TelephonyEventBuilder(phoneId)
@@ -888,31 +934,20 @@
         ssProto.dataRoamingType = serviceState.getDataRoamingType();
 
         ssProto.voiceOperator = new TelephonyServiceState.TelephonyOperator();
-
-        if (serviceState.getVoiceOperatorAlphaLong() != null) {
-            ssProto.voiceOperator.alphaLong = serviceState.getVoiceOperatorAlphaLong();
-        }
-
-        if (serviceState.getVoiceOperatorAlphaShort() != null) {
-            ssProto.voiceOperator.alphaShort = serviceState.getVoiceOperatorAlphaShort();
-        }
-
-        if (serviceState.getVoiceOperatorNumeric() != null) {
-            ssProto.voiceOperator.numeric = serviceState.getVoiceOperatorNumeric();
-        }
-
         ssProto.dataOperator = new TelephonyServiceState.TelephonyOperator();
-
-        if (serviceState.getDataOperatorAlphaLong() != null) {
-            ssProto.dataOperator.alphaLong = serviceState.getDataOperatorAlphaLong();
+        if (serviceState.getOperatorAlphaLong() != null) {
+            ssProto.voiceOperator.alphaLong = serviceState.getOperatorAlphaLong();
+            ssProto.dataOperator.alphaLong = serviceState.getOperatorAlphaLong();
         }
 
-        if (serviceState.getDataOperatorAlphaShort() != null) {
-            ssProto.dataOperator.alphaShort = serviceState.getDataOperatorAlphaShort();
+        if (serviceState.getOperatorAlphaShort() != null) {
+            ssProto.voiceOperator.alphaShort = serviceState.getOperatorAlphaShort();
+            ssProto.dataOperator.alphaShort = serviceState.getOperatorAlphaShort();
         }
 
-        if (serviceState.getDataOperatorNumeric() != null) {
-            ssProto.dataOperator.numeric = serviceState.getDataOperatorNumeric();
+        if (serviceState.getOperatorNumeric() != null) {
+            ssProto.voiceOperator.numeric = serviceState.getOperatorNumeric();
+            ssProto.dataOperator.numeric = serviceState.getOperatorNumeric();
         }
 
         // Log PS WWAN only because CS WWAN would be exactly the same as voiceRat, and PS WLAN
@@ -938,6 +973,8 @@
         ssProto.voiceRat = serviceState.getRilVoiceRadioTechnology();
         ssProto.dataRat = serviceState.getRilDataRadioTechnology();
         ssProto.channelNumber = serviceState.getChannelNumber();
+        ssProto.nrFrequencyRange = serviceState.getNrFrequencyRange();
+        ssProto.nrState = serviceState.getNrState();
         return ssProto;
     }
 
@@ -1568,6 +1605,10 @@
                 call.isEmergencyCall = conn.isEmergencyCall();
                 call.emergencyNumberInfo = convertEmergencyNumberToEmergencyNumberInfo(
                         conn.getEmergencyNumberInfo());
+                EmergencyNumberTracker emergencyNumberTracker = conn.getEmergencyNumberTracker();
+                call.emergencyNumberDatabaseVersion = emergencyNumberTracker != null
+                        ? emergencyNumberTracker.getEmergencyNumberDbVersion()
+                        : TelephonyManager.INVALID_EMERGENCY_NUMBER_DB_VERSION;
             }
         }
     }
@@ -1778,7 +1819,7 @@
             Rlog.e(TAG, "SMS session is missing");
         } else {
 
-            int errorCode = 0;
+            int errorCode = SmsResponse.NO_ERROR_CODE;
             if (response != null) {
                 errorCode = response.mErrorCode;
             }
@@ -1800,9 +1841,11 @@
      *
      * @param phoneId Phone id
      * @param errorReason Defined in {@link SmsManager} RESULT_XXX.
+     * @param messageId Unique id for this message.
      */
     public synchronized void writeOnImsServiceSmsSolicitedResponse(int phoneId,
-            @ImsSmsImplBase.SendStatusResult int resultCode, int errorReason) {
+            @ImsSmsImplBase.SendStatusResult int resultCode, int errorReason,
+            long messageId) {
 
         InProgressSmsSession smsSession = mInProgressSmsSessions.get(phoneId);
         if (smsSession == null) {
@@ -1813,6 +1856,7 @@
                     SmsSession.Event.Type.SMS_SEND_RESULT)
                     .setImsServiceErrno(resultCode)
                     .setErrorCode(errorReason)
+                    .setMessageId((messageId))
             );
 
             smsSession.decreaseExpectedResponse();
@@ -2088,6 +2132,9 @@
             cq.averageRelativeJitterMillis = callQuality.getAverageRelativeJitter();
             cq.maxRelativeJitterMillis = callQuality.getMaxRelativeJitter();
             cq.codecType = convertImsCodec(callQuality.getCodecType());
+            cq.rtpInactivityDetected = callQuality.isRtpInactivityDetected();
+            cq.rxSilenceDetected = callQuality.isIncomingSilenceDetectedAtCallSetup();
+            cq.txSilenceDetected = callQuality.isOutgoingSilenceDetectedAtCallSetup();
         }
         return cq;
     }
@@ -2119,10 +2166,15 @@
      * @param phoneId Phone id
      * @param session IMS call session
      * @param reasonInfo Call end reason
+     * @param cqm Call Quality Metrics
+     * @param emergencyNumber Emergency Number Info
+     * @param countryIso Network country iso
+     * @param emergencyNumberDatabaseVersion Emergency Number Database Version
      */
     public void writeOnImsCallTerminated(int phoneId, ImsCallSession session,
                                          ImsReasonInfo reasonInfo, CallQualityMetrics cqm,
-                                         EmergencyNumber emergencyNumber, String countryIso) {
+                                         EmergencyNumber emergencyNumber, String countryIso,
+                                         int emergencyNumberDatabaseVersion) {
         InProgressCallSession callSession = mInProgressCallSessions.get(phoneId);
         if (callSession == null) {
             Rlog.e(TAG, "Call session is missing");
@@ -2144,6 +2196,8 @@
                     callSessionEvent.setIsImsEmergencyCall(true);
                     callSessionEvent.setImsEmergencyNumberInfo(
                             convertEmergencyNumberToEmergencyNumberInfo(emergencyNumber));
+                    callSessionEvent.setEmergencyNumberDatabaseVersion(
+                            emergencyNumberDatabaseVersion);
                 }
             }
             callSession.addEvent(callSessionEvent);
@@ -2350,13 +2404,16 @@
      * @param timestamps array with timestamps of each incoming SMS part. It contains a single
      * @param blocked indicates if the message was blocked or not.
      * @param success Indicates if the SMS-PP was successfully delivered to the USIM.
+     * @param messageId Unique id for this message.
      */
     private void writeIncomingSmsSessionWithType(int phoneId, int type, boolean smsOverIms,
-            String format, long[] timestamps, boolean blocked, boolean success) {
+            String format, long[] timestamps, boolean blocked, boolean success,
+            long messageId) {
         logv("Logged SMS session consisting of " + timestamps.length
                 + " parts, over IMS = " + smsOverIms
                 + " blocked = " + blocked
-                + " type = " + type);
+                + " type = " + type
+                + " messageId = " + messageId);
 
         InProgressSmsSession smsSession = startNewSmsSession(phoneId);
         for (long time : timestamps) {
@@ -2368,7 +2425,8 @@
                         .setErrorCode(success ? SmsManager.RESULT_ERROR_NONE :
                             SmsManager.RESULT_ERROR_GENERIC_FAILURE)
                         .setSmsType(type)
-                        .setBlocked(blocked);
+                        .setBlocked(blocked)
+                        .setMessageId(messageId);
             smsSession.addEvent(time, eventBuilder);
         }
         finishSmsSession(smsSession);
@@ -2382,11 +2440,12 @@
      * @param format SMS format. Either 3GPP or 3GPP2.
      * @param timestamps array with timestamps of each incoming SMS part. It contains a single
      * @param success Indicates if the SMS-PP was successfully delivered to the USIM.
+     * @param messageId Unique id for this message.
      */
     public void writeIncomingWapPush(int phoneId, boolean smsOverIms, String format,
-            long[] timestamps, boolean success) {
+            long[] timestamps, boolean success, long messageId) {
         writeIncomingSmsSessionWithType(phoneId, SmsSession.Event.SmsType.SMS_TYPE_WAP_PUSH,
-                smsOverIms, format, timestamps, false, success);
+                smsOverIms, format, timestamps, false, success, messageId);
     }
 
     /**
@@ -2397,11 +2456,12 @@
      * @param format SMS format. Either 3GPP or 3GPP2.
      * @param timestamps array with timestamps of each incoming SMS part. It contains a single
      * @param blocked indicates if the message was blocked or not.
+     * @param messageId Unique id for this message.
      */
     public void writeIncomingSmsSession(int phoneId, boolean smsOverIms, String format,
-            long[] timestamps, boolean blocked) {
+            long[] timestamps, boolean blocked, long messageId) {
         writeIncomingSmsSessionWithType(phoneId, SmsSession.Event.SmsType.SMS_TYPE_NORMAL,
-                smsOverIms, format, timestamps, blocked, true);
+                smsOverIms, format, timestamps, blocked, true, messageId);
     }
 
     /**
@@ -2507,14 +2567,14 @@
         }
 
         // fill in complete matching information from the SIM.
-        carrierIdMatchingResult.mccmnc = TextUtils.emptyIfNull(simInfo.mccMnc);
-        carrierIdMatchingResult.spn = TextUtils.emptyIfNull(simInfo.spn);
-        carrierIdMatchingResult.pnn = TextUtils.emptyIfNull(simInfo.plmn);
-        carrierIdMatchingResult.gid1 = TextUtils.emptyIfNull(simInfo.gid1);
-        carrierIdMatchingResult.gid2 = TextUtils.emptyIfNull(simInfo.gid2);
-        carrierIdMatchingResult.imsiPrefix = TextUtils.emptyIfNull(simInfo.imsiPrefixPattern);
-        carrierIdMatchingResult.iccidPrefix = TextUtils.emptyIfNull(simInfo.iccidPrefix);
-        carrierIdMatchingResult.preferApn = TextUtils.emptyIfNull(simInfo.apn);
+        carrierIdMatchingResult.mccmnc = TelephonyUtils.emptyIfNull(simInfo.mccMnc);
+        carrierIdMatchingResult.spn = TelephonyUtils.emptyIfNull(simInfo.spn);
+        carrierIdMatchingResult.pnn = TelephonyUtils.emptyIfNull(simInfo.plmn);
+        carrierIdMatchingResult.gid1 = TelephonyUtils.emptyIfNull(simInfo.gid1);
+        carrierIdMatchingResult.gid2 = TelephonyUtils.emptyIfNull(simInfo.gid2);
+        carrierIdMatchingResult.imsiPrefix = TelephonyUtils.emptyIfNull(simInfo.imsiPrefixPattern);
+        carrierIdMatchingResult.iccidPrefix = TelephonyUtils.emptyIfNull(simInfo.iccidPrefix);
+        carrierIdMatchingResult.preferApn = TelephonyUtils.emptyIfNull(simInfo.apn);
         if (simInfo.privilegeAccessRule != null) {
             carrierIdMatchingResult.privilegeAccessRule =
                     simInfo.privilegeAccessRule.stream().toArray(String[]::new);
@@ -2534,7 +2594,8 @@
      *
      * @param emergencyNumber Updated emergency number
      */
-    public void writeEmergencyNumberUpdateEvent(int phoneId, EmergencyNumber emergencyNumber) {
+    public void writeEmergencyNumberUpdateEvent(int phoneId, EmergencyNumber emergencyNumber,
+            int emergencyNumberDatabaseVersion) {
         if (emergencyNumber == null) {
             return;
         }
@@ -2542,7 +2603,25 @@
                 convertEmergencyNumberToEmergencyNumberInfo(emergencyNumber);
 
         TelephonyEvent event = new TelephonyEventBuilder(phoneId).setUpdatedEmergencyNumber(
-                emergencyNumberInfo).build();
+                emergencyNumberInfo, emergencyNumberDatabaseVersion).build();
+        addTelephonyEvent(event);
+    }
+
+    /**
+     * Write network capabilities changed event
+     *
+     * @param phoneId Phone id
+     * @param networkCapabilities Network capabilities
+     */
+    public void writeNetworkCapabilitiesChangedEvent(int phoneId,
+            NetworkCapabilities networkCapabilities) {
+        final NetworkCapabilitiesInfo caps = new NetworkCapabilitiesInfo();
+        caps.isNetworkUnmetered = networkCapabilities.hasCapability(
+                NetworkCapabilities.NET_CAPABILITY_TEMPORARILY_NOT_METERED);
+
+        TelephonyEvent event = new TelephonyEventBuilder(phoneId)
+                .setNetworkCapabilities(caps).build();
+        mLastNetworkCapabilitiesInfos.put(phoneId, caps);
         addTelephonyEvent(event);
     }
 
diff --git a/src/java/com/android/internal/telephony/metrics/VoiceCallRatTracker.java b/src/java/com/android/internal/telephony/metrics/VoiceCallRatTracker.java
new file mode 100644
index 0000000..7d7cd0e
--- /dev/null
+++ b/src/java/com/android/internal/telephony/metrics/VoiceCallRatTracker.java
@@ -0,0 +1,244 @@
+/*
+ * Copyright (C) 2020 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.metrics;
+
+import com.android.internal.telephony.nano.PersistAtomsProto.RawVoiceCallRatUsage;
+import com.android.telephony.Rlog;
+
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+
+/**
+ * Uses a HashMap to track carrier and RAT usage, in terms of duration and number of calls.
+ *
+ * <p>This tracker is first used by each call, and then used for aggregating usages across calls.
+ *
+ * <p>This class is not thread-safe. Callers (from {@link PersistAtomsStorage} and {@link
+ * VoiceCallSessionStats}) should ensure each instance of this class is accessed by only one thread
+ * at a time.
+ */
+public class VoiceCallRatTracker {
+    private static final String TAG = VoiceCallRatTracker.class.getSimpleName();
+
+    /** A Map holding all carrier-RAT combinations and corresponding durations and call counts. */
+    private final Map<Key, Value> mRatUsageMap = new HashMap<>();
+
+    /** Tracks last carrier/RAT combination during a call. */
+    private Key mLastKey;
+
+    /** Tracks the time when last carrier/RAT combination was updated. */
+    private long mLastKeyTimestampMillis;
+
+    /** Creates an empty RAT tracker for each call. */
+    VoiceCallRatTracker() {
+        clear();
+    }
+
+    /** Creates an RAT tracker from saved atoms at startup. */
+    public static VoiceCallRatTracker fromProto(RawVoiceCallRatUsage[] usages) {
+        VoiceCallRatTracker tracker = new VoiceCallRatTracker();
+        if (usages == null) {
+            Rlog.e(TAG, "fromProto: usages=null");
+        } else {
+            Arrays.stream(usages).forEach(e -> tracker.addProto(e));
+        }
+        return tracker;
+    }
+
+    /** Append the map to javanano persist atoms. */
+    public RawVoiceCallRatUsage[] toProto() {
+        return mRatUsageMap.entrySet().stream()
+                .map(VoiceCallRatTracker::entryToProto)
+                .toArray(RawVoiceCallRatUsage[]::new);
+    }
+
+    /** Resets the tracker. */
+    public void clear() {
+        mRatUsageMap.clear();
+        mLastKey = null;
+        mLastKeyTimestampMillis = 0L;
+    }
+
+    /** Adds one RAT usage to the tracker during a call session. */
+    public void add(int carrierId, int rat, long timestampMillis, Set<Integer> connectionIds) {
+        // both new RAT (different key) and new connections (same key) needs to be added
+        // set duration and connections (may have changed) for last RAT/carrier combination
+        if (mLastKey != null) {
+            long durationMillis = timestampMillis - mLastKeyTimestampMillis;
+            if (durationMillis < 0L) {
+                Rlog.e(TAG, "add: durationMillis<0");
+                durationMillis = 0L;
+            }
+            addToKey(mLastKey, durationMillis, connectionIds);
+        }
+
+        // set connections for new RAT/carrier combination
+        Key key = new Key(carrierId, rat);
+        addToKey(key, 0L, connectionIds);
+
+        mLastKey = key;
+        mLastKeyTimestampMillis = timestampMillis;
+    }
+
+    /** Finalize the duration of the last RAT at the end of a call session. */
+    public void conclude(long timestampMillis) {
+        if (mLastKey != null) {
+            long durationMillis = timestampMillis - mLastKeyTimestampMillis;
+            if (durationMillis < 0L) {
+                Rlog.e(TAG, "conclude: durationMillis<0");
+                durationMillis = 0L;
+            }
+            Value value = mRatUsageMap.get(mLastKey);
+            if (value == null) {
+                Rlog.e(TAG, "conclude: value=null && mLastKey!=null");
+            } else {
+                value.durationMillis += durationMillis;
+            }
+            mRatUsageMap.values().stream().forEach(Value::endSession);
+        } else {
+            Rlog.e(TAG, "conclude: mLastKey=null");
+        }
+    }
+
+    /** Merges this tracker with another instance created during a call session. */
+    public VoiceCallRatTracker mergeWith(VoiceCallRatTracker that) {
+        if (that == null) {
+            Rlog.e(TAG, "mergeWith: attempting to merge with null", new Throwable());
+        } else {
+            that.mRatUsageMap.entrySet().stream()
+                    .forEach(
+                            e ->
+                                    this.mRatUsageMap.merge(
+                                            e.getKey(), e.getValue(), Value::mergeInPlace));
+        }
+        return this;
+    }
+
+    private void addToKey(Key key, long durationMillis, Set<Integer> connectionIds) {
+        Value value = mRatUsageMap.get(key);
+        if (value == null) {
+            mRatUsageMap.put(key, new Value(durationMillis, connectionIds));
+        } else {
+            value.add(durationMillis, connectionIds);
+        }
+    }
+
+    private void addProto(RawVoiceCallRatUsage usage) {
+        mRatUsageMap.put(Key.fromProto(usage), Value.fromProto(usage));
+    }
+
+    private static RawVoiceCallRatUsage entryToProto(Map.Entry<Key, Value> entry) {
+        Key key = entry.getKey();
+        Value value = entry.getValue();
+        RawVoiceCallRatUsage usage = new RawVoiceCallRatUsage();
+        usage.carrierId = key.carrierId;
+        usage.rat = key.rat;
+        if (value.mConnectionIds != null) {
+            Rlog.e(TAG, "call not concluded when converting to proto");
+        }
+        usage.totalDurationMillis = value.durationMillis;
+        usage.callCount = value.callCount;
+        return usage;
+    }
+
+    /*
+     * NOTE: proto does not support message as map keys, and javanano generates mutable objects for
+     * keys anyways. So it is better to implement the key/value class in java.
+     */
+
+    private static class Key {
+        public final int carrierId;
+        public final int rat;
+
+        Key(int carrierId, int rat) {
+            this.carrierId = carrierId;
+            this.rat = rat;
+        }
+
+        static Key fromProto(RawVoiceCallRatUsage usage) {
+            return new Key(usage.carrierId, usage.rat);
+        }
+
+        public int hashCode() {
+            return Objects.hash(carrierId, rat);
+        }
+
+        public boolean equals(Object that) {
+            if (that == null || that.getClass() != this.getClass()) {
+                return false;
+            }
+            Key thatKey = (Key) that;
+            return thatKey.carrierId == this.carrierId && thatKey.rat == this.rat;
+        }
+    }
+
+    private static class Value {
+        public long durationMillis;
+        public long callCount;
+
+        private Set<Integer> mConnectionIds;
+
+        Value(long durationMillis, Set<Integer> connectionIds) {
+            this.durationMillis = durationMillis;
+            mConnectionIds = connectionIds;
+            callCount = 0L;
+        }
+
+        private Value(long durationMillis, long callCount) {
+            this.durationMillis = durationMillis;
+            mConnectionIds = null;
+            this.callCount = callCount;
+        }
+
+        void add(long durationMillis, Set<Integer> connectionIds) {
+            this.durationMillis += durationMillis;
+            if (mConnectionIds != null) {
+                mConnectionIds.addAll(connectionIds);
+            } else {
+                Rlog.e(TAG, "Value: trying to add to concluded call");
+            }
+        }
+
+        void endSession() {
+            if (mConnectionIds != null) {
+                if (callCount != 0L) {
+                    Rlog.e(TAG, "Value: mConnectionIds!=null && callCount!=0");
+                }
+                callCount = mConnectionIds.size();
+                mConnectionIds = null; // allow GC
+            }
+        }
+
+        static Value fromProto(RawVoiceCallRatUsage usage) {
+            Value value = new Value(usage.totalDurationMillis, usage.callCount);
+            return value;
+        }
+
+        static Value mergeInPlace(Value dest, Value src) {
+            if (src.mConnectionIds != null || dest.mConnectionIds != null) {
+                Rlog.e(TAG, "Value: call not concluded yet when merging");
+            }
+            // NOTE: does not handle overflow since it is practically impossible
+            dest.durationMillis += src.durationMillis;
+            dest.callCount += src.callCount;
+            return dest;
+        }
+    }
+}
diff --git a/src/java/com/android/internal/telephony/metrics/VoiceCallSessionStats.java b/src/java/com/android/internal/telephony/metrics/VoiceCallSessionStats.java
new file mode 100644
index 0000000..ad6d52d
--- /dev/null
+++ b/src/java/com/android/internal/telephony/metrics/VoiceCallSessionStats.java
@@ -0,0 +1,638 @@
+/*
+ * Copyright (C) 2020 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.metrics;
+
+import static com.android.internal.telephony.TelephonyStatsLog.VOICE_CALL_SESSION__BEARER_AT_END__CALL_BEARER_CS;
+import static com.android.internal.telephony.TelephonyStatsLog.VOICE_CALL_SESSION__BEARER_AT_END__CALL_BEARER_IMS;
+import static com.android.internal.telephony.TelephonyStatsLog.VOICE_CALL_SESSION__BEARER_AT_END__CALL_BEARER_UNKNOWN;
+import static com.android.internal.telephony.TelephonyStatsLog.VOICE_CALL_SESSION__DIRECTION__CALL_DIRECTION_MO;
+import static com.android.internal.telephony.TelephonyStatsLog.VOICE_CALL_SESSION__DIRECTION__CALL_DIRECTION_MT;
+import static com.android.internal.telephony.TelephonyStatsLog.VOICE_CALL_SESSION__SETUP_DURATION__CALL_SETUP_DURATION_EXTREMELY_FAST;
+import static com.android.internal.telephony.TelephonyStatsLog.VOICE_CALL_SESSION__SETUP_DURATION__CALL_SETUP_DURATION_EXTREMELY_SLOW;
+import static com.android.internal.telephony.TelephonyStatsLog.VOICE_CALL_SESSION__SETUP_DURATION__CALL_SETUP_DURATION_FAST;
+import static com.android.internal.telephony.TelephonyStatsLog.VOICE_CALL_SESSION__SETUP_DURATION__CALL_SETUP_DURATION_NORMAL;
+import static com.android.internal.telephony.TelephonyStatsLog.VOICE_CALL_SESSION__SETUP_DURATION__CALL_SETUP_DURATION_SLOW;
+import static com.android.internal.telephony.TelephonyStatsLog.VOICE_CALL_SESSION__SETUP_DURATION__CALL_SETUP_DURATION_ULTRA_FAST;
+import static com.android.internal.telephony.TelephonyStatsLog.VOICE_CALL_SESSION__SETUP_DURATION__CALL_SETUP_DURATION_ULTRA_SLOW;
+import static com.android.internal.telephony.TelephonyStatsLog.VOICE_CALL_SESSION__SETUP_DURATION__CALL_SETUP_DURATION_UNKNOWN;
+import static com.android.internal.telephony.TelephonyStatsLog.VOICE_CALL_SESSION__SETUP_DURATION__CALL_SETUP_DURATION_VERY_FAST;
+import static com.android.internal.telephony.TelephonyStatsLog.VOICE_CALL_SESSION__SETUP_DURATION__CALL_SETUP_DURATION_VERY_SLOW;
+
+import android.annotation.Nullable;
+import android.os.SystemClock;
+import android.telephony.Annotation.NetworkType;
+import android.telephony.DisconnectCause;
+import android.telephony.ServiceState;
+import android.telephony.SubscriptionInfo;
+import android.telephony.TelephonyManager;
+import android.telephony.ims.ImsReasonInfo;
+import android.telephony.ims.ImsStreamMediaProfile;
+import android.util.SparseArray;
+import android.util.SparseIntArray;
+import android.util.SparseLongArray;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.telephony.Call;
+import com.android.internal.telephony.Connection;
+import com.android.internal.telephony.DriverCall;
+import com.android.internal.telephony.GsmCdmaConnection;
+import com.android.internal.telephony.Phone;
+import com.android.internal.telephony.PhoneConstants;
+import com.android.internal.telephony.PhoneFactory;
+import com.android.internal.telephony.ServiceStateTracker;
+import com.android.internal.telephony.imsphone.ImsPhoneConnection;
+import com.android.internal.telephony.nano.PersistAtomsProto.VoiceCallSession;
+import com.android.internal.telephony.nano.TelephonyProto.TelephonyCallSession.Event.AudioCodec;
+import com.android.internal.telephony.uicc.UiccController;
+import com.android.internal.telephony.uicc.UiccSlot;
+import com.android.telephony.Rlog;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+/** Collects voice call events per phone ID for the pulled atom. */
+public class VoiceCallSessionStats {
+    private static final String TAG = VoiceCallSessionStats.class.getSimpleName();
+
+    /** Bitmask value of unknown audio codecs. */
+    private static final long AUDIO_CODEC_UNKNOWN = 1L << AudioCodec.AUDIO_CODEC_UNKNOWN;
+
+    /**
+     * Value denoting the carrier ID being unknown.
+     *
+     * <p>NOTE: 0 is unused in {@code carrier_list.textpb} (it starts from 1).
+     */
+    private static final int CARRIER_ID_UNKNOWN = 0;
+
+    /** Holds the audio codec bitmask value for CS calls. */
+    private static final SparseLongArray CS_CODEC_MAP = buildGsmCdmaCodecMap();
+
+    /** Holds the audio codec bitmask value for IMS calls. */
+    private static final SparseLongArray IMS_CODEC_MAP = buildImsCodecMap();
+
+    /** Holds setup duration buckets with keys as their lower bounds in milliseconds. */
+    private static final SparseIntArray CALL_SETUP_DURATION_MAP = buildCallSetupDurationMap();
+
+    /**
+     * Tracks statistics for each call connection, indexed with ID returned by {@link
+     * #getConnectionId}.
+     */
+    private final SparseArray<VoiceCallSession> mCallProtos = new SparseArray<>();
+
+    /**
+     * Tracks call RAT usage.
+     *
+     * <p>RAT usage is mainly tied to phones rather than calls, since each phone can have multiple
+     * concurrent calls, and we do not want to count the RAT duration multiple times.
+     */
+    private final VoiceCallRatTracker mRatUsage = new VoiceCallRatTracker();
+
+    private final int mPhoneId;
+    private final Phone mPhone;
+    private int mCarrierId = CARRIER_ID_UNKNOWN;
+
+    private final PersistAtomsStorage mAtomsStorage =
+            PhoneFactory.getMetricsCollector().getAtomsStorage();
+    private final UiccController mUiccController = UiccController.getInstance();
+
+    public VoiceCallSessionStats(int phoneId, Phone phone) {
+        mPhoneId = phoneId;
+        mPhone = phone;
+    }
+
+    /* CS calls */
+
+    /** Updates internal states when previous CS calls are accepted to track MT call setup time. */
+    public synchronized void onRilAcceptCall(List<Connection> connections) {
+        for (Connection conn : connections) {
+            addCall(conn);
+        }
+    }
+
+    /** Updates internal states when a CS MO call is created. */
+    public synchronized void onRilDial(Connection conn) {
+        addCall(conn);
+    }
+
+    /**
+     * Updates internal states when CS calls are created or terminated, or CS call state is changed.
+     */
+    public synchronized void onRilCallListChanged(List<GsmCdmaConnection> connections) {
+        for (Connection conn : connections) {
+            int id = getConnectionId(conn);
+            if (!mCallProtos.contains(id)) {
+                // handle new connections
+                if (conn.getDisconnectCause() == DisconnectCause.NOT_DISCONNECTED) {
+                    addCall(conn);
+                    checkCallSetup(conn, mCallProtos.get(id));
+                } else {
+                    logd("onRilCallListChanged: skip adding disconnected connection");
+                }
+            } else {
+                VoiceCallSession proto = mCallProtos.get(id);
+                // handle call state change
+                checkCallSetup(conn, proto);
+                // handle terminated connections
+                if (conn.getDisconnectCause() != DisconnectCause.NOT_DISCONNECTED) {
+                    proto.bearerAtEnd = getBearer(conn); // should be CS
+                    proto.disconnectReasonCode = conn.getDisconnectCause();
+                    proto.disconnectExtraCode = conn.getPreciseDisconnectCause();
+                    proto.disconnectExtraMessage = conn.getVendorDisconnectCause();
+                    finishCall(id);
+                }
+            }
+        }
+        // NOTE: we cannot check stray connections (CS call in our list but not in RIL), as
+        // GsmCdmaCallTracker can call this with a partial list
+    }
+
+    /* IMS calls */
+
+    /** Updates internal states when an IMS MO call is created. */
+    public synchronized void onImsDial(ImsPhoneConnection conn) {
+        addCall(conn);
+        if (conn.hasRttTextStream()) {
+            setRttStarted(conn);
+        }
+    }
+
+    /** Updates internal states when an IMS MT call is created. */
+    public synchronized void onImsCallReceived(ImsPhoneConnection conn) {
+        addCall(conn);
+        if (conn.hasRttTextStream()) {
+            setRttStarted(conn);
+        }
+    }
+
+    /** Updates internal states when previous IMS calls are accepted to track MT call setup time. */
+    public synchronized void onImsAcceptCall(List<Connection> connections) {
+        for (Connection conn : connections) {
+            addCall(conn);
+        }
+    }
+
+    /** Updates internal states when an IMS call is terminated. */
+    public synchronized void onImsCallTerminated(
+            @Nullable ImsPhoneConnection conn, ImsReasonInfo reasonInfo) {
+        if (conn == null) {
+            List<Integer> imsConnIds = getImsConnectionIds();
+            if (imsConnIds.size() == 1) {
+                loge("onImsCallTerminated: ending IMS call w/ conn=null");
+                finishImsCall(imsConnIds.get(0), reasonInfo);
+            } else {
+                loge("onImsCallTerminated: %d IMS calls w/ conn=null", imsConnIds.size());
+            }
+        } else {
+            int id = getConnectionId(conn);
+            if (mCallProtos.contains(id)) {
+                finishImsCall(id, reasonInfo);
+            } else {
+                loge("onImsCallTerminated: untracked connection");
+                // fake a call so at least some info can be tracked
+                addCall(conn);
+                finishImsCall(id, reasonInfo);
+            }
+        }
+    }
+
+    /** Updates internal states when RTT is started on an IMS call. */
+    public synchronized void onRttStarted(ImsPhoneConnection conn) {
+        setRttStarted(conn);
+    }
+
+    /* general & misc. */
+
+    /** Updates internal states when carrier changes. */
+    public synchronized void onActiveSubscriptionInfoChanged(List<SubscriptionInfo> subInfos) {
+        int slotId = getSimSlotId();
+        if (subInfos != null) {
+            for (SubscriptionInfo subInfo : subInfos) {
+                if (subInfo.getSimSlotIndex() == slotId) {
+                    mCarrierId = subInfo.getCarrierId();
+                }
+            }
+        }
+    }
+
+    /** Updates internal states when audio codec for a call is changed. */
+    public synchronized void onAudioCodecChanged(Connection conn, int audioQuality) {
+        VoiceCallSession proto = mCallProtos.get(getConnectionId(conn));
+        if (proto == null) {
+            loge("onAudioCodecChanged: untracked connection");
+            return;
+        }
+        proto.codecBitmask |= audioQualityToCodecBitmask(proto.bearerAtEnd, audioQuality);
+    }
+
+    /**
+     * Updates internal states when a call changes state to track setup time and status.
+     *
+     * <p>This is currently mainly used by IMS since CS call states are updated through {@link
+     * #onRilCallListChanged}.
+     */
+    public synchronized void onCallStateChanged(Call call) {
+        for (Connection conn : call.getConnections()) {
+            VoiceCallSession proto = mCallProtos.get(getConnectionId(conn));
+            if (proto != null) {
+                checkCallSetup(conn, proto);
+            } else {
+                loge("onCallStateChanged: untracked connection");
+            }
+        }
+    }
+
+    /** Updates internal states when an IMS call is handover to a CS call. */
+    public synchronized void onRilSrvccStateChanged(int state) {
+        List<Connection> handoverConnections = null;
+        if (mPhone.getImsPhone() != null) {
+            loge("onRilSrvccStateChanged: ImsPhone is null");
+        } else {
+            handoverConnections = mPhone.getImsPhone().getHandoverConnection();
+        }
+        List<Integer> imsConnIds;
+        if (handoverConnections == null) {
+            imsConnIds = getImsConnectionIds();
+            loge("onRilSrvccStateChanged: ImsPhone has no handover, we have %d", imsConnIds.size());
+        } else {
+            imsConnIds =
+                    handoverConnections.stream()
+                            .map(VoiceCallSessionStats::getConnectionId)
+                            .collect(Collectors.toList());
+        }
+        switch (state) {
+            case TelephonyManager.SRVCC_STATE_HANDOVER_COMPLETED:
+                // connection will now be CS
+                for (int id : imsConnIds) {
+                    VoiceCallSession proto = mCallProtos.get(id);
+                    proto.srvccCompleted = true;
+                    proto.bearerAtEnd = VOICE_CALL_SESSION__BEARER_AT_END__CALL_BEARER_CS;
+                }
+                break;
+            case TelephonyManager.SRVCC_STATE_HANDOVER_FAILED:
+                for (int id : imsConnIds) {
+                    mCallProtos.get(id).srvccFailureCount++;
+                }
+                break;
+            case TelephonyManager.SRVCC_STATE_HANDOVER_CANCELED:
+                for (int id : imsConnIds) {
+                    mCallProtos.get(id).srvccCancellationCount++;
+                }
+                break;
+            default: // including STARTED and NONE, do nothing
+        }
+    }
+
+    /** Updates internal states when RAT changes. */
+    public synchronized void onServiceStateChanged(ServiceState state) {
+        if (hasCalls()) {
+            updateRatTracker(state);
+        }
+    }
+
+    /* internal */
+
+    /**
+     * Adds a call connection.
+     *
+     * <p>Should be called when the call is created, and when setup begins (upon {@code
+     * RilRequest.RIL_REQUEST_ANSWER} or {@code ImsCommand.IMS_CMD_ACCEPT}).
+     */
+    private void addCall(Connection conn) {
+        int id = getConnectionId(conn);
+        if (mCallProtos.contains(id)) {
+            // mostly handles ringing MT call getting accepted (MT call setup begins)
+            logd("addCall: resetting setup info");
+            VoiceCallSession proto = mCallProtos.get(id);
+            proto.setupBeginMillis = getTimeMillis();
+            proto.setupDuration = VOICE_CALL_SESSION__SETUP_DURATION__CALL_SETUP_DURATION_UNKNOWN;
+        } else {
+            int bearer = getBearer(conn);
+            ServiceState serviceState = getServiceState();
+            int rat = getRat(serviceState);
+
+            VoiceCallSession proto = new VoiceCallSession();
+
+            proto.bearerAtStart = bearer;
+            proto.bearerAtEnd = bearer;
+            proto.direction = getDirection(conn);
+            proto.setupDuration = VOICE_CALL_SESSION__SETUP_DURATION__CALL_SETUP_DURATION_UNKNOWN;
+            proto.setupFailed = true;
+            proto.disconnectReasonCode = conn.getDisconnectCause();
+            proto.disconnectExtraCode = conn.getPreciseDisconnectCause();
+            proto.disconnectExtraMessage = conn.getVendorDisconnectCause();
+            proto.ratAtStart = rat;
+            proto.ratAtEnd = rat;
+            proto.ratSwitchCount = 0L;
+            proto.codecBitmask = 0L;
+            proto.simSlotIndex = getSimSlotId();
+            proto.isMultiSim = SimSlotState.getCurrentState().numActiveSims > 1;
+            proto.isEsim = isEsim();
+            proto.carrierId = mCarrierId;
+            proto.srvccCompleted = false;
+            proto.srvccFailureCount = 0L;
+            proto.srvccCancellationCount = 0L;
+            proto.rttEnabled = false;
+            proto.isEmergency = conn.isEmergencyCall();
+            proto.isRoaming = serviceState != null ? serviceState.getVoiceRoaming() : false;
+
+            // internal fields for tracking
+            proto.setupBeginMillis = getTimeMillis();
+
+            proto.concurrentCallCountAtStart = mCallProtos.size();
+            mCallProtos.put(id, proto);
+
+            // RAT call count needs to be updated
+            updateRatTracker(serviceState);
+        }
+    }
+
+    /** Sends the call metrics to persist storage when it is finished. */
+    private void finishCall(int connectionId) {
+        VoiceCallSession proto = mCallProtos.get(connectionId);
+        if (proto == null) {
+            loge("finishCall: could not find call to be removed");
+            return;
+        }
+        mCallProtos.delete(connectionId);
+        proto.concurrentCallCountAtEnd = mCallProtos.size();
+
+        // ensure internal fields are cleared
+        proto.setupBeginMillis = 0L;
+
+        // sanitize for javanano & StatsEvent
+        if (proto.disconnectExtraMessage == null) {
+            proto.disconnectExtraMessage = "";
+        }
+
+        mAtomsStorage.addVoiceCallSession(proto);
+
+        // merge RAT usages to PersistPullers when the call session ends (i.e. no more active calls)
+        if (!hasCalls()) {
+            mRatUsage.conclude(getTimeMillis());
+            mAtomsStorage.addVoiceCallRatUsage(mRatUsage);
+            mRatUsage.clear();
+        }
+    }
+
+    private void setRttStarted(ImsPhoneConnection conn) {
+        VoiceCallSession proto = mCallProtos.get(getConnectionId(conn));
+        if (proto == null) {
+            loge("onRttStarted: untracked connection");
+            return;
+        }
+        // should be IMS w/o SRVCC
+        if (proto.bearerAtStart != getBearer(conn) || proto.bearerAtEnd != getBearer(conn)) {
+            loge("onRttStarted: connection bearer mismatch but proceeding");
+        }
+        proto.rttEnabled = true;
+    }
+
+    /** Returns a {@link Set} of Connection IDs so RAT usage can be correctly tracked. */
+    private Set<Integer> getConnectionIds() {
+        Set<Integer> ids = new HashSet<>();
+        for (int i = 0; i < mCallProtos.size(); i++) {
+            ids.add(mCallProtos.keyAt(i));
+        }
+        return ids;
+    }
+
+    private List<Integer> getImsConnectionIds() {
+        List<Integer> imsConnIds = new ArrayList<>(mCallProtos.size());
+        for (int i = 0; i < mCallProtos.size(); i++) {
+            if (mCallProtos.valueAt(i).bearerAtEnd
+                    == VOICE_CALL_SESSION__BEARER_AT_END__CALL_BEARER_IMS) {
+                imsConnIds.add(mCallProtos.keyAt(i));
+            }
+        }
+        return imsConnIds;
+    }
+
+    private boolean hasCalls() {
+        return mCallProtos.size() > 0;
+    }
+
+    private void checkCallSetup(Connection conn, VoiceCallSession proto) {
+        if (proto.setupBeginMillis != 0L && isSetupFinished(conn.getCall())) {
+            proto.setupDuration = classifySetupDuration(getTimeMillis() - proto.setupBeginMillis);
+            proto.setupBeginMillis = 0L;
+        }
+        // clear setupFailed if call now active, but otherwise leave it unchanged
+        if (conn.getState() == Call.State.ACTIVE) {
+            proto.setupFailed = false;
+        }
+    }
+
+    private void updateRatTracker(ServiceState state) {
+        int rat = getRat(state);
+        mRatUsage.add(mCarrierId, rat, getTimeMillis(), getConnectionIds());
+        for (int i = 0; i < mCallProtos.size(); i++) {
+            VoiceCallSession proto = mCallProtos.valueAt(i);
+            if (proto.ratAtEnd != rat) {
+                proto.ratSwitchCount++;
+                proto.ratAtEnd = rat;
+            }
+            // assuming that SIM carrier ID does not change during the call
+        }
+    }
+
+    private void finishImsCall(int id, ImsReasonInfo reasonInfo) {
+        VoiceCallSession proto = mCallProtos.get(id);
+        proto.bearerAtEnd = VOICE_CALL_SESSION__BEARER_AT_END__CALL_BEARER_IMS;
+        proto.disconnectReasonCode = reasonInfo.mCode;
+        proto.disconnectExtraCode = reasonInfo.mExtraCode;
+        proto.disconnectExtraMessage = reasonInfo.mExtraMessage;
+        finishCall(id);
+    }
+
+    private boolean isEsim() {
+        int slotId = getSimSlotId();
+        UiccSlot slot = mUiccController.getUiccSlot(slotId);
+        if (slot != null) {
+            return slot.isEuicc();
+        } else {
+            // should not happen, but assume we are not using eSIM
+            loge("isEsim: slot %d is null", slotId);
+            return false;
+        }
+    }
+
+    private int getSimSlotId() {
+        // NOTE: UiccController's mapping hasn't be initialized when Phone was created
+        return mUiccController.getSlotIdFromPhoneId(mPhoneId);
+    }
+
+    private @Nullable ServiceState getServiceState() {
+        ServiceStateTracker tracker = mPhone.getServiceStateTracker();
+        return tracker != null ? tracker.getServiceState() : null;
+    }
+
+    private static int getDirection(Connection conn) {
+        return conn.isIncoming()
+                ? VOICE_CALL_SESSION__DIRECTION__CALL_DIRECTION_MT
+                : VOICE_CALL_SESSION__DIRECTION__CALL_DIRECTION_MO;
+    }
+
+    private static int getBearer(Connection conn) {
+        int phoneType = conn.getPhoneType();
+        switch (phoneType) {
+            case PhoneConstants.PHONE_TYPE_GSM:
+            case PhoneConstants.PHONE_TYPE_CDMA:
+                return VOICE_CALL_SESSION__BEARER_AT_END__CALL_BEARER_CS;
+            case PhoneConstants.PHONE_TYPE_IMS:
+                return VOICE_CALL_SESSION__BEARER_AT_END__CALL_BEARER_IMS;
+            default:
+                loge("getBearer: unknown phoneType=%d", phoneType);
+                return VOICE_CALL_SESSION__BEARER_AT_END__CALL_BEARER_UNKNOWN;
+        }
+    }
+
+    private @NetworkType int getRat(@Nullable ServiceState state) {
+        if (state == null) {
+            return TelephonyManager.NETWORK_TYPE_UNKNOWN;
+        }
+        boolean isWifiCall =
+                mPhone.getImsPhone() != null
+                && mPhone.getImsPhone().isWifiCallingEnabled()
+                && state.getDataNetworkType() == TelephonyManager.NETWORK_TYPE_IWLAN;
+        return isWifiCall ? TelephonyManager.NETWORK_TYPE_IWLAN : state.getVoiceNetworkType();
+    }
+
+    // NOTE: when setup is finished for MO calls, it is not successful yet.
+    private static boolean isSetupFinished(@Nullable Call call) {
+        if (call != null) {
+            switch (call.getState()) {
+                case ACTIVE: // MT setup: accepted to ACTIVE
+                case ALERTING: // MO setup: dial to ALERTING
+                    return true;
+                default: // do nothing
+            }
+        }
+        return false;
+    }
+
+    private static long audioQualityToCodecBitmask(int bearer, int audioQuality) {
+        switch (bearer) {
+            case VOICE_CALL_SESSION__BEARER_AT_END__CALL_BEARER_CS:
+                return CS_CODEC_MAP.get(audioQuality, AUDIO_CODEC_UNKNOWN);
+            case VOICE_CALL_SESSION__BEARER_AT_END__CALL_BEARER_IMS:
+                return IMS_CODEC_MAP.get(audioQuality, AUDIO_CODEC_UNKNOWN);
+            default:
+                loge("audioQualityToCodecBitmask: unknown bearer %d", bearer);
+                return AUDIO_CODEC_UNKNOWN;
+        }
+    }
+
+    private static int classifySetupDuration(long durationMillis) {
+        for (int i = 0; i < CALL_SETUP_DURATION_MAP.size(); i++) {
+            if (durationMillis < CALL_SETUP_DURATION_MAP.keyAt(i)) {
+                return CALL_SETUP_DURATION_MAP.valueAt(i);
+            }
+        }
+        return VOICE_CALL_SESSION__SETUP_DURATION__CALL_SETUP_DURATION_EXTREMELY_SLOW;
+    }
+
+    /**
+     * Generates an ID for each connection, which should be the same for IMS and CS connections
+     * involved in the same SRVCC.
+     *
+     * <p>Among the fields copied from ImsPhoneConnection to GsmCdmaConnection during SRVCC, the
+     * Connection's create time seems to be the best choice for ID (assuming no multiple calls in a
+     * millisecond). The 64-bit time is truncated to 32-bit so it can be used as an index in various
+     * data structures, which is good for calls shorter than 49 days.
+     */
+    private static int getConnectionId(Connection conn) {
+        return conn == null ? 0 : (int) conn.getCreateTime();
+    }
+
+    @VisibleForTesting
+    protected long getTimeMillis() {
+        return SystemClock.elapsedRealtime();
+    }
+
+    private static void logd(String format, Object... args) {
+        Rlog.d(TAG, String.format(format, args));
+    }
+
+    private static void loge(String format, Object... args) {
+        Rlog.e(TAG, String.format(format, args));
+    }
+
+    private static SparseLongArray buildGsmCdmaCodecMap() {
+        SparseLongArray map = new SparseLongArray();
+
+        map.put(DriverCall.AUDIO_QUALITY_AMR, 1L << AudioCodec.AUDIO_CODEC_AMR);
+        map.put(DriverCall.AUDIO_QUALITY_AMR_WB, 1L << AudioCodec.AUDIO_CODEC_AMR_WB);
+        map.put(DriverCall.AUDIO_QUALITY_GSM_EFR, 1L << AudioCodec.AUDIO_CODEC_GSM_EFR);
+        map.put(DriverCall.AUDIO_QUALITY_GSM_FR, 1L << AudioCodec.AUDIO_CODEC_GSM_FR);
+        map.put(DriverCall.AUDIO_QUALITY_GSM_HR, 1L << AudioCodec.AUDIO_CODEC_GSM_HR);
+        map.put(DriverCall.AUDIO_QUALITY_EVRC, 1L << AudioCodec.AUDIO_CODEC_EVRC);
+        map.put(DriverCall.AUDIO_QUALITY_EVRC_B, 1L << AudioCodec.AUDIO_CODEC_EVRC_B);
+        map.put(DriverCall.AUDIO_QUALITY_EVRC_WB, 1L << AudioCodec.AUDIO_CODEC_EVRC_WB);
+        map.put(DriverCall.AUDIO_QUALITY_EVRC_NW, 1L << AudioCodec.AUDIO_CODEC_EVRC_NW);
+
+        return map;
+    }
+
+    private static SparseLongArray buildImsCodecMap() {
+        SparseLongArray map = new SparseLongArray();
+
+        map.put(ImsStreamMediaProfile.AUDIO_QUALITY_AMR, 1L << AudioCodec.AUDIO_CODEC_AMR);
+        map.put(ImsStreamMediaProfile.AUDIO_QUALITY_AMR_WB, 1L << AudioCodec.AUDIO_CODEC_AMR_WB);
+        map.put(
+                ImsStreamMediaProfile.AUDIO_QUALITY_QCELP13K,
+                1L << AudioCodec.AUDIO_CODEC_QCELP13K);
+        map.put(ImsStreamMediaProfile.AUDIO_QUALITY_EVRC, 1L << AudioCodec.AUDIO_CODEC_EVRC);
+        map.put(ImsStreamMediaProfile.AUDIO_QUALITY_EVRC_B, 1L << AudioCodec.AUDIO_CODEC_EVRC_B);
+        map.put(ImsStreamMediaProfile.AUDIO_QUALITY_EVRC_WB, 1L << AudioCodec.AUDIO_CODEC_EVRC_WB);
+        map.put(ImsStreamMediaProfile.AUDIO_QUALITY_EVRC_NW, 1L << AudioCodec.AUDIO_CODEC_EVRC_NW);
+        map.put(ImsStreamMediaProfile.AUDIO_QUALITY_GSM_EFR, 1L << AudioCodec.AUDIO_CODEC_GSM_EFR);
+        map.put(ImsStreamMediaProfile.AUDIO_QUALITY_GSM_FR, 1L << AudioCodec.AUDIO_CODEC_GSM_FR);
+        map.put(ImsStreamMediaProfile.AUDIO_QUALITY_GSM_HR, 1L << AudioCodec.AUDIO_CODEC_GSM_HR);
+        map.put(ImsStreamMediaProfile.AUDIO_QUALITY_G711U, 1L << AudioCodec.AUDIO_CODEC_G711U);
+        map.put(ImsStreamMediaProfile.AUDIO_QUALITY_G723, 1L << AudioCodec.AUDIO_CODEC_G723);
+        map.put(ImsStreamMediaProfile.AUDIO_QUALITY_G711A, 1L << AudioCodec.AUDIO_CODEC_G711A);
+        map.put(ImsStreamMediaProfile.AUDIO_QUALITY_G722, 1L << AudioCodec.AUDIO_CODEC_G722);
+        map.put(ImsStreamMediaProfile.AUDIO_QUALITY_G711AB, 1L << AudioCodec.AUDIO_CODEC_G711AB);
+        map.put(ImsStreamMediaProfile.AUDIO_QUALITY_G729, 1L << AudioCodec.AUDIO_CODEC_G729);
+        map.put(ImsStreamMediaProfile.AUDIO_QUALITY_EVS_NB, 1L << AudioCodec.AUDIO_CODEC_EVS_NB);
+        map.put(ImsStreamMediaProfile.AUDIO_QUALITY_EVS_WB, 1L << AudioCodec.AUDIO_CODEC_EVS_WB);
+        map.put(ImsStreamMediaProfile.AUDIO_QUALITY_EVS_SWB, 1L << AudioCodec.AUDIO_CODEC_EVS_SWB);
+        map.put(ImsStreamMediaProfile.AUDIO_QUALITY_EVS_FB, 1L << AudioCodec.AUDIO_CODEC_EVS_FB);
+
+        return map;
+    }
+
+    private static SparseIntArray buildCallSetupDurationMap() {
+        SparseIntArray map = new SparseIntArray();
+
+        map.put(0, VOICE_CALL_SESSION__SETUP_DURATION__CALL_SETUP_DURATION_UNKNOWN);
+        map.put(60, VOICE_CALL_SESSION__SETUP_DURATION__CALL_SETUP_DURATION_EXTREMELY_FAST);
+        map.put(100, VOICE_CALL_SESSION__SETUP_DURATION__CALL_SETUP_DURATION_ULTRA_FAST);
+        map.put(300, VOICE_CALL_SESSION__SETUP_DURATION__CALL_SETUP_DURATION_VERY_FAST);
+        map.put(600, VOICE_CALL_SESSION__SETUP_DURATION__CALL_SETUP_DURATION_FAST);
+        map.put(1000, VOICE_CALL_SESSION__SETUP_DURATION__CALL_SETUP_DURATION_NORMAL);
+        map.put(3000, VOICE_CALL_SESSION__SETUP_DURATION__CALL_SETUP_DURATION_SLOW);
+        map.put(6000, VOICE_CALL_SESSION__SETUP_DURATION__CALL_SETUP_DURATION_VERY_SLOW);
+        map.put(10000, VOICE_CALL_SESSION__SETUP_DURATION__CALL_SETUP_DURATION_ULTRA_SLOW);
+        // anything above would be CALL_SETUP_DURATION_EXTREMELY_SLOW
+
+        return map;
+    }
+}
diff --git a/src/java/com/android/internal/telephony/nitz/NitzSignalInputFilterPredicateFactory.java b/src/java/com/android/internal/telephony/nitz/NitzSignalInputFilterPredicateFactory.java
new file mode 100644
index 0000000..aa72722
--- /dev/null
+++ b/src/java/com/android/internal/telephony/nitz/NitzSignalInputFilterPredicateFactory.java
@@ -0,0 +1,273 @@
+/*
+ * Copyright 2019 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.nitz;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.os.PowerManager;
+import android.os.PowerManager.WakeLock;
+import android.os.TimestampedValue;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.telephony.NitzData;
+import com.android.internal.telephony.NitzStateMachine.DeviceState;
+import com.android.internal.telephony.nitz.NitzStateMachineImpl.NitzSignalInputFilterPredicate;
+import com.android.telephony.Rlog;
+
+import java.util.Arrays;
+import java.util.Objects;
+
+/**
+ * A factory class for the {@link NitzSignalInputFilterPredicate} instance used by
+ * {@link NitzStateMachineImpl}. This class is exposed for testing and provides access to various
+ * internal components.
+ */
+@VisibleForTesting
+public final class NitzSignalInputFilterPredicateFactory {
+
+    private static final String LOG_TAG = NitzStateMachineImpl.LOG_TAG;
+    private static final boolean DBG = NitzStateMachineImpl.DBG;
+    private static final String WAKELOCK_TAG = "NitzSignalInputFilterPredicateFactory";
+
+    private NitzSignalInputFilterPredicateFactory() {}
+
+    /**
+     * Returns the real {@link NitzSignalInputFilterPredicate} to use for NITZ signal input
+     * filtering.
+     */
+    @NonNull
+    public static NitzSignalInputFilterPredicate create(
+            @NonNull Context context, @NonNull DeviceState deviceState) {
+        Objects.requireNonNull(context);
+        Objects.requireNonNull(deviceState);
+
+        TrivalentPredicate[] components = new TrivalentPredicate[] {
+                // Disables NITZ processing entirely: can return false or null.
+                createIgnoreNitzPropertyCheck(deviceState),
+                // Filters bad reference times from new signals: can return false or null.
+                createBogusElapsedRealtimeCheck(context, deviceState),
+                // Ensures oldSignal == null is always processed: can return true or null.
+                createNoOldSignalCheck(),
+                // Adds rate limiting: can return true or false.
+                createRateLimitCheck(deviceState),
+        };
+        return new NitzSignalInputFilterPredicateImpl(components);
+    }
+
+    /**
+     * A filtering function that can give a {@code true} (must process), {@code false} (must not
+     * process) and a {@code null} (no opinion) response given a previous NITZ signal and a new
+     * signal. The previous signal may be {@code null} (unless ruled out by a prior
+     * {@link TrivalentPredicate}).
+     */
+    @VisibleForTesting
+    @FunctionalInterface
+    public interface TrivalentPredicate {
+
+        /**
+         * See {@link TrivalentPredicate}.
+         */
+        @Nullable
+        Boolean mustProcessNitzSignal(
+                @Nullable TimestampedValue<NitzData> previousSignal,
+                @NonNull TimestampedValue<NitzData> newSignal);
+    }
+
+    /**
+     * Returns a {@link TrivalentPredicate} function that implements a check for the
+     * "gsm.ignore-nitz" Android system property. The function can return {@code false} or
+     * {@code null}.
+     */
+    @VisibleForTesting
+    @NonNull
+    public static TrivalentPredicate createIgnoreNitzPropertyCheck(
+            @NonNull DeviceState deviceState) {
+        return (oldSignal, newSignal) -> {
+            boolean ignoreNitz = deviceState.getIgnoreNitz();
+            if (ignoreNitz) {
+                if (DBG) {
+                    Rlog.d(LOG_TAG, "mustProcessNitzSignal: Not processing NITZ signal because"
+                            + " gsm.ignore-nitz is set");
+                }
+                return false;
+            }
+            return null;
+        };
+    }
+
+    /**
+     * Returns a {@link TrivalentPredicate} function that implements a check for a bad reference
+     * time associated with {@code newSignal}. The function can return {@code false} or
+     * {@code null}.
+     */
+    @VisibleForTesting
+    @NonNull
+    public static TrivalentPredicate createBogusElapsedRealtimeCheck(
+            @NonNull Context context, @NonNull DeviceState deviceState) {
+        PowerManager powerManager =
+                (PowerManager) context.getSystemService(Context.POWER_SERVICE);
+        final WakeLock wakeLock =
+                powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, WAKELOCK_TAG);
+
+        return (oldSignal, newSignal) -> {
+            Objects.requireNonNull(newSignal);
+
+            // Validate the newSignal to reject obviously bogus elapsedRealtime values.
+            try {
+                // Acquire the wake lock as we are reading the elapsed realtime clock below.
+                wakeLock.acquire();
+
+                long elapsedRealtime = deviceState.elapsedRealtime();
+                long millisSinceNitzReceived = elapsedRealtime - newSignal.getReferenceTimeMillis();
+                if (millisSinceNitzReceived < 0 || millisSinceNitzReceived > Integer.MAX_VALUE) {
+                    if (DBG) {
+                        Rlog.d(LOG_TAG, "mustProcessNitzSignal: Not processing NITZ signal"
+                                + " because unexpected elapsedRealtime=" + elapsedRealtime
+                                + " nitzSignal=" + newSignal);
+                    }
+                    return false;
+                }
+                return null;
+            } finally {
+                wakeLock.release();
+            }
+        };
+    }
+
+    /**
+     * Returns a {@link TrivalentPredicate} function that implements a check for a {@code null}
+     * {@code oldSignal} (indicating there's no history). The function can return {@code true}
+     * or {@code null}.
+     */
+    @VisibleForTesting
+    @NonNull
+    public static TrivalentPredicate createNoOldSignalCheck() {
+        // Always process a signal when there was no previous signal.
+        return (oldSignal, newSignal) -> oldSignal == null ? true : null;
+    }
+
+    /**
+     * Returns a {@link TrivalentPredicate} function that implements filtering using
+     * {@code oldSignal} and {@code newSignal}. The function can return {@code true} or
+     * {@code false} and so is intended as the final function in a chain.
+     *
+     * Function detail: if an NITZ signal received that is too similar to a previous one
+     * it should be disregarded if it's received within a configured time period.
+     * The general contract for {@link TrivalentPredicate} allows {@code previousSignal} to be
+     * {@code null}, but previous functions are expected to prevent it in this case.
+     */
+    @VisibleForTesting
+    @NonNull
+    public static TrivalentPredicate createRateLimitCheck(@NonNull DeviceState deviceState) {
+        return new TrivalentPredicate() {
+            @Override
+            @NonNull
+            public Boolean mustProcessNitzSignal(
+                    @NonNull TimestampedValue<NitzData> previousSignal,
+                    @NonNull TimestampedValue<NitzData> newSignal) {
+                Objects.requireNonNull(newSignal);
+                Objects.requireNonNull(newSignal.getValue());
+                Objects.requireNonNull(previousSignal);
+                Objects.requireNonNull(previousSignal.getValue());
+
+                NitzData newNitzData = newSignal.getValue();
+                NitzData previousNitzData = previousSignal.getValue();
+
+                // Compare the discrete NitzData fields associated with local time offset. Any
+                // difference and we should process the signal regardless of how recent the last one
+                // was.
+                if (!offsetInfoIsTheSame(previousNitzData, newNitzData)) {
+                    return true;
+                }
+
+                // Now check the continuous NitzData field (time) to see if it is sufficiently
+                // different.
+                int nitzUpdateSpacing = deviceState.getNitzUpdateSpacingMillis();
+                int nitzUpdateDiff = deviceState.getNitzUpdateDiffMillis();
+
+                // Calculate the elapsed time between the new signal and the last signal.
+                long elapsedRealtimeSinceLastSaved = newSignal.getReferenceTimeMillis()
+                        - previousSignal.getReferenceTimeMillis();
+
+                // Calculate the UTC difference between the time the two signals hold.
+                long utcTimeDifferenceMillis = newNitzData.getCurrentTimeInMillis()
+                        - previousNitzData.getCurrentTimeInMillis();
+
+                // Ideally the difference between elapsedRealtimeSinceLastSaved and
+                // utcTimeDifferenceMillis would be zero.
+                long millisGainedOrLost = Math
+                        .abs(utcTimeDifferenceMillis - elapsedRealtimeSinceLastSaved);
+
+                if (elapsedRealtimeSinceLastSaved > nitzUpdateSpacing
+                        || millisGainedOrLost > nitzUpdateDiff) {
+                    return true;
+                }
+
+                if (DBG) {
+                    Rlog.d(LOG_TAG, "mustProcessNitzSignal: NITZ signal filtered"
+                            + " previousSignal=" + previousSignal
+                            + ", newSignal=" + newSignal
+                            + ", nitzUpdateSpacing=" + nitzUpdateSpacing
+                            + ", nitzUpdateDiff=" + nitzUpdateDiff);
+                }
+                return false;
+            }
+
+            private boolean offsetInfoIsTheSame(NitzData one, NitzData two) {
+                return Objects.equals(two.getDstAdjustmentMillis(), one.getDstAdjustmentMillis())
+                        && Objects.equals(
+                                two.getEmulatorHostTimeZone(), one.getEmulatorHostTimeZone())
+                        && two.getLocalOffsetMillis() == one.getLocalOffsetMillis();
+            }
+        };
+    }
+
+    /**
+     * An implementation of {@link NitzSignalInputFilterPredicate} that tries a series of
+     * {@link TrivalentPredicate} instances until one provides a {@code true} or {@code false}
+     * response indicating that the {@code newSignal} should be processed or not. If all return
+     * {@code null} then a default of {@code true} is returned.
+     */
+    @VisibleForTesting
+    public static class NitzSignalInputFilterPredicateImpl
+            implements NitzSignalInputFilterPredicate {
+
+        @NonNull
+        private final TrivalentPredicate[] mComponents;
+
+        @VisibleForTesting
+        public NitzSignalInputFilterPredicateImpl(@NonNull TrivalentPredicate[] components) {
+            this.mComponents = Arrays.copyOf(components, components.length);
+        }
+
+        @Override
+        public boolean mustProcessNitzSignal(@Nullable TimestampedValue<NitzData> oldSignal,
+                @NonNull TimestampedValue<NitzData> newSignal) {
+            Objects.requireNonNull(newSignal);
+
+            for (TrivalentPredicate component : mComponents) {
+                Boolean result = component.mustProcessNitzSignal(oldSignal, newSignal);
+                if (result != null) {
+                    return result;
+                }
+            }
+            // The default is to process.
+            return true;
+        }
+    }
+}
diff --git a/src/java/com/android/internal/telephony/nitz/NitzStateMachineImpl.java b/src/java/com/android/internal/telephony/nitz/NitzStateMachineImpl.java
new file mode 100644
index 0000000..a36eb4f
--- /dev/null
+++ b/src/java/com/android/internal/telephony/nitz/NitzStateMachineImpl.java
@@ -0,0 +1,355 @@
+/*
+ * Copyright 2019 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.nitz;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.timedetector.TelephonyTimeSuggestion;
+import android.app.timezonedetector.TelephonyTimeZoneSuggestion;
+import android.content.Context;
+import android.os.TimestampedValue;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.telephony.NitzData;
+import com.android.internal.telephony.NitzStateMachine;
+import com.android.internal.telephony.Phone;
+import com.android.internal.util.IndentingPrintWriter;
+import com.android.telephony.Rlog;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.Objects;
+
+/**
+ * An implementation of {@link NitzStateMachine} responsible for telephony time and time zone
+ * detection.
+ *
+ * <p>This implementation has a number of notable characteristics:
+ * <ul>
+ *     <li>It is decomposed into multiple classes that perform specific, well-defined, usually
+ *     stateless, testable behaviors.
+ *     </li>
+ *     <li>It splits responsibility for setting the device time zone with a "time zone detection
+ *     service". The time zone detection service is stateful, recording the latest suggestion from
+ *     several sources. The {@link NitzStateMachineImpl} actively signals when it has no answer
+ *     for the current time zone, allowing the service to arbitrate between the multiple sources
+ *     without polling each of them.
+ *     </li>
+ *     <li>Rate limiting of NITZ signals is performed for time zone as well as time detection.</li>
+ * </ul>
+ */
+public final class NitzStateMachineImpl implements NitzStateMachine {
+
+    /**
+     * An interface for predicates applied to incoming NITZ signals to determine whether they must
+     * be processed. See {@link NitzSignalInputFilterPredicateFactory#create(Context, DeviceState)}
+     * for the real implementation. The use of an interface means the behavior can be tested
+     * independently and easily replaced for tests.
+     */
+    @VisibleForTesting
+    @FunctionalInterface
+    public interface NitzSignalInputFilterPredicate {
+
+        /**
+         * See {@link NitzSignalInputFilterPredicate}.
+         */
+        boolean mustProcessNitzSignal(
+                @Nullable TimestampedValue<NitzData> oldSignal,
+                @NonNull TimestampedValue<NitzData> newSignal);
+    }
+
+    /**
+     * An interface for the stateless component that generates suggestions using country and/or NITZ
+     * information. The use of an interface means the behavior can be tested independently.
+     */
+    @VisibleForTesting
+    public interface TimeZoneSuggester {
+
+        /**
+         * Generates a {@link TelephonyTimeZoneSuggestion} given the information available. This
+         * method must always return a non-null {@link TelephonyTimeZoneSuggestion} but that object
+         * does not have to contain a time zone if the available information is not sufficient to
+         * determine one. {@link TelephonyTimeZoneSuggestion#getDebugInfo()} provides debugging /
+         * logging information explaining the choice.
+         */
+        @NonNull
+        TelephonyTimeZoneSuggestion getTimeZoneSuggestion(
+                int slotIndex, @Nullable String countryIsoCode,
+                @Nullable TimestampedValue<NitzData> nitzSignal);
+    }
+
+    static final String LOG_TAG = "NewNitzStateMachineImpl";
+    static final boolean DBG = true;
+
+    // Miscellaneous dependencies and helpers not related to detection state.
+    private final int mSlotIndex;
+    /** Applied to NITZ signals during input filtering. */
+    private final NitzSignalInputFilterPredicate mNitzSignalInputFilter;
+    /**
+     * Creates a {@link TelephonyTimeZoneSuggestion} for passing to the time zone detection service.
+     */
+    private final TimeZoneSuggester mTimeZoneSuggester;
+    /** A facade to the time / time zone detection services. */
+    private final TimeServiceHelper mTimeServiceHelper;
+
+    // Shared detection state.
+
+    /**
+     * The last / latest NITZ signal <em>processed</em> (i.e. after input filtering). It is used for
+     * input filtering (e.g. rate limiting) and provides the NITZ information when time / time zone
+     * needs to be recalculated when something else has changed.
+     */
+    @Nullable
+    private TimestampedValue<NitzData> mLatestNitzSignal;
+
+    // Time Zone detection state.
+
+    /**
+     * Records the country to use for time zone detection. It can be a valid ISO 3166 alpha-2 code
+     * (lower case), empty (test network) or null (no country detected). A country code is required
+     * to determine time zone except when on a test network.
+     */
+    private String mCountryIsoCode;
+
+    /**
+     * Creates an instance for the supplied {@link Phone}.
+     */
+    public static NitzStateMachineImpl createInstance(@NonNull Phone phone) {
+        Objects.requireNonNull(phone);
+
+        int slotIndex = phone.getPhoneId();
+        DeviceState deviceState = new DeviceStateImpl(phone);
+        TimeZoneLookupHelper timeZoneLookupHelper = new TimeZoneLookupHelper();
+        TimeZoneSuggester timeZoneSuggester =
+                new TimeZoneSuggesterImpl(deviceState, timeZoneLookupHelper);
+        TimeServiceHelper newTimeServiceHelper = new TimeServiceHelperImpl(phone);
+        NitzSignalInputFilterPredicate nitzSignalFilter =
+                NitzSignalInputFilterPredicateFactory.create(phone.getContext(), deviceState);
+        return new NitzStateMachineImpl(
+                slotIndex, nitzSignalFilter, timeZoneSuggester, newTimeServiceHelper);
+    }
+
+    /**
+     * Creates an instance using the supplied components. Used during tests to supply fakes.
+     * See {@link #createInstance(Phone)}
+     */
+    @VisibleForTesting
+    public NitzStateMachineImpl(int slotIndex,
+            @NonNull NitzSignalInputFilterPredicate nitzSignalInputFilter,
+            @NonNull TimeZoneSuggester timeZoneSuggester,
+            @NonNull TimeServiceHelper newTimeServiceHelper) {
+        mSlotIndex = slotIndex;
+        mTimeZoneSuggester = Objects.requireNonNull(timeZoneSuggester);
+        mTimeServiceHelper = Objects.requireNonNull(newTimeServiceHelper);
+        mNitzSignalInputFilter = Objects.requireNonNull(nitzSignalInputFilter);
+    }
+
+    @Override
+    public void handleNetworkAvailable() {
+        // We no longer do any useful work here: we assume handleNetworkUnavailable() is reliable.
+        // TODO: Remove this method when all implementations do nothing.
+    }
+
+    @Override
+    public void handleNetworkUnavailable() {
+        String reason = "handleNetworkUnavailable()";
+        clearNetworkStateAndRerunDetection(reason);
+    }
+
+    private void clearNetworkStateAndRerunDetection(String reason) {
+        if (mLatestNitzSignal == null) {
+            // The network state is already empty so there's no need to do anything.
+            if (DBG) {
+                Rlog.d(LOG_TAG, reason + ": mLatestNitzSignal was already null. Nothing to do.");
+            }
+            return;
+        }
+
+        // The previous NITZ signal received is now invalid so clear it.
+        mLatestNitzSignal = null;
+
+        // countryIsoCode can be assigned null here, in which case the doTimeZoneDetection() call
+        // below will do nothing, which is ok as nothing will have changed.
+        String countryIsoCode = mCountryIsoCode;
+        if (DBG) {
+            Rlog.d(LOG_TAG, reason + ": countryIsoCode=" + countryIsoCode);
+        }
+
+        // Generate a new time zone suggestion (which could be an empty suggestion) and update the
+        // service as needed.
+        doTimeZoneDetection(countryIsoCode, null /* nitzSignal */, reason);
+
+        // Generate a new time suggestion and update the service as needed.
+        doTimeDetection(null /* nitzSignal */, reason);
+    }
+
+    @Override
+    public void handleCountryDetected(@NonNull String countryIsoCode) {
+        if (DBG) {
+            Rlog.d(LOG_TAG, "handleCountryDetected: countryIsoCode=" + countryIsoCode
+                    + ", mLatestNitzSignal=" + mLatestNitzSignal);
+        }
+
+        String oldCountryIsoCode = mCountryIsoCode;
+        mCountryIsoCode = Objects.requireNonNull(countryIsoCode);
+        if (!Objects.equals(oldCountryIsoCode, mCountryIsoCode)) {
+            // Generate a new time zone suggestion and update the service as needed.
+            doTimeZoneDetection(countryIsoCode, mLatestNitzSignal,
+                    "handleCountryDetected(\"" + countryIsoCode + "\")");
+        }
+    }
+
+    @Override
+    public void handleCountryUnavailable() {
+        if (DBG) {
+            Rlog.d(LOG_TAG, "handleCountryUnavailable:"
+                    + " mLatestNitzSignal=" + mLatestNitzSignal);
+        }
+        mCountryIsoCode = null;
+
+        // Generate a new time zone suggestion and update the service as needed.
+        doTimeZoneDetection(null /* countryIsoCode */, mLatestNitzSignal,
+                "handleCountryUnavailable()");
+    }
+
+    @Override
+    public void handleNitzReceived(@NonNull TimestampedValue<NitzData> nitzSignal) {
+        if (DBG) {
+            Rlog.d(LOG_TAG, "handleNitzReceived: nitzSignal=" + nitzSignal);
+        }
+        Objects.requireNonNull(nitzSignal);
+
+        // Perform input filtering to filter bad data and avoid processing signals too often.
+        TimestampedValue<NitzData> previousNitzSignal = mLatestNitzSignal;
+        if (!mNitzSignalInputFilter.mustProcessNitzSignal(previousNitzSignal, nitzSignal)) {
+            return;
+        }
+
+        // Always store the latest valid NITZ signal to be processed.
+        mLatestNitzSignal = nitzSignal;
+
+        String reason = "handleNitzReceived(" + nitzSignal + ")";
+
+        // Generate a new time zone suggestion and update the service as needed.
+        String countryIsoCode = mCountryIsoCode;
+        doTimeZoneDetection(countryIsoCode, nitzSignal, reason);
+
+        // Generate a new time suggestion and update the service as needed.
+        doTimeDetection(nitzSignal, reason);
+    }
+
+    @Override
+    public void handleAirplaneModeChanged(boolean on) {
+        // Treat entry / exit from airplane mode as a strong signal that the user wants to clear
+        // cached state. If the user really is boarding a plane they won't want cached state from
+        // before their flight influencing behavior.
+        //
+        // State is cleared on entry AND exit: on entry because the detection code shouldn't be
+        // opinionated while in airplane mode, and on exit to avoid any unexpected signals received
+        // while in airplane mode from influencing behavior afterwards.
+        //
+        // After clearing detection state, the time zone detection should work out from first
+        // principles what the time / time zone is. This assumes calls like handleNetworkAvailable()
+        // will be made after airplane mode is re-enabled as the device re-establishes network
+        // connectivity.
+
+        // Clear country detection state.
+        mCountryIsoCode = null;
+
+        String reason = "handleAirplaneModeChanged(" + on + ")";
+        clearNetworkStateAndRerunDetection(reason);
+    }
+
+    /**
+     * Perform a round of time zone detection and notify the time zone detection service as needed.
+     */
+    private void doTimeZoneDetection(
+            @Nullable String countryIsoCode, @Nullable TimestampedValue<NitzData> nitzSignal,
+            @NonNull String reason) {
+        try {
+            Objects.requireNonNull(reason);
+
+            TelephonyTimeZoneSuggestion suggestion = mTimeZoneSuggester.getTimeZoneSuggestion(
+                    mSlotIndex, countryIsoCode, nitzSignal);
+            suggestion.addDebugInfo("Detection reason=" + reason);
+
+            if (DBG) {
+                Rlog.d(LOG_TAG, "doTimeZoneDetection: countryIsoCode=" + countryIsoCode
+                        + ", nitzSignal=" + nitzSignal + ", suggestion=" + suggestion
+                        + ", reason=" + reason);
+            }
+            mTimeServiceHelper.maybeSuggestDeviceTimeZone(suggestion);
+        } catch (RuntimeException ex) {
+            Rlog.e(LOG_TAG, "doTimeZoneDetection: Exception thrown"
+                    + " mSlotIndex=" + mSlotIndex
+                    + ", countryIsoCode=" + countryIsoCode
+                    + ", nitzSignal=" + nitzSignal
+                    + ", reason=" + reason
+                    + ", ex=" + ex, ex);
+        }
+    }
+
+    /**
+     * Perform a round of time detection and notify the time detection service as needed.
+     */
+    private void doTimeDetection(@Nullable TimestampedValue<NitzData> nitzSignal,
+            @NonNull String reason) {
+        try {
+            Objects.requireNonNull(reason);
+
+            TelephonyTimeSuggestion.Builder builder =
+                    new TelephonyTimeSuggestion.Builder(mSlotIndex);
+            if (nitzSignal == null) {
+                builder.addDebugInfo("Clearing time suggestion"
+                        + " reason=" + reason);
+            } else {
+                TimestampedValue<Long> newNitzTime = new TimestampedValue<>(
+                        nitzSignal.getReferenceTimeMillis(),
+                        nitzSignal.getValue().getCurrentTimeInMillis());
+                builder.setUtcTime(newNitzTime);
+                builder.addDebugInfo("Sending new time suggestion"
+                        + " nitzSignal=" + nitzSignal
+                        + ", reason=" + reason);
+            }
+            mTimeServiceHelper.suggestDeviceTime(builder.build());
+        } catch (RuntimeException ex) {
+            Rlog.e(LOG_TAG, "doTimeDetection: Exception thrown"
+                    + " mSlotIndex=" + mSlotIndex
+                    + ", nitzSignal=" + nitzSignal
+                    + ", reason=" + reason
+                    + ", ex=" + ex, ex);
+        }
+    }
+
+    @Override
+    public void dumpState(PrintWriter pw) {
+        pw.println(" NitzStateMachineImpl.mLatestNitzSignal=" + mLatestNitzSignal);
+        pw.println(" NitzStateMachineImpl.mCountryIsoCode=" + mCountryIsoCode);
+        mTimeServiceHelper.dumpState(pw);
+        pw.flush();
+    }
+
+    @Override
+    public void dumpLogs(FileDescriptor fd, IndentingPrintWriter ipw, String[] args) {
+        mTimeServiceHelper.dumpLogs(ipw);
+    }
+
+    @Nullable
+    public NitzData getCachedNitzData() {
+        return mLatestNitzSignal != null ? mLatestNitzSignal.getValue() : null;
+    }
+}
diff --git a/src/java/com/android/internal/telephony/nitz/TimeServiceHelper.java b/src/java/com/android/internal/telephony/nitz/TimeServiceHelper.java
new file mode 100644
index 0000000..60f602c
--- /dev/null
+++ b/src/java/com/android/internal/telephony/nitz/TimeServiceHelper.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright 2019 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.nitz;
+
+import android.annotation.NonNull;
+import android.app.timedetector.TelephonyTimeSuggestion;
+import android.app.timezonedetector.TelephonyTimeZoneSuggestion;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.IndentingPrintWriter;
+
+import java.io.PrintWriter;
+
+/**
+ * An interface to various time / time zone detection behaviors that are centralized into services.
+ * This interface exists to separate out behavior to enable easier testing of the
+ * {@link NitzStateMachineImpl}.
+ */
+@VisibleForTesting
+public interface TimeServiceHelper {
+
+    /**
+     * Suggests the time to the time detector service.
+     *
+     * @param suggestion the time suggestion
+     */
+    void suggestDeviceTime(@NonNull TelephonyTimeSuggestion suggestion);
+
+    /**
+     * Suggests the time zone to the time zone detector service.
+     *
+     * <p>NOTE: The {@link TelephonyTimeZoneSuggestion} cannot be null. The zoneId it contains can
+     * be null to indicate there is no active suggestion; this can be used to clear a previous
+     * suggestion.
+     *
+     * @param suggestion the time zone suggestion
+     */
+    void maybeSuggestDeviceTimeZone(@NonNull TelephonyTimeZoneSuggestion suggestion);
+
+    /**
+     * Dumps any logs held to the supplied writer.
+     */
+    void dumpLogs(IndentingPrintWriter ipw);
+
+    /**
+     * Dumps internal state such as field values.
+     */
+    void dumpState(PrintWriter pw);
+}
diff --git a/src/java/com/android/internal/telephony/nitz/TimeServiceHelperImpl.java b/src/java/com/android/internal/telephony/nitz/TimeServiceHelperImpl.java
new file mode 100644
index 0000000..7f85786
--- /dev/null
+++ b/src/java/com/android/internal/telephony/nitz/TimeServiceHelperImpl.java
@@ -0,0 +1,124 @@
+/*
+ * Copyright 2019 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.nitz;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.timedetector.TelephonyTimeSuggestion;
+import android.app.timedetector.TimeDetector;
+import android.app.timezonedetector.TelephonyTimeZoneSuggestion;
+import android.app.timezonedetector.TimeZoneDetector;
+import android.content.Context;
+import android.os.SystemClock;
+import android.os.TimestampedValue;
+import android.util.LocalLog;
+
+import com.android.internal.telephony.Phone;
+import com.android.internal.telephony.metrics.TelephonyMetrics;
+import com.android.internal.util.IndentingPrintWriter;
+
+import java.io.PrintWriter;
+import java.util.Objects;
+
+/**
+ * The real implementation of {@link TimeServiceHelper}.
+ */
+public final class TimeServiceHelperImpl implements TimeServiceHelper {
+
+    private final int mSlotIndex;
+    private final TimeDetector mTimeDetector;
+    private final TimeZoneDetector mTimeZoneDetector;
+
+    private final LocalLog mTimeZoneLog = new LocalLog(30, false /* mUseLocalTimestamps */);
+    private final LocalLog mTimeLog = new LocalLog(30, false /* mUseLocalTimestamps */);
+
+    /**
+     * Records the last time zone suggestion made. Used to avoid sending duplicate suggestions to
+     * the time zone service. The value can be {@code null} to indicate no previous suggestion has
+     * been made.
+     */
+    @NonNull
+    private TelephonyTimeZoneSuggestion mLastSuggestedTimeZone;
+
+    public TimeServiceHelperImpl(@NonNull Phone phone) {
+        mSlotIndex = phone.getPhoneId();
+        Context context = Objects.requireNonNull(phone.getContext());
+        mTimeDetector = Objects.requireNonNull(context.getSystemService(TimeDetector.class));
+        mTimeZoneDetector =
+                Objects.requireNonNull(context.getSystemService(TimeZoneDetector.class));
+    }
+
+    @Override
+    public void suggestDeviceTime(@NonNull TelephonyTimeSuggestion timeSuggestion) {
+        mTimeLog.log("Sending time suggestion: " + timeSuggestion);
+
+        Objects.requireNonNull(timeSuggestion);
+
+        if (timeSuggestion.getUtcTime() != null) {
+            TimestampedValue<Long> utcTime = timeSuggestion.getUtcTime();
+            TelephonyMetrics.getInstance().writeNITZEvent(mSlotIndex, utcTime.getValue());
+        }
+        mTimeDetector.suggestTelephonyTime(timeSuggestion);
+    }
+
+    @Override
+    public void maybeSuggestDeviceTimeZone(@NonNull TelephonyTimeZoneSuggestion newSuggestion) {
+        Objects.requireNonNull(newSuggestion);
+
+        TelephonyTimeZoneSuggestion oldSuggestion = mLastSuggestedTimeZone;
+        if (shouldSendNewTimeZoneSuggestion(oldSuggestion, newSuggestion)) {
+            mTimeZoneLog.log("Suggesting time zone update: " + newSuggestion);
+            mTimeZoneDetector.suggestTelephonyTimeZone(newSuggestion);
+            mLastSuggestedTimeZone = newSuggestion;
+        }
+    }
+
+    private static boolean shouldSendNewTimeZoneSuggestion(
+            @Nullable TelephonyTimeZoneSuggestion oldSuggestion,
+            @NonNull TelephonyTimeZoneSuggestion newSuggestion) {
+        if (oldSuggestion == null) {
+            // No previous suggestion.
+            return true;
+        }
+        // This code relies on PhoneTimeZoneSuggestion.equals() to only check meaningful fields.
+        return !Objects.equals(newSuggestion, oldSuggestion);
+    }
+
+    @Override
+    public void dumpLogs(IndentingPrintWriter ipw) {
+        ipw.println("TimeServiceHelperImpl:");
+        ipw.increaseIndent();
+        ipw.println("SystemClock.elapsedRealtime()=" + SystemClock.elapsedRealtime());
+        ipw.println("System.currentTimeMillis()=" + System.currentTimeMillis());
+
+        ipw.println("Time Logs:");
+        ipw.increaseIndent();
+        mTimeLog.dump(ipw);
+        ipw.decreaseIndent();
+
+        ipw.println("Time zone Logs:");
+        ipw.increaseIndent();
+        mTimeZoneLog.dump(ipw);
+        ipw.decreaseIndent();
+        ipw.decreaseIndent();
+    }
+
+    @Override
+    public void dumpState(PrintWriter pw) {
+        pw.println(" TimeServiceHelperImpl.mLastSuggestedTimeZone=" + mLastSuggestedTimeZone);
+    }
+}
diff --git a/src/java/com/android/internal/telephony/nitz/TimeZoneLookupHelper.java b/src/java/com/android/internal/telephony/nitz/TimeZoneLookupHelper.java
new file mode 100644
index 0000000..fab45f7
--- /dev/null
+++ b/src/java/com/android/internal/telephony/nitz/TimeZoneLookupHelper.java
@@ -0,0 +1,337 @@
+/*
+ * Copyright 2017 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.nitz;
+
+import static com.android.internal.telephony.nitz.TimeZoneLookupHelper.CountryResult.QUALITY_MULTIPLE_ZONES_DIFFERENT_OFFSETS;
+import static com.android.internal.telephony.nitz.TimeZoneLookupHelper.CountryResult.QUALITY_MULTIPLE_ZONES_SAME_OFFSET;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.icu.util.TimeZone;
+import android.text.TextUtils;
+import android.timezone.CountryTimeZones;
+import android.timezone.CountryTimeZones.OffsetResult;
+import android.timezone.CountryTimeZones.TimeZoneMapping;
+import android.timezone.TimeZoneFinder;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.telephony.NitzData;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * An interface to various time zone lookup behaviors.
+ */
+@VisibleForTesting
+public final class TimeZoneLookupHelper {
+
+    /**
+     * The result of looking up a time zone using country information.
+     */
+    @VisibleForTesting
+    public static final class CountryResult {
+
+        @IntDef({ QUALITY_SINGLE_ZONE, QUALITY_DEFAULT_BOOSTED, QUALITY_MULTIPLE_ZONES_SAME_OFFSET,
+                QUALITY_MULTIPLE_ZONES_DIFFERENT_OFFSETS })
+        @Retention(RetentionPolicy.SOURCE)
+        public @interface Quality {}
+
+        public static final int QUALITY_SINGLE_ZONE = 1;
+        public static final int QUALITY_DEFAULT_BOOSTED = 2;
+        public static final int QUALITY_MULTIPLE_ZONES_SAME_OFFSET = 3;
+        public static final int QUALITY_MULTIPLE_ZONES_DIFFERENT_OFFSETS = 4;
+
+        /** A time zone to use for the country. */
+        @NonNull
+        public final String zoneId;
+
+        /**
+         * The quality of the match.
+         */
+        @Quality
+        public final int quality;
+
+        /**
+         * Freeform information about why the value of {@link #quality} was chosen. Not used for
+         * {@link #equals(Object)}.
+         */
+        private final String mDebugInfo;
+
+        public CountryResult(@NonNull String zoneId, @Quality int quality, String debugInfo) {
+            this.zoneId = Objects.requireNonNull(zoneId);
+            this.quality = quality;
+            mDebugInfo = debugInfo;
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            if (this == o) {
+                return true;
+            }
+            if (o == null || getClass() != o.getClass()) {
+                return false;
+            }
+            CountryResult that = (CountryResult) o;
+            return quality == that.quality
+                    && zoneId.equals(that.zoneId);
+        }
+
+        @Override
+        public int hashCode() {
+            return Objects.hash(zoneId, quality);
+        }
+
+        @Override
+        public String toString() {
+            return "CountryResult{"
+                    + "zoneId='" + zoneId + '\''
+                    + ", quality=" + quality
+                    + ", mDebugInfo=" + mDebugInfo
+                    + '}';
+        }
+    }
+
+    /** The last CountryTimeZones object retrieved. */
+    @Nullable
+    private CountryTimeZones mLastCountryTimeZones;
+
+    @VisibleForTesting
+    public TimeZoneLookupHelper() {}
+
+    /**
+     * Looks for a time zone for the supplied NITZ and country information.
+     *
+     * <p><em>Note:</em> When there are multiple matching zones then one of the matching candidates
+     * will be returned in the result. If the current device default zone matches it will be
+     * returned in preference to other candidates. This method can return {@code null} if no
+     * matching time zones are found.
+     */
+    @VisibleForTesting
+    @Nullable
+    public OffsetResult lookupByNitzCountry(
+            @NonNull NitzData nitzData, @NonNull String isoCountryCode) {
+        CountryTimeZones countryTimeZones = getCountryTimeZones(isoCountryCode);
+        if (countryTimeZones == null) {
+            return null;
+        }
+        TimeZone bias = TimeZone.getDefault();
+
+        // Android NITZ time zone matching doesn't try to do a precise match using the DST offset
+        // supplied by the carrier. It only considers whether or not the carrier suggests local time
+        // is DST (if known). NITZ is limited in only being able to express DST offsets in whole
+        // hours and the DST info is optional.
+        Integer dstAdjustmentMillis = nitzData.getDstAdjustmentMillis();
+        if (dstAdjustmentMillis == null) {
+            return countryTimeZones.lookupByOffsetWithBias(
+                    nitzData.getCurrentTimeInMillis(), bias, nitzData.getLocalOffsetMillis());
+
+        } else {
+            // We don't try to match the exact DST offset given, we just use it to work out if
+            // the country is in DST.
+            boolean isDst = dstAdjustmentMillis != 0;
+            return countryTimeZones.lookupByOffsetWithBias(
+                    nitzData.getCurrentTimeInMillis(), bias,
+                    nitzData.getLocalOffsetMillis(), isDst);
+        }
+    }
+
+    /**
+     * Looks for a time zone using only information present in the supplied {@link NitzData} object.
+     *
+     * <p><em>Note:</em> Because multiple time zones can have the same offset / DST state at a given
+     * time this process is error prone; an arbitrary match is returned when there are multiple
+     * candidates. The algorithm can also return a non-exact match by assuming that the DST
+     * information provided by NITZ is incorrect. This method can return {@code null} if no matching
+     * time zones are found.
+     */
+    @VisibleForTesting
+    @Nullable
+    public OffsetResult lookupByNitz(@NonNull NitzData nitzData) {
+        int utcOffsetMillis = nitzData.getLocalOffsetMillis();
+        long timeMillis = nitzData.getCurrentTimeInMillis();
+
+        // Android NITZ time zone matching doesn't try to do a precise match using the DST offset
+        // supplied by the carrier. It only considers whether or not the carrier suggests local time
+        // is DST (if known). NITZ is limited in only being able to express DST offsets in whole
+        // hours and the DST info is optional.
+        Integer dstAdjustmentMillis = nitzData.getDstAdjustmentMillis();
+        Boolean isDst = dstAdjustmentMillis == null ? null : dstAdjustmentMillis != 0;
+
+        OffsetResult match = lookupByInstantOffsetDst(timeMillis, utcOffsetMillis, isDst);
+        if (match == null && isDst != null) {
+            // This branch is extremely unlikely and could probably be removed. The match above will
+            // have searched the entire tzdb for a zone with the same total offset and isDst state.
+            // Here we try another match but use "null" for isDst to indicate that only the total
+            // offset should be considered. If, by the end of this, there isn't a match then the
+            // current offset suggested by the carrier must be highly unusual.
+            match = lookupByInstantOffsetDst(timeMillis, utcOffsetMillis, null /* isDst */);
+        }
+        return match;
+    }
+
+    /**
+     * Returns information about the time zones used in a country at a given time.
+     *
+     * {@code null} can be returned if a problem occurs during lookup, e.g. if the country code is
+     * unrecognized, if the country is uninhabited, or if there is a problem with the data.
+     */
+    @VisibleForTesting
+    @Nullable
+    public CountryResult lookupByCountry(@NonNull String isoCountryCode, long whenMillis) {
+        CountryTimeZones countryTimeZones = getCountryTimeZones(isoCountryCode);
+        if (countryTimeZones == null) {
+            // Unknown country code.
+            return null;
+        }
+        TimeZone countryDefaultZone = countryTimeZones.getDefaultTimeZone();
+        if (countryDefaultZone == null) {
+            // This is not expected: the country default should have been validated before.
+            return null;
+        }
+
+        String debugInfo;
+        int matchQuality;
+        if (countryTimeZones.isDefaultTimeZoneBoosted()) {
+            matchQuality = CountryResult.QUALITY_DEFAULT_BOOSTED;
+            debugInfo = "Country default is boosted";
+        } else {
+            List<TimeZoneMapping> effectiveTimeZoneMappings =
+                    countryTimeZones.getEffectiveTimeZoneMappingsAt(whenMillis);
+            if (effectiveTimeZoneMappings.isEmpty()) {
+                // This should never happen unless there's been an error loading the data.
+                // Treat it the same as a low quality answer.
+                matchQuality = QUALITY_MULTIPLE_ZONES_DIFFERENT_OFFSETS;
+                debugInfo = "No effective time zones found at whenMillis=" + whenMillis;
+            } else if (effectiveTimeZoneMappings.size() == 1) {
+                // The default is the only zone so it's a good candidate.
+                matchQuality = CountryResult.QUALITY_SINGLE_ZONE;
+                debugInfo = "One effective time zone found at whenMillis=" + whenMillis;
+            } else {
+                boolean countryUsesDifferentOffsets = countryUsesDifferentOffsets(
+                        whenMillis, effectiveTimeZoneMappings, countryDefaultZone);
+                matchQuality = countryUsesDifferentOffsets
+                        ? QUALITY_MULTIPLE_ZONES_DIFFERENT_OFFSETS
+                        : QUALITY_MULTIPLE_ZONES_SAME_OFFSET;
+                debugInfo = "countryUsesDifferentOffsets=" + countryUsesDifferentOffsets + " at"
+                        + " whenMillis=" + whenMillis;
+            }
+        }
+        return new CountryResult(countryDefaultZone.getID(), matchQuality, debugInfo);
+    }
+
+    private static boolean countryUsesDifferentOffsets(
+            long whenMillis, @NonNull List<TimeZoneMapping> effectiveTimeZoneMappings,
+            @NonNull TimeZone countryDefaultZone) {
+        String countryDefaultId = countryDefaultZone.getID();
+        int countryDefaultOffset = countryDefaultZone.getOffset(whenMillis);
+        for (TimeZoneMapping timeZoneMapping : effectiveTimeZoneMappings) {
+            if (timeZoneMapping.getTimeZoneId().equals(countryDefaultId)) {
+                continue;
+            }
+
+            TimeZone timeZone = timeZoneMapping.getTimeZone();
+            int candidateOffset = timeZone.getOffset(whenMillis);
+            if (countryDefaultOffset != candidateOffset) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    private static OffsetResult lookupByInstantOffsetDst(long timeMillis, int utcOffsetMillis,
+            @Nullable Boolean isDst) {
+
+        String[] zones = TimeZone.getAvailableIDs();
+        TimeZone match = null;
+        boolean isOnlyMatch = true;
+        for (String zone : zones) {
+            TimeZone tz = TimeZone.getFrozenTimeZone(zone);
+            if (offsetMatchesAtTime(tz, utcOffsetMillis, isDst, timeMillis)) {
+                if (match == null) {
+                    match = tz;
+                } else {
+                    isOnlyMatch = false;
+                    break;
+                }
+            }
+        }
+
+        if (match == null) {
+            return null;
+        }
+        return new OffsetResult(match, isOnlyMatch);
+    }
+
+    /**
+     * Returns {@code true} if the specified {@code totalOffset} and {@code isDst} would be valid in
+     * the {@code timeZone} at time {@code whenMillis}. {@code totalOffetMillis} is always matched.
+     * If {@code isDst} is {@code null} this means the DST state is unknown so DST state is ignored.
+     * If {@code isDst} is not {@code null} then it is also matched.
+     */
+    private static boolean offsetMatchesAtTime(@NonNull TimeZone timeZone, int totalOffsetMillis,
+            @Nullable Boolean isDst, long whenMillis) {
+        int[] offsets = new int[2];
+        timeZone.getOffset(whenMillis, false /* local */, offsets);
+
+        if (totalOffsetMillis != (offsets[0] + offsets[1])) {
+            return false;
+        }
+
+        return isDst == null || isDst == (offsets[1] != 0);
+    }
+
+    /**
+     * Returns {@code true} if the supplied (lower-case) ISO country code is for a country known to
+     * use a raw offset of zero from UTC at the time specified.
+     */
+    @VisibleForTesting
+    public boolean countryUsesUtc(@NonNull String isoCountryCode, long whenMillis) {
+        if (TextUtils.isEmpty(isoCountryCode)) {
+            return false;
+        }
+
+        CountryTimeZones countryTimeZones = getCountryTimeZones(isoCountryCode);
+        return countryTimeZones != null && countryTimeZones.hasUtcZone(whenMillis);
+    }
+
+    @Nullable
+    private CountryTimeZones getCountryTimeZones(@NonNull String isoCountryCode) {
+        Objects.requireNonNull(isoCountryCode);
+
+        // A single entry cache of the last CountryTimeZones object retrieved since there should
+        // be strong consistency across calls.
+        synchronized (this) {
+            if (mLastCountryTimeZones != null) {
+                if (mLastCountryTimeZones.matchesCountryCode(isoCountryCode)) {
+                    return mLastCountryTimeZones;
+                }
+            }
+
+            // Perform the lookup. It's very unlikely to return null, but we won't cache null.
+            CountryTimeZones countryTimeZones =
+                    TimeZoneFinder.getInstance().lookupCountryTimeZones(isoCountryCode);
+            if (countryTimeZones != null) {
+                mLastCountryTimeZones = countryTimeZones;
+            }
+            return countryTimeZones;
+        }
+    }
+}
diff --git a/src/java/com/android/internal/telephony/nitz/TimeZoneSuggesterImpl.java b/src/java/com/android/internal/telephony/nitz/TimeZoneSuggesterImpl.java
new file mode 100644
index 0000000..48491df
--- /dev/null
+++ b/src/java/com/android/internal/telephony/nitz/TimeZoneSuggesterImpl.java
@@ -0,0 +1,300 @@
+/*
+ * Copyright 2019 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.nitz;
+
+import static android.app.timezonedetector.TelephonyTimeZoneSuggestion.createEmptySuggestion;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.timezonedetector.TelephonyTimeZoneSuggestion;
+import android.os.TimestampedValue;
+import android.text.TextUtils;
+import android.timezone.CountryTimeZones.OffsetResult;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.telephony.NitzData;
+import com.android.internal.telephony.NitzStateMachine.DeviceState;
+import com.android.internal.telephony.nitz.NitzStateMachineImpl.TimeZoneSuggester;
+import com.android.internal.telephony.nitz.TimeZoneLookupHelper.CountryResult;
+import com.android.telephony.Rlog;
+
+import java.util.Objects;
+
+/**
+ * The real implementation of {@link TimeZoneSuggester}.
+ */
+@VisibleForTesting
+public class TimeZoneSuggesterImpl implements TimeZoneSuggester {
+
+    private static final String LOG_TAG = NitzStateMachineImpl.LOG_TAG;
+
+    private final DeviceState mDeviceState;
+    private final TimeZoneLookupHelper mTimeZoneLookupHelper;
+
+    @VisibleForTesting
+    public TimeZoneSuggesterImpl(
+            @NonNull DeviceState deviceState, @NonNull TimeZoneLookupHelper timeZoneLookupHelper) {
+        mDeviceState = Objects.requireNonNull(deviceState);
+        mTimeZoneLookupHelper = Objects.requireNonNull(timeZoneLookupHelper);
+    }
+
+    @Override
+    @NonNull
+    public TelephonyTimeZoneSuggestion getTimeZoneSuggestion(int slotIndex,
+            @Nullable String countryIsoCode, @Nullable TimestampedValue<NitzData> nitzSignal) {
+        try {
+            // Check for overriding NITZ-based signals from Android running in an emulator.
+            TelephonyTimeZoneSuggestion overridingSuggestion = null;
+            if (nitzSignal != null) {
+                NitzData nitzData = nitzSignal.getValue();
+                if (nitzData.getEmulatorHostTimeZone() != null) {
+                    TelephonyTimeZoneSuggestion.Builder builder =
+                            new TelephonyTimeZoneSuggestion.Builder(slotIndex)
+                            .setZoneId(nitzData.getEmulatorHostTimeZone().getID())
+                            .setMatchType(TelephonyTimeZoneSuggestion.MATCH_TYPE_EMULATOR_ZONE_ID)
+                            .setQuality(TelephonyTimeZoneSuggestion.QUALITY_SINGLE_ZONE)
+                            .addDebugInfo("Emulator time zone override: " + nitzData);
+                    overridingSuggestion = builder.build();
+                }
+            }
+
+            TelephonyTimeZoneSuggestion suggestion;
+            if (overridingSuggestion != null) {
+                suggestion = overridingSuggestion;
+            } else if (countryIsoCode == null) {
+                if (nitzSignal == null) {
+                    suggestion = createEmptySuggestion(slotIndex,
+                            "getTimeZoneSuggestion: nitzSignal=null, countryIsoCode=null");
+                } else {
+                    // NITZ only - wait until we have a country.
+                    suggestion = createEmptySuggestion(slotIndex, "getTimeZoneSuggestion:"
+                            + " nitzSignal=" + nitzSignal + ", countryIsoCode=null");
+                }
+            } else { // countryIsoCode != null
+                if (nitzSignal == null) {
+                    if (countryIsoCode.isEmpty()) {
+                        // This is assumed to be a test network with no NITZ data to go on.
+                        suggestion = createEmptySuggestion(slotIndex,
+                                "getTimeZoneSuggestion: nitzSignal=null, countryIsoCode=\"\"");
+                    } else {
+                        // Country only
+                        suggestion = findTimeZoneFromNetworkCountryCode(
+                                slotIndex, countryIsoCode, mDeviceState.currentTimeMillis());
+                    }
+                } else { // nitzSignal != null
+                    if (countryIsoCode.isEmpty()) {
+                        // We have been told we have a country code but it's empty. This is most
+                        // likely because we're on a test network that's using a bogus MCC
+                        // (eg, "001"). Obtain a TimeZone based only on the NITZ parameters: without
+                        // a country it will be arbitrary, but it should at least have the correct
+                        // offset.
+                        suggestion = findTimeZoneForTestNetwork(slotIndex, nitzSignal);
+                    } else {
+                        // We have both NITZ and Country code.
+                        suggestion = findTimeZoneFromCountryAndNitz(
+                                slotIndex, countryIsoCode, nitzSignal);
+                    }
+                }
+            }
+
+            // Ensure the return value is never null.
+            Objects.requireNonNull(suggestion);
+
+            return suggestion;
+        } catch (RuntimeException e) {
+            // This would suggest a coding error. Log at a high level and try to avoid leaving the
+            // device in a bad state by making an "empty" suggestion.
+            String message = "getTimeZoneSuggestion: Error during lookup: "
+                    + " countryIsoCode=" + countryIsoCode
+                    + ", nitzSignal=" + nitzSignal
+                    + ", e=" + e.getMessage();
+            TelephonyTimeZoneSuggestion errorSuggestion = createEmptySuggestion(slotIndex, message);
+            Rlog.w(LOG_TAG, message, e);
+            return errorSuggestion;
+        }
+    }
+
+    /**
+     * Creates a {@link TelephonyTimeZoneSuggestion} using only NITZ. This happens when the device
+     * is attached to a test cell with an unrecognized MCC. In these cases we try to return a
+     * suggestion for an arbitrary time zone that matches the NITZ offset information.
+     */
+    @NonNull
+    private TelephonyTimeZoneSuggestion findTimeZoneForTestNetwork(
+            int slotIndex, @NonNull TimestampedValue<NitzData> nitzSignal) {
+        Objects.requireNonNull(nitzSignal);
+        NitzData nitzData = Objects.requireNonNull(nitzSignal.getValue());
+
+        TelephonyTimeZoneSuggestion.Builder suggestionBuilder =
+                new TelephonyTimeZoneSuggestion.Builder(slotIndex);
+        suggestionBuilder.addDebugInfo("findTimeZoneForTestNetwork: nitzSignal=" + nitzSignal);
+        OffsetResult lookupResult =
+                mTimeZoneLookupHelper.lookupByNitz(nitzData);
+        if (lookupResult == null) {
+            suggestionBuilder.addDebugInfo("findTimeZoneForTestNetwork: No zone found");
+        } else {
+            suggestionBuilder.setZoneId(lookupResult.getTimeZone().getID());
+            suggestionBuilder.setMatchType(
+                    TelephonyTimeZoneSuggestion.MATCH_TYPE_TEST_NETWORK_OFFSET_ONLY);
+            int quality = lookupResult.isOnlyMatch()
+                    ? TelephonyTimeZoneSuggestion.QUALITY_SINGLE_ZONE
+                    : TelephonyTimeZoneSuggestion.QUALITY_MULTIPLE_ZONES_WITH_SAME_OFFSET;
+            suggestionBuilder.setQuality(quality);
+            suggestionBuilder.addDebugInfo(
+                    "findTimeZoneForTestNetwork: lookupResult=" + lookupResult);
+        }
+        return suggestionBuilder.build();
+    }
+
+    /**
+     * Creates a {@link TelephonyTimeZoneSuggestion} using network country code and NITZ.
+     */
+    @NonNull
+    private TelephonyTimeZoneSuggestion findTimeZoneFromCountryAndNitz(
+            int slotIndex, @NonNull String countryIsoCode,
+            @NonNull TimestampedValue<NitzData> nitzSignal) {
+        Objects.requireNonNull(countryIsoCode);
+        Objects.requireNonNull(nitzSignal);
+
+        TelephonyTimeZoneSuggestion.Builder suggestionBuilder =
+                new TelephonyTimeZoneSuggestion.Builder(slotIndex);
+        suggestionBuilder.addDebugInfo("findTimeZoneFromCountryAndNitz:"
+                + " countryIsoCode=" + countryIsoCode
+                + ", nitzSignal=" + nitzSignal);
+        NitzData nitzData = Objects.requireNonNull(nitzSignal.getValue());
+        if (isNitzSignalOffsetInfoBogus(countryIsoCode, nitzData)) {
+            suggestionBuilder.addDebugInfo(
+                    "findTimeZoneFromCountryAndNitz: NITZ signal looks bogus");
+            return suggestionBuilder.build();
+        }
+
+        // Try to find a match using both country + NITZ signal.
+        OffsetResult lookupResult =
+                mTimeZoneLookupHelper.lookupByNitzCountry(nitzData, countryIsoCode);
+        if (lookupResult != null) {
+            suggestionBuilder.setZoneId(lookupResult.getTimeZone().getID());
+            suggestionBuilder.setMatchType(
+                    TelephonyTimeZoneSuggestion.MATCH_TYPE_NETWORK_COUNTRY_AND_OFFSET);
+            int quality = lookupResult.isOnlyMatch()
+                    ? TelephonyTimeZoneSuggestion.QUALITY_SINGLE_ZONE
+                    : TelephonyTimeZoneSuggestion.QUALITY_MULTIPLE_ZONES_WITH_SAME_OFFSET;
+            suggestionBuilder.setQuality(quality);
+            suggestionBuilder.addDebugInfo("findTimeZoneFromCountryAndNitz:"
+                    + " lookupResult=" + lookupResult);
+            return suggestionBuilder.build();
+        }
+
+        // The country + offset provided no match, so see if the country by itself would be enough.
+        CountryResult countryResult = mTimeZoneLookupHelper.lookupByCountry(
+                countryIsoCode, nitzData.getCurrentTimeInMillis());
+        if (countryResult == null) {
+            // Country not recognized.
+            suggestionBuilder.addDebugInfo(
+                    "findTimeZoneFromCountryAndNitz: lookupByCountry() country not recognized");
+            return suggestionBuilder.build();
+        }
+
+        // If the country has a single zone, or it has multiple zones but the default zone is
+        // "boosted" (i.e. the country default is considered a good suggestion in most cases) then
+        // use it.
+        if (countryResult.quality == CountryResult.QUALITY_SINGLE_ZONE
+                || countryResult.quality == CountryResult.QUALITY_DEFAULT_BOOSTED) {
+            suggestionBuilder.setZoneId(countryResult.zoneId);
+            suggestionBuilder.setMatchType(
+                    TelephonyTimeZoneSuggestion.MATCH_TYPE_NETWORK_COUNTRY_ONLY);
+            suggestionBuilder.setQuality(TelephonyTimeZoneSuggestion.QUALITY_SINGLE_ZONE);
+            suggestionBuilder.addDebugInfo(
+                    "findTimeZoneFromCountryAndNitz: high quality country-only suggestion:"
+                            + " countryResult=" + countryResult);
+            return suggestionBuilder.build();
+        }
+
+        // Quality is not high enough to set the zone using country only.
+        suggestionBuilder.addDebugInfo("findTimeZoneFromCountryAndNitz: country-only suggestion"
+                + " quality not high enough. countryResult=" + countryResult);
+        return suggestionBuilder.build();
+    }
+
+    /**
+     * Creates a {@link TelephonyTimeZoneSuggestion} using only network country code; works well on
+     * countries which only have one time zone or multiple zones with the same offset.
+     *
+     * @param countryIsoCode country code from network MCC
+     * @param whenMillis the time to use when looking at time zone rules data
+     */
+    @NonNull
+    private TelephonyTimeZoneSuggestion findTimeZoneFromNetworkCountryCode(
+            int slotIndex, @NonNull String countryIsoCode, long whenMillis) {
+        Objects.requireNonNull(countryIsoCode);
+        if (TextUtils.isEmpty(countryIsoCode)) {
+            throw new IllegalArgumentException("countryIsoCode must not be empty");
+        }
+
+        TelephonyTimeZoneSuggestion.Builder suggestionBuilder =
+                new TelephonyTimeZoneSuggestion.Builder(slotIndex);
+        suggestionBuilder.addDebugInfo("findTimeZoneFromNetworkCountryCode:"
+                + " whenMillis=" + whenMillis + ", countryIsoCode=" + countryIsoCode);
+        CountryResult lookupResult = mTimeZoneLookupHelper.lookupByCountry(
+                countryIsoCode, whenMillis);
+        if (lookupResult != null) {
+            suggestionBuilder.setZoneId(lookupResult.zoneId);
+            suggestionBuilder.setMatchType(
+                    TelephonyTimeZoneSuggestion.MATCH_TYPE_NETWORK_COUNTRY_ONLY);
+
+            int quality;
+            if (lookupResult.quality == CountryResult.QUALITY_SINGLE_ZONE
+                    || lookupResult.quality == CountryResult.QUALITY_DEFAULT_BOOSTED) {
+                quality = TelephonyTimeZoneSuggestion.QUALITY_SINGLE_ZONE;
+            } else if (lookupResult.quality == CountryResult.QUALITY_MULTIPLE_ZONES_SAME_OFFSET) {
+                quality = TelephonyTimeZoneSuggestion.QUALITY_MULTIPLE_ZONES_WITH_SAME_OFFSET;
+            } else if (lookupResult.quality
+                    == CountryResult.QUALITY_MULTIPLE_ZONES_DIFFERENT_OFFSETS) {
+                quality = TelephonyTimeZoneSuggestion.QUALITY_MULTIPLE_ZONES_WITH_DIFFERENT_OFFSETS;
+            } else {
+                // This should never happen.
+                throw new IllegalArgumentException(
+                        "lookupResult.quality not recognized: countryIsoCode=" + countryIsoCode
+                                + ", whenMillis=" + whenMillis + ", lookupResult=" + lookupResult);
+            }
+            suggestionBuilder.setQuality(quality);
+            suggestionBuilder.addDebugInfo(
+                    "findTimeZoneFromNetworkCountryCode: lookupResult=" + lookupResult);
+        } else {
+            suggestionBuilder.addDebugInfo(
+                    "findTimeZoneFromNetworkCountryCode: Country not recognized?");
+        }
+        return suggestionBuilder.build();
+    }
+
+    /**
+     * Returns true if the NITZ signal is definitely bogus, assuming that the country is correct.
+     */
+    private boolean isNitzSignalOffsetInfoBogus(String countryIsoCode, NitzData nitzData) {
+        if (TextUtils.isEmpty(countryIsoCode)) {
+            // We cannot say for sure.
+            return false;
+        }
+
+        boolean zeroOffsetNitz = nitzData.getLocalOffsetMillis() == 0;
+        return zeroOffsetNitz && !countryUsesUtc(countryIsoCode, nitzData);
+    }
+
+    private boolean countryUsesUtc(String countryIsoCode, NitzData nitzData) {
+        return mTimeZoneLookupHelper.countryUsesUtc(
+                countryIsoCode, nitzData.getCurrentTimeInMillis());
+    }
+}
diff --git a/src/java/com/android/internal/telephony/sip/SipCallBase.java b/src/java/com/android/internal/telephony/sip/SipCallBase.java
index 395692a..a7396cc 100644
--- a/src/java/com/android/internal/telephony/sip/SipCallBase.java
+++ b/src/java/com/android/internal/telephony/sip/SipCallBase.java
@@ -17,21 +17,12 @@
 package com.android.internal.telephony.sip;
 
 import com.android.internal.telephony.Call;
-import com.android.internal.telephony.Connection;
-import java.util.Iterator;
-import java.util.List;
 
 abstract class SipCallBase extends Call {
 
     @Override
-    public List<Connection> getConnections() {
-        // FIXME should return Collections.unmodifiableList();
-        return mConnections;
-    }
-
-    @Override
     public boolean isMultiparty() {
-        return mConnections.size() > 1;
+        return getConnectionsCount() > 1;
     }
 
     @Override
diff --git a/src/java/com/android/internal/telephony/sip/SipCommandInterface.java b/src/java/com/android/internal/telephony/sip/SipCommandInterface.java
index 075b4d6..e57c12f 100644
--- a/src/java/com/android/internal/telephony/sip/SipCommandInterface.java
+++ b/src/java/com/android/internal/telephony/sip/SipCommandInterface.java
@@ -23,12 +23,14 @@
 import android.os.Message;
 import android.telephony.ImsiEncryptionInfo;
 import android.telephony.NetworkScanRequest;
+import android.telephony.SignalThresholdInfo;
 import android.telephony.data.DataProfile;
 import android.telephony.emergency.EmergencyNumber;
 
 import com.android.internal.telephony.BaseCommands;
 import com.android.internal.telephony.CommandsInterface;
 import com.android.internal.telephony.UUSInfo;
+import com.android.internal.telephony.uicc.IccCardApplicationStatus.PersoSubState;
 import com.android.internal.telephony.cdma.CdmaSmsBroadcastConfigInfo;
 import com.android.internal.telephony.gsm.SmsBroadcastConfigInfo;
 
@@ -90,6 +92,11 @@
     }
 
     @Override
+    public void supplySimDepersonalization(PersoSubState persoType,
+            String controlKey, Message result) {
+    }
+
+    @Override
     public void getCurrentCalls(Message result) {
     }
 
@@ -241,6 +248,10 @@
     }
 
     @Override
+    public void sendCdmaSMSExpectMore(byte[] pdu, Message result) {
+    }
+
+    @Override
     public void sendImsGsmSms (String smscPDU, String pdu,
             int retry, int messageRef, Message response) {
     }
@@ -267,7 +278,7 @@
     }
 
     @Override
-    public void writeSmsToRuim(int status, String pdu, Message response) {
+    public void writeSmsToRuim(int status, byte[] pdu, Message response) {
     }
 
     @Override
@@ -334,8 +345,7 @@
     }
 
     @Override
-    public void setNetworkSelectionModeManual(
-            String operatorNumeric, Message response) {
+    public void setNetworkSelectionModeManual(String operatorNumeric, int ran, Message response) {
     }
 
     @Override
@@ -635,8 +645,8 @@
     }
 
     @Override
-    public void setSignalStrengthReportingCriteria(int hysteresisMs, int hysteresisDb,
-            int[] thresholdsDbm, int ran, Message result) {
+    public void setSignalStrengthReportingCriteria(
+            SignalThresholdInfo signalThresholdInfo, int ran, Message result) {
     }
 
     @Override
diff --git a/src/java/com/android/internal/telephony/sip/SipConnectionBase.java b/src/java/com/android/internal/telephony/sip/SipConnectionBase.java
index acf6d36..40e5203 100644
--- a/src/java/com/android/internal/telephony/sip/SipConnectionBase.java
+++ b/src/java/com/android/internal/telephony/sip/SipConnectionBase.java
@@ -16,16 +16,15 @@
 
 package com.android.internal.telephony.sip;
 
+import android.os.SystemClock;
+import android.telephony.PhoneNumberUtils;
+
 import com.android.internal.telephony.Call;
 import com.android.internal.telephony.Connection;
 import com.android.internal.telephony.Phone;
 import com.android.internal.telephony.PhoneConstants;
 import com.android.internal.telephony.UUSInfo;
-
-import android.os.SystemClock;
-import android.telephony.DisconnectCause;
-import android.telephony.Rlog;
-import android.telephony.PhoneNumberUtils;
+import com.android.telephony.Rlog;
 
 abstract class SipConnectionBase extends Connection {
     private static final String LOG_TAG = "SipConnBase";
diff --git a/src/java/com/android/internal/telephony/sip/SipPhone.java b/src/java/com/android/internal/telephony/sip/SipPhone.java
old mode 100644
new mode 100755
index ff9e5a7..adfcdce
--- a/src/java/com/android/internal/telephony/sip/SipPhone.java
+++ b/src/java/com/android/internal/telephony/sip/SipPhone.java
@@ -16,6 +16,7 @@
 
 package com.android.internal.telephony.sip;
 
+import android.compat.annotation.UnsupportedAppUsage;
 import android.content.Context;
 import android.media.AudioManager;
 import android.net.rtp.AudioGroup;
@@ -31,7 +32,6 @@
 import android.telephony.PhoneNumberUtils;
 import android.telephony.ServiceState;
 import android.text.TextUtils;
-import android.telephony.Rlog;
 
 import com.android.internal.telephony.Call;
 import com.android.internal.telephony.CallStateException;
@@ -39,9 +39,9 @@
 import com.android.internal.telephony.Phone;
 import com.android.internal.telephony.PhoneConstants;
 import com.android.internal.telephony.PhoneNotifier;
+import com.android.telephony.Rlog;
 
 import java.text.ParseException;
-import java.util.List;
 import java.util.regex.Pattern;
 
 /**
@@ -59,7 +59,9 @@
 
     // A call that is ringing or (call) waiting
     private SipCall mRingingCall = new SipCall();
+    @UnsupportedAppUsage
     private SipCall mForegroundCall = new SipCall();
+    @UnsupportedAppUsage
     private SipCall mBackgroundCall = new SipCall();
 
     private SipManager mSipManager;
@@ -183,6 +185,12 @@
     }
 
     @Override
+    public Connection startConference(String[] participantsToDial, DialArgs dialArgs)
+            throws CallStateException {
+        throw new CallStateException("startConference: not supported");
+    }
+
+    @Override
     public Connection dial(String dialString, DialArgs dialArgs) throws CallStateException {
         synchronized (SipPhone.class) {
             return dialInternal(dialString, dialArgs.videoState);
@@ -431,6 +439,7 @@
         return false;
     }
 
+    @UnsupportedAppUsage
     private void log(String s) {
         Rlog.d(LOG_TAG, s);
     }
@@ -439,6 +448,7 @@
         Rlog.d(LOG_TAG, s);
     }
 
+    @UnsupportedAppUsage
     private void loge(String s) {
         Rlog.e(LOG_TAG, s);
     }
@@ -454,10 +464,11 @@
 
         void reset() {
             if (SC_DBG) log("reset");
-            mConnections.clear();
+            clearConnections();
             setState(Call.State.IDLE);
         }
 
+        @UnsupportedAppUsage
         void switchWith(SipCall that) {
             if (SC_DBG) log("switchWith");
             synchronized (SipPhone.class) {
@@ -470,9 +481,9 @@
 
         private void takeOver(SipCall that) {
             if (SC_DBG) log("takeOver");
-            mConnections = that.mConnections;
+            copyConnectionFrom(that);
             mState = that.mState;
-            for (Connection c : mConnections) {
+            for (Connection c : getConnections()) {
                 ((SipConnection) c).changeOwner(this);
             }
         }
@@ -482,15 +493,6 @@
             return SipPhone.this;
         }
 
-        @Override
-        public List<Connection> getConnections() {
-            if (SC_VDBG) log("getConnections");
-            synchronized (SipPhone.class) {
-                // FIXME should return Collections.unmodifiableList();
-                return mConnections;
-            }
-        }
-
         Connection dial(String originalNumber) throws SipException {
             if (SC_DBG) log("dial: num=" + (SC_VDBG ? originalNumber : "xxx"));
             // TODO: Should this be synchronized?
@@ -506,7 +508,7 @@
                 SipConnection c = new SipConnection(this, callee,
                         originalNumber);
                 c.dial();
-                mConnections.add(c);
+                addConnection(c);
                 setState(Call.State.DIALING);
                 return c;
             } catch (ParseException e) {
@@ -522,7 +524,7 @@
                             + ": " + this + " on phone " + getPhone());
                     setState(State.DISCONNECTING);
                     CallStateException excp = null;
-                    for (Connection c : mConnections) {
+                    for (Connection c : getConnections()) {
                         try {
                             c.hangup();
                         } catch (CallStateException e) {
@@ -537,10 +539,20 @@
             }
         }
 
+        /**
+         * Hangup the ringing call with a specified reason; reason is not supported on SIP.
+         * @param rejectReason
+         */
+        @Override
+        public void hangup(@android.telecom.Call.RejectReason int rejectReason)
+                throws CallStateException  {
+            hangup();
+        }
+
         SipConnection initIncomingCall(SipAudioCall sipAudioCall, boolean makeCallWait) {
             SipProfile callee = sipAudioCall.getPeerProfile();
             SipConnection c = new SipConnection(this, callee);
-            mConnections.add(c);
+            addConnection(c);
 
             Call.State newState = makeCallWait ? State.WAITING : State.INCOMING;
             c.initIncomingCall(sipAudioCall, newState);
@@ -560,10 +572,10 @@
             if (this != mRingingCall) {
                 throw new CallStateException("acceptCall() in a non-ringing call");
             }
-            if (mConnections.size() != 1) {
+            if (getConnectionsCount() != 1) {
                 throw new CallStateException("acceptCall() in a conf call");
             }
-            ((SipConnection) mConnections.get(0)).acceptCall();
+            ((SipConnection) getConnections().get(0)).acceptCall();
         }
 
         private boolean isSpeakerOn() {
@@ -594,18 +606,20 @@
                     audioGroup.getMode()));
         }
 
+        @UnsupportedAppUsage
         void hold() throws CallStateException {
             if (SC_DBG) log("hold:");
             setState(State.HOLDING);
-            for (Connection c : mConnections) ((SipConnection) c).hold();
+            for (Connection c : getConnections()) ((SipConnection) c).hold();
             setAudioGroupMode();
         }
 
+        @UnsupportedAppUsage
         void unhold() throws CallStateException {
             if (SC_DBG) log("unhold:");
             setState(State.ACTIVE);
-            AudioGroup audioGroup = new AudioGroup();
-            for (Connection c : mConnections) {
+            AudioGroup audioGroup = new AudioGroup(mContext);
+            for (Connection c : getConnections()) {
                 ((SipConnection) c).unhold(audioGroup);
             }
             setAudioGroupMode();
@@ -613,15 +627,15 @@
 
         void setMute(boolean muted) {
             if (SC_DBG) log("setMute: muted=" + muted);
-            for (Connection c : mConnections) {
+            for (Connection c : getConnections()) {
                 ((SipConnection) c).setMute(muted);
             }
         }
 
         boolean getMute() {
-            boolean ret = mConnections.isEmpty()
+            boolean ret = getConnections().isEmpty()
                     ? false
-                    : ((SipConnection) mConnections.get(0)).getMute();
+                    : ((SipConnection) getConnections().get(0)).getMute();
             if (SC_DBG) log("getMute: ret=" + ret);
             return ret;
         }
@@ -632,8 +646,8 @@
 
             // copy to an array to avoid concurrent modification as connections
             // in that.connections will be removed in add(SipConnection).
-            Connection[] cc = that.mConnections.toArray(
-                    new Connection[that.mConnections.size()]);
+            Connection[] cc = that.getConnections().toArray(
+                    new Connection[that.getConnectionsCount()]);
             for (Connection c : cc) {
                 SipConnection conn = (SipConnection) c;
                 add(conn);
@@ -648,9 +662,9 @@
             if (SC_DBG) log("add:");
             SipCall call = conn.getCall();
             if (call == this) return;
-            if (call != null) call.mConnections.remove(conn);
+            if (call != null) call.removeConnection(conn);
 
-            mConnections.add(conn);
+            addConnection(conn);
             conn.changeOwner(this);
         }
 
@@ -687,7 +701,7 @@
             if (mState != newState) {
                 if (SC_DBG) log("setState: cur state" + mState
                         + " --> " + newState + ": " + this + ": on phone "
-                        + getPhone() + " " + mConnections.size());
+                        + getPhone() + " " + getConnectionsCount());
 
                 if (newState == Call.State.ALERTING) {
                     mState = newState; // need in ALERTING to enable ringback
@@ -715,8 +729,8 @@
             if (mState != State.DISCONNECTED) {
                 boolean allConnectionsDisconnected = true;
                 if (SC_DBG) log("---check connections: "
-                        + mConnections.size());
-                for (Connection c : mConnections) {
+                        + getConnectionsCount());
+                for (Connection c : getConnections()) {
                     if (SC_DBG) log("   state=" + c.getState() + ": "
                             + c);
                     if (c.getState() != State.DISCONNECTED) {
@@ -730,8 +744,8 @@
         }
 
         private AudioGroup getAudioGroup() {
-            if (mConnections.isEmpty()) return null;
-            return ((SipConnection) mConnections.get(0)).getAudioGroup();
+            if (getConnections().isEmpty()) return null;
+            return ((SipConnection) getConnections().get(0)).getAudioGroup();
         }
 
         private void log(String s) {
@@ -1012,6 +1026,19 @@
             throw new CallStateException ("deflect is not supported for SipPhone");
         }
 
+        @Override
+        public void transfer(String number, boolean isConfirmationRequired)
+                throws CallStateException {
+            //Transfer is not supported.
+            throw new CallStateException("transfer is not supported for SipPhone");
+        }
+
+        @Override
+        public void consultativeTransfer(Connection other) throws CallStateException {
+            //Transfer is not supported.
+            throw new CallStateException("transfer is not supported for SipPhone");
+        }
+
         private void log(String s) {
             Rlog.d(SCN_TAG, s);
         }
diff --git a/src/java/com/android/internal/telephony/sip/SipPhoneBase.java b/src/java/com/android/internal/telephony/sip/SipPhoneBase.java
index a87fb2b..dea124b 100755
--- a/src/java/com/android/internal/telephony/sip/SipPhoneBase.java
+++ b/src/java/com/android/internal/telephony/sip/SipPhoneBase.java
@@ -23,9 +23,8 @@
 import android.os.Message;
 import android.os.RegistrantList;
 import android.os.ResultReceiver;
-import android.os.SystemProperties;
+import android.sysprop.TelephonyProperties;
 import android.telephony.NetworkScanRequest;
-import android.telephony.Rlog;
 import android.telephony.ServiceState;
 import android.telephony.SignalStrength;
 
@@ -38,8 +37,8 @@
 import com.android.internal.telephony.Phone;
 import com.android.internal.telephony.PhoneConstants;
 import com.android.internal.telephony.PhoneNotifier;
-import com.android.internal.telephony.TelephonyProperties;
 import com.android.internal.telephony.uicc.IccFileHandler;
+import com.android.telephony.Rlog;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -186,10 +185,9 @@
         Rlog.v(LOG_TAG, "canDial(): serviceState = " + serviceState);
         if (serviceState == ServiceState.STATE_POWER_OFF) return false;
 
-        String disableCall = SystemProperties.get(
-                TelephonyProperties.PROPERTY_DISABLE_CALL, "false");
+        boolean disableCall = TelephonyProperties.disable_call().orElse(false);
         Rlog.v(LOG_TAG, "canDial(): disableCall = " + disableCall);
-        if (disableCall.equals("true")) return false;
+        if (disableCall) return false;
 
         Rlog.v(LOG_TAG, "canDial(): ringingCall: " + getRingingCall().getState());
         Rlog.v(LOG_TAG, "canDial(): foregndCall: " + getForegroundCall().getState());
@@ -326,12 +324,23 @@
     }
 
     @Override
+    public void getCallForwardingOption(int commandInterfaceCFReason, int serviceClass,
+            Message onComplete) {
+    }
+
+    @Override
     public void setCallForwardingOption(int commandInterfaceCFAction,
             int commandInterfaceCFReason, String dialingNumber,
             int timerSeconds, Message onComplete) {
     }
 
     @Override
+    public void setCallForwardingOption(int commandInterfaceCFAction,
+            int commandInterfaceCFReason, String dialingNumber, int serviceClass,
+            int timerSeconds, Message onComplete) {
+    }
+
+    @Override
     public void getOutgoingCallerIdDisplay(Message onComplete) {
         // FIXME: what to reply?
         AsyncResult.forMessage(onComplete, null, null);
diff --git a/src/java/com/android/internal/telephony/sip/SipPhoneFactory.java b/src/java/com/android/internal/telephony/sip/SipPhoneFactory.java
index 3383bed..42b7be7 100644
--- a/src/java/com/android/internal/telephony/sip/SipPhoneFactory.java
+++ b/src/java/com/android/internal/telephony/sip/SipPhoneFactory.java
@@ -16,11 +16,11 @@
 
 package com.android.internal.telephony.sip;
 
-import com.android.internal.telephony.PhoneNotifier;
-
 import android.content.Context;
 import android.net.sip.SipProfile;
-import android.telephony.Rlog;
+
+import com.android.internal.telephony.PhoneNotifier;
+import com.android.telephony.Rlog;
 
 import java.text.ParseException;
 
diff --git a/src/java/com/android/internal/telephony/test/TestConferenceEventPackageParser.java b/src/java/com/android/internal/telephony/test/TestConferenceEventPackageParser.java
index 62c2a77..8df7709 100644
--- a/src/java/com/android/internal/telephony/test/TestConferenceEventPackageParser.java
+++ b/src/java/com/android/internal/telephony/test/TestConferenceEventPackageParser.java
@@ -16,16 +16,16 @@
 
 package com.android.internal.telephony.test;
 
+import android.os.Bundle;
 import android.telephony.ims.ImsConferenceState;
-import com.android.internal.util.XmlUtils;
+import android.util.Log;
+import android.util.Xml;
+
+import com.android.internal.telephony.util.XmlUtils;
 
 import org.xmlpull.v1.XmlPullParser;
 import org.xmlpull.v1.XmlPullParserException;
 
-import android.os.Bundle;
-import android.util.Log;
-import android.util.Xml;
-
 import java.io.IOException;
 import java.io.InputStream;
 
diff --git a/src/java/com/android/internal/telephony/uicc/AdnRecord.java b/src/java/com/android/internal/telephony/uicc/AdnRecord.java
index 7aa6a11..a0b6438 100644
--- a/src/java/com/android/internal/telephony/uicc/AdnRecord.java
+++ b/src/java/com/android/internal/telephony/uicc/AdnRecord.java
@@ -16,18 +16,17 @@
 
 package com.android.internal.telephony.uicc;
 
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.telephony.PhoneNumberUtils;
-import android.telephony.Rlog;
 import android.text.TextUtils;
 
 import com.android.internal.telephony.GsmAlphabet;
+import com.android.telephony.Rlog;
 
 import java.util.Arrays;
 
-
 /**
  *
  * Used to load or store ADNs (Abbreviated Dialing Numbers).
@@ -93,7 +92,7 @@
             recordNumber = source.readInt();
             alphaTag = source.readString();
             number = source.readString();
-            emails = source.readStringArray();
+            emails = source.createStringArray();
 
             return new AdnRecord(efid, recordNumber, alphaTag, number, emails);
         }
diff --git a/src/java/com/android/internal/telephony/uicc/AdnRecordCache.java b/src/java/com/android/internal/telephony/uicc/AdnRecordCache.java
index 959984b..90d7a38 100644
--- a/src/java/com/android/internal/telephony/uicc/AdnRecordCache.java
+++ b/src/java/com/android/internal/telephony/uicc/AdnRecordCache.java
@@ -16,7 +16,7 @@
 
 package com.android.internal.telephony.uicc;
 
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
 import android.os.AsyncResult;
 import android.os.Handler;
 import android.os.Message;
diff --git a/src/java/com/android/internal/telephony/uicc/AdnRecordLoader.java b/src/java/com/android/internal/telephony/uicc/AdnRecordLoader.java
index 391b8fc..a23248c 100644
--- a/src/java/com/android/internal/telephony/uicc/AdnRecordLoader.java
+++ b/src/java/com/android/internal/telephony/uicc/AdnRecordLoader.java
@@ -16,16 +16,15 @@
 
 package com.android.internal.telephony.uicc;
 
-import java.util.ArrayList;
-
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
 import android.os.AsyncResult;
 import android.os.Handler;
 import android.os.Looper;
 import android.os.Message;
-import android.telephony.Rlog;
 
-import com.android.internal.telephony.uicc.IccConstants;
+import com.android.telephony.Rlog;
+
+import java.util.ArrayList;
 
 public class AdnRecordLoader extends Handler {
     final static String LOG_TAG = "AdnRecordLoader";
diff --git a/src/java/com/android/internal/telephony/uicc/AnswerToReset.java b/src/java/com/android/internal/telephony/uicc/AnswerToReset.java
index 6fd7c68..9ded7d6 100644
--- a/src/java/com/android/internal/telephony/uicc/AnswerToReset.java
+++ b/src/java/com/android/internal/telephony/uicc/AnswerToReset.java
@@ -17,10 +17,10 @@
 package com.android.internal.telephony.uicc;
 
 import android.annotation.Nullable;
-import android.telephony.Rlog;
 import android.util.ArrayMap;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.telephony.Rlog;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
diff --git a/src/java/com/android/internal/telephony/uicc/CarrierTestOverride.java b/src/java/com/android/internal/telephony/uicc/CarrierTestOverride.java
index 61379d2..da51848 100644
--- a/src/java/com/android/internal/telephony/uicc/CarrierTestOverride.java
+++ b/src/java/com/android/internal/telephony/uicc/CarrierTestOverride.java
@@ -17,10 +17,10 @@
 package com.android.internal.telephony.uicc;
 
 import android.os.Environment;
-import android.telephony.Rlog;
 import android.util.Xml;
 
-import com.android.internal.util.XmlUtils;
+import com.android.internal.telephony.util.XmlUtils;
+import com.android.telephony.Rlog;
 
 import org.xmlpull.v1.XmlPullParser;
 import org.xmlpull.v1.XmlPullParserException;
diff --git a/src/java/com/android/internal/telephony/uicc/CsimFileHandler.java b/src/java/com/android/internal/telephony/uicc/CsimFileHandler.java
index e45afa9..c5dd05c 100644
--- a/src/java/com/android/internal/telephony/uicc/CsimFileHandler.java
+++ b/src/java/com/android/internal/telephony/uicc/CsimFileHandler.java
@@ -16,9 +16,8 @@
 
 package com.android.internal.telephony.uicc;
 
-import android.telephony.Rlog;
-
 import com.android.internal.telephony.CommandsInterface;
+import com.android.telephony.Rlog;
 
 /**
  * {@hide}
diff --git a/src/java/com/android/internal/telephony/uicc/IccCardApplicationStatus.java b/src/java/com/android/internal/telephony/uicc/IccCardApplicationStatus.java
index 76242c4..d0ad358 100644
--- a/src/java/com/android/internal/telephony/uicc/IccCardApplicationStatus.java
+++ b/src/java/com/android/internal/telephony/uicc/IccCardApplicationStatus.java
@@ -16,11 +16,10 @@
 
 package com.android.internal.telephony.uicc;
 
-import android.annotation.UnsupportedAppUsage;
-import android.telephony.Rlog;
+import android.compat.annotation.UnsupportedAppUsage;
 
 import com.android.internal.telephony.uicc.IccCardStatus.PinState;
-
+import com.android.telephony.Rlog;
 
 /**
  * See also RIL_AppStatus in include/telephony/ril.h
@@ -29,6 +28,8 @@
  */
 public class IccCardApplicationStatus {
     // TODO: Replace with constants from PhoneConstants.APPTYPE_xxx
+    @UnsupportedAppUsage(implicitMember =
+            "values()[Lcom/android/internal/telephony/uicc/IccCardApplicationStatus$AppType;")
     public enum AppType{
         @UnsupportedAppUsage
         APPTYPE_UNKNOWN,
@@ -44,6 +45,8 @@
         APPTYPE_ISIM
     }
 
+    @UnsupportedAppUsage(implicitMember =
+            "values()[Lcom/android/internal/telephony/uicc/IccCardApplicationStatus$AppState;")
     public enum AppState{
         @UnsupportedAppUsage
         APPSTATE_UNKNOWN,
@@ -80,7 +83,9 @@
         }
     }
 
-    public enum PersoSubState{
+    @UnsupportedAppUsage(implicitMember =
+            "values()[Lcom/android/internal/telephony/uicc/IccCardApplicationStatus$PersoSubState;")
+    public enum PersoSubState {
         @UnsupportedAppUsage
         PERSOSUBSTATE_UNKNOWN,
         PERSOSUBSTATE_IN_PROGRESS,
@@ -111,11 +116,32 @@
         PERSOSUBSTATE_RUIM_HRPD_PUK,
         PERSOSUBSTATE_RUIM_CORPORATE_PUK,
         PERSOSUBSTATE_RUIM_SERVICE_PROVIDER_PUK,
-        PERSOSUBSTATE_RUIM_RUIM_PUK;
+        PERSOSUBSTATE_RUIM_RUIM_PUK,
+        PERSOSUBSTATE_SIM_SPN,
+        PERSOSUBSTATE_SIM_SPN_PUK,
+        PERSOSUBSTATE_SIM_SP_EHPLMN,
+        PERSOSUBSTATE_SIM_SP_EHPLMN_PUK,
+        PERSOSUBSTATE_SIM_ICCID,
+        PERSOSUBSTATE_SIM_ICCID_PUK,
+        PERSOSUBSTATE_SIM_IMPI,
+        PERSOSUBSTATE_SIM_IMPI_PUK,
+        PERSOSUBSTATE_SIM_NS_SP,
+        PERSOSUBSTATE_SIM_NS_SP_PUK;
 
         boolean isPersoSubStateUnknown() {
             return this == PERSOSUBSTATE_UNKNOWN;
         }
+
+        public static boolean isPersoLocked(PersoSubState mState) {
+            switch (mState) {
+                case PERSOSUBSTATE_UNKNOWN:
+                case PERSOSUBSTATE_IN_PROGRESS:
+                case PERSOSUBSTATE_READY:
+                    return false;
+                default:
+                    return true;
+            }
+        }
     }
 
     @UnsupportedAppUsage
@@ -133,6 +159,10 @@
     public PinState       pin2;
 
     @UnsupportedAppUsage
+    public IccCardApplicationStatus() {
+    }
+
+    @UnsupportedAppUsage
     public AppType AppTypeFromRILInt(int type) {
         AppType newType;
         /* RIL_AppType ril.h */
@@ -196,6 +226,17 @@
             case 22: newSubState = PersoSubState.PERSOSUBSTATE_RUIM_CORPORATE_PUK; break;
             case 23: newSubState = PersoSubState.PERSOSUBSTATE_RUIM_SERVICE_PROVIDER_PUK; break;
             case 24: newSubState = PersoSubState.PERSOSUBSTATE_RUIM_RUIM_PUK; break;
+            case 25: newSubState = PersoSubState.PERSOSUBSTATE_SIM_SPN; break;
+            case 26: newSubState = PersoSubState.PERSOSUBSTATE_SIM_SPN_PUK; break;
+            case 27: newSubState = PersoSubState.PERSOSUBSTATE_SIM_SP_EHPLMN; break;
+            case 28: newSubState = PersoSubState.PERSOSUBSTATE_SIM_SP_EHPLMN_PUK; break;
+            case 29: newSubState = PersoSubState.PERSOSUBSTATE_SIM_ICCID; break;
+            case 30: newSubState = PersoSubState.PERSOSUBSTATE_SIM_ICCID_PUK; break;
+            case 31: newSubState = PersoSubState.PERSOSUBSTATE_SIM_IMPI; break;
+            case 32: newSubState = PersoSubState.PERSOSUBSTATE_SIM_IMPI_PUK; break;
+            case 33: newSubState = PersoSubState.PERSOSUBSTATE_SIM_NS_SP; break;
+            case 34: newSubState = PersoSubState.PERSOSUBSTATE_SIM_NS_SP_PUK; break;
+
             default:
                 newSubState = PersoSubState.PERSOSUBSTATE_UNKNOWN;
                 loge("PersoSubstateFromRILInt: bad substate: " + substate
diff --git a/src/java/com/android/internal/telephony/uicc/IccCardStatus.java b/src/java/com/android/internal/telephony/uicc/IccCardStatus.java
index df21601..765d1e1 100644
--- a/src/java/com/android/internal/telephony/uicc/IccCardStatus.java
+++ b/src/java/com/android/internal/telephony/uicc/IccCardStatus.java
@@ -16,7 +16,7 @@
 
 package com.android.internal.telephony.uicc;
 
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
 import android.telephony.SubscriptionInfo;
 
 /**
diff --git a/src/java/com/android/internal/telephony/uicc/IccFileHandler.java b/src/java/com/android/internal/telephony/uicc/IccFileHandler.java
index 1c4123d..ceeed34 100644
--- a/src/java/com/android/internal/telephony/uicc/IccFileHandler.java
+++ b/src/java/com/android/internal/telephony/uicc/IccFileHandler.java
@@ -16,8 +16,10 @@
 
 package com.android.internal.telephony.uicc;
 
-import android.annotation.UnsupportedAppUsage;
-import android.os.*;
+import android.compat.annotation.UnsupportedAppUsage;
+import android.os.AsyncResult;
+import android.os.Handler;
+import android.os.Message;
 
 import com.android.internal.telephony.CommandsInterface;
 
@@ -94,6 +96,8 @@
     static protected final int EVENT_READ_ICON_DONE = 10;
     /** Finished retrieving size of record for EFimg now. */
     static protected final int EVENT_GET_RECORD_SIZE_IMG_DONE = 11;
+    /** Finished retriveing record size of transparent file. */
+    protected static final int EVENT_GET_EF_TRANSPARENT_SIZE_DONE = 12;
 
      // member variables
     @UnsupportedAppUsage
@@ -222,14 +226,14 @@
     }
 
     /**
-     * get record size for a linear fixed EF
+     * Get record size for a linear fixed EF
      *
      * @param fileid EF id
      * @param path Path of the EF on the card
-     * @param onLoaded ((AsnyncResult)(onLoaded.obj)).result is the recordSize[]
-     *        int[0] is the record length int[1] is the total length of the EF
-     *        file int[3] is the number of records in the EF file So int[0] *
-     *        int[3] = int[1]
+     * @param onLoaded ((AsnyncResult)(onLoaded.obj)).result is the recordSize[]. recordSize[0] is
+     *                 the single record length, recordSize[1] is the total length of the EF file
+     *                 and recordSize[2] is the number of records in the EF file. So recordSize[0]
+     *                 * recordSize[2] = recordSize[1].
      */
     @UnsupportedAppUsage
     public void getEFLinearRecordSize(int fileid, String path, Message onLoaded) {
@@ -242,13 +246,13 @@
     }
 
     /**
-     * get record size for a linear fixed EF
+     * Get record size for a linear fixed EF
      *
      * @param fileid EF id
-     * @param onLoaded ((AsnyncResult)(onLoaded.obj)).result is the recordSize[]
-     *        int[0] is the record length int[1] is the total length of the EF
-     *        file int[3] is the number of records in the EF file So int[0] *
-     *        int[3] = int[1]
+     * @param onLoaded ((AsnyncResult)(onLoaded.obj)).result is the recordSize[]. recordSize[0] is
+     *                 the single record length, recordSize[1] is the total length of the EF file
+     *                 and recordSize[2] is the number of records in the EF file. So recordSize[0]
+     *                 * recordSize[2] = recordSize[1].
      */
     @UnsupportedAppUsage
     public void getEFLinearRecordSize(int fileid, Message onLoaded) {
@@ -256,6 +260,39 @@
     }
 
     /**
+     * Get record size for a transparent EF
+     *
+     * @param fileid EF id
+     * @param path Path of the EF on the card
+     * @param onLoaded ((AsnyncResult)(onLoaded.obj)).result is the size of data int
+     */
+    public void getEFTransparentRecordSize(int fileid, String path, Message onLoaded) {
+        String efPath = (path == null) ? getEFPath(fileid) : path;
+        Message response = obtainMessage(EVENT_GET_EF_TRANSPARENT_SIZE_DONE, fileid, 0, onLoaded);
+        mCi.iccIOForApp(
+                COMMAND_GET_RESPONSE,
+                fileid,
+                getEFPath(fileid),
+                0,
+                0,
+                GET_RESPONSE_EF_SIZE_BYTES,
+                null,
+                null,
+                mAid,
+                response);
+    }
+
+    /**
+     * Get record size for a transparent EF
+     *
+     * @param fileid EF id
+     * @param onLoaded ((AsnyncResult)(onLoaded.obj)).result is the size of the data int
+     */
+    public void getEFTransparentRecordSize(int fileid, Message onLoaded) {
+        getEFTransparentRecordSize(fileid, getEFPath(fileid), onLoaded);
+    }
+
+    /**
      * Load all records from a SIM Linear Fixed EF
      *
      * @param fileid EF id
@@ -263,7 +300,6 @@
      * @param onLoaded
      *
      * ((AsyncResult)(onLoaded.obj)).result is an ArrayList<byte[]>
-     *
      */
     @UnsupportedAppUsage
     public void loadEFLinearFixedAll(int fileid, String path, Message onLoaded) {
@@ -296,9 +332,7 @@
      * @param onLoaded
      *
      * ((AsyncResult)(onLoaded.obj)).result is the byte[]
-     *
      */
-
     @UnsupportedAppUsage
     public void loadEFTransparent(int fileid, Message onLoaded) {
         Message response = obtainMessage(EVENT_GET_BINARY_SIZE_DONE,
@@ -473,15 +507,14 @@
 
                 recordSize = new int[3];
                 recordSize[0] = data[RESPONSE_DATA_RECORD_LENGTH] & 0xFF;
-                recordSize[1] = ((data[RESPONSE_DATA_FILE_SIZE_1] & 0xff) << 8)
-                       + (data[RESPONSE_DATA_FILE_SIZE_2] & 0xff);
+                recordSize[1] = getDataFileSize(data);
                 recordSize[2] = recordSize[1] / recordSize[0];
 
                 sendResult(response, recordSize, null);
                 break;
 
-             case EVENT_GET_RECORD_SIZE_IMG_DONE:
-             case EVENT_GET_RECORD_SIZE_DONE:
+            case EVENT_GET_RECORD_SIZE_IMG_DONE:
+            case EVENT_GET_RECORD_SIZE_DONE:
                 ar = (AsyncResult)msg.obj;
                 lc = (LoadLinearFixedContext) ar.userObj;
                 result = (IccIoResult) ar.result;
@@ -505,24 +538,23 @@
 
                 lc.mRecordSize = data[RESPONSE_DATA_RECORD_LENGTH] & 0xFF;
 
-                size = ((data[RESPONSE_DATA_FILE_SIZE_1] & 0xff) << 8)
-                       + (data[RESPONSE_DATA_FILE_SIZE_2] & 0xff);
+                size = getDataFileSize(data);
 
                 lc.mCountRecords = size / lc.mRecordSize;
 
-                 if (lc.mLoadAll) {
-                     lc.results = new ArrayList<byte[]>(lc.mCountRecords);
-                 }
+                if (lc.mLoadAll) {
+                    lc.results = new ArrayList<byte[]>(lc.mCountRecords);
+                }
 
-                 if (path == null) {
-                     path = getEFPath(lc.mEfid);
-                 }
-                 mCi.iccIOForApp(COMMAND_READ_RECORD, lc.mEfid, path,
-                         lc.mRecordNum,
-                         READ_RECORD_MODE_ABSOLUTE,
-                         lc.mRecordSize, null, null, mAid,
-                         obtainMessage(EVENT_READ_RECORD_DONE, lc));
-                 break;
+                if (path == null) {
+                    path = getEFPath(lc.mEfid);
+                }
+                mCi.iccIOForApp(COMMAND_READ_RECORD, lc.mEfid, path,
+                        lc.mRecordNum,
+                        READ_RECORD_MODE_ABSOLUTE,
+                        lc.mRecordSize, null, null, mAid,
+                        obtainMessage(EVENT_READ_RECORD_DONE, lc));
+                break;
             case EVENT_GET_BINARY_SIZE_DONE:
                 ar = (AsyncResult)msg.obj;
                 response = (Message) ar.userObj;
@@ -549,13 +581,12 @@
                     throw new IccFileTypeMismatch();
                 }
 
-                size = ((data[RESPONSE_DATA_FILE_SIZE_1] & 0xff) << 8)
-                       + (data[RESPONSE_DATA_FILE_SIZE_2] & 0xff);
+                size = getDataFileSize(data);
 
                 mCi.iccIOForApp(COMMAND_READ_BINARY, fileid, getEFPath(fileid),
                                 0, 0, size, null, null, mAid,
                                 obtainMessage(EVENT_READ_BINARY_DONE,
-                                              fileid, 0, response));
+                                            fileid, 0, response));
             break;
 
             case EVENT_READ_IMG_DONE:
@@ -608,6 +639,31 @@
                 sendResult(response, result.payload, null);
             break;
 
+            case EVENT_GET_EF_TRANSPARENT_SIZE_DONE:
+                ar = (AsyncResult) msg.obj;
+                response = (Message) ar.userObj;
+                result = (IccIoResult) ar.result;
+
+                if (processException(response, (AsyncResult) msg.obj)) {
+                    break;
+                }
+
+                data = result.payload;
+
+                fileid = msg.arg1;
+
+                if (TYPE_EF != data[RESPONSE_DATA_FILE_TYPE]) {
+                    throw new IccFileTypeMismatch();
+                }
+
+                if (EF_TYPE_TRANSPARENT != data[RESPONSE_DATA_STRUCTURE]) {
+                    throw new IccFileTypeMismatch();
+                }
+
+                size = getDataFileSize(data);
+                sendResult(response, size, null);
+                break;
+
         }} catch (Exception exc) {
             if (response != null) {
                 sendResult(response, null, exc);
@@ -656,4 +712,14 @@
     protected abstract void logd(String s);
     protected abstract void loge(String s);
 
+    /**
+     * Calculate the size of a data file
+     *
+     * @param data the raw file
+     * @return the size of the file
+     */
+    private static int getDataFileSize(byte[] data) {
+        return (((data[RESPONSE_DATA_FILE_SIZE_1] & 0xff) << 8)
+                    + (data[RESPONSE_DATA_FILE_SIZE_2] & 0xff));
+    }
 }
diff --git a/src/java/com/android/internal/telephony/uicc/IccIoResult.java b/src/java/com/android/internal/telephony/uicc/IccIoResult.java
index 7a922af..c3fdf1f 100644
--- a/src/java/com/android/internal/telephony/uicc/IccIoResult.java
+++ b/src/java/com/android/internal/telephony/uicc/IccIoResult.java
@@ -16,8 +16,9 @@
 
 package com.android.internal.telephony.uicc;
 
-import android.annotation.UnsupportedAppUsage;
-import android.os.Build;
+import android.compat.annotation.UnsupportedAppUsage;
+
+import com.android.internal.telephony.util.TelephonyUtils;
 
 /**
  * {@hide}
@@ -34,88 +35,92 @@
         // All error codes below are copied directly from their respective specification
         // without modification except in cases where necessary string formatting has been omitted.
         switch(sw1) {
+            case 0x61: return sw2 + " more response bytes available";
             case 0x62:
                 switch(sw2) {
-                    case 0x00: return "No information given,"
-                               + " state of non volatile memory unchanged";
-                    case 0x81: return "Part of returned data may be corrupted";
-                    case 0x82: return "End of file/record reached before reading Le bytes";
-                    case 0x83: return "Selected file invalidated";
-                    case 0x84: return "Selected file in termination state";
-                    case 0xF1: return "More data available";
-                    case 0xF2: return "More data available and proactive command pending";
-                    case 0xF3: return "Response data available";
+                    case 0x00: return "no information given,"
+                            + " state of non volatile memory unchanged";
+                    case 0x81: return "part of returned data may be corrupted";
+                    case 0x82: return "end of file/record reached before reading Le bytes";
+                    case 0x83: return "selected file invalidated";
+                    case 0x84: return "selected file in termination state";
+                    case 0xF1: return "more data available";
+                    case 0xF2: return "more data available and proactive command pending";
+                    case 0xF3: return "response data available";
                 }
                 break;
             case 0x63:
                 if (sw2 >> 4 == 0x0C) {
-                    return "Command successful but after using an internal"
-                        + "update retry routine but Verification failed";
+                    int retries = sw2 & 0x0F;
+                    return "command successful but after using an internal update retry routine "
+                            + retries + " times,"
+                            + " or verification failed, " + retries + " retries remaining";
                 }
                 switch(sw2) {
-                    case 0xF1: return "More data expected";
-                    case 0xF2: return "More data expected and proactive command pending";
+                    case 0xF1: return "more data expected";
+                    case 0xF2: return "more data expected and proactive command pending";
                 }
                 break;
             case 0x64:
                 switch(sw2) {
-                    case 0x00: return "No information given,"
+                    case 0x00: return "no information given,"
                                + " state of non-volatile memory unchanged";
                 }
                 break;
             case 0x65:
                 switch(sw2) {
-                    case 0x00: return "No information given, state of non-volatile memory changed";
-                    case 0x81: return "Memory problem";
+                    case 0x00: return "no information given, state of non-volatile memory changed";
+                    case 0x81: return "memory problem";
                 }
                 break;
             case 0x67:
                 switch(sw2) {
                     case 0x00: return "incorrect parameter P3";
-                    default: return "The interpretation of this status word is command dependent";
+                    default: return "the interpretation of this status word is command dependent";
                 }
                 // break;
+            case 0x68:
+                switch(sw2) {
+                    case 0x00: return "no information given";
+                    case 0x81: return "logical channel not supported";
+                    case 0x82: return "secure messaging not supported";
+                }
+                break;
+            case 0x69:
+                switch(sw2) {
+                    case 0x00: return "no information given";
+                    case 0x81: return "command incompatible with file structure";
+                    case 0x82: return "security status not satisfied";
+                    case 0x83: return "authentication/PIN method blocked";
+                    case 0x84: return "referenced data invalidated";
+                    case 0x85: return "conditions of use not satisfied";
+                    case 0x86: return "command not allowed (no EF selected)";
+                    case 0x89: return "command not allowed - secure channel -"
+                            + " security not satisfied";
+                }
+                break;
+            case 0x6A:
+                switch(sw2) {
+                    case 0x80: return "incorrect parameters in the data field";
+                    case 0x81: return "function not supported";
+                    case 0x82: return "file not found";
+                    case 0x83: return "record not found";
+                    case 0x84: return "not enough memory space";
+                    case 0x86: return "incorrect parameters P1 to P2";
+                    case 0x87: return "lc inconsistent with P1 to P2";
+                    case 0x88: return "referenced data not found";
+                }
+                break;
             case 0x6B: return "incorrect parameter P1 or P2";
+            case 0x6C: return "wrong length, retry with " + sw2;
             case 0x6D: return "unknown instruction code given in the command";
             case 0x6E: return "wrong instruction class given in the command";
             case 0x6F:
                 switch(sw2) {
                     case 0x00: return "technical problem with no diagnostic given";
-                    default: return "The interpretation of this status word is command dependent";
+                    default: return "the interpretation of this status word is command dependent";
                 }
                 // break;
-            case 0x68:
-                switch(sw2) {
-                    case 0x00: return "No information given";
-                    case 0x81: return "Logical channel not supported";
-                    case 0x82: return "Secure messaging not supported";
-                }
-                break;
-            case 0x69:
-                switch(sw2) {
-                    case 0x00: return "No information given";
-                    case 0x81: return "Command incompatible with file structure";
-                    case 0x82: return "Security status not satisfied";
-                    case 0x83: return "Authentication/PIN method blocked";
-                    case 0x84: return "Referenced data invalidated";
-                    case 0x85: return "Conditions of use not satisfied";
-                    case 0x86: return "Command not allowed (no EF selected)";
-                    case 0x89: return "Command not allowed - secure channel -"
-                               + " security not satisfied";
-                }
-                break;
-            case 0x6A:
-                switch(sw2) {
-                    case 0x80: return "Incorrect parameters in the data field";
-                    case 0x81: return "Function not supported";
-                    case 0x82: return "File not found";
-                    case 0x83: return "Record not found";
-                    case 0x84: return "Not enough memory space";
-                    case 0x86: return "Incorrect parameters P1 to P2";
-                    case 0x87: return "Lc inconsistent with P1 to P2";
-                    case 0x88: return "Referenced data not found";
-                }
-                break;
             case 0x90: return null; // success
             case 0x91: return null; // success
             //Status Code 0x92 has contradictory meanings from 11.11 and 102.221 10.2.1.1
@@ -129,9 +134,8 @@
                 break;
             case 0x93:
                 switch(sw2) {
-                    case 0x00:
-                        return "SIM Application Toolkit is busy. Command cannot be executed"
-                            + " at present, further normal commands are allowed.";
+                    case 0x00: return "SIM Application Toolkit is busy. Command cannot be executed"
+                            + " at present, further normal commands are allowed";
                 }
                 break;
             case 0x94:
@@ -153,7 +157,7 @@
                     case 0x10: return "in contradiction with invalidation status";
                     case 0x40: return "unsuccessful CHV verification, no attempt left/"
                             + "unsuccessful UNBLOCK CHV verification, no attempt left/"
-                            + "CHV blocked"
+                            + "CHV blocked/"
                             + "UNBLOCK CHV blocked";
                     case 0x50: return "increase cannot be performed, Max value reached";
                     // The definition for these status codes can be found in TS 31.102 7.3.1
@@ -198,7 +202,7 @@
                 + " sw2:0x"
                 + Integer.toHexString(sw2)
                 + " Payload: "
-                + (Build.IS_DEBUGGABLE ? IccUtils.bytesToHexString(payload) : "*******")
+                + (TelephonyUtils.IS_DEBUGGABLE ? IccUtils.bytesToHexString(payload) : "*******")
                 + ((!success()) ? " Error: " + getErrorString() : "");
     }
 
diff --git a/src/java/com/android/internal/telephony/uicc/IccRecords.java b/src/java/com/android/internal/telephony/uicc/IccRecords.java
index 7944f6f..b33e899 100644
--- a/src/java/com/android/internal/telephony/uicc/IccRecords.java
+++ b/src/java/com/android/internal/telephony/uicc/IccRecords.java
@@ -18,21 +18,23 @@
 
 import android.annotation.IntDef;
 import android.annotation.Nullable;
-import android.annotation.UnsupportedAppUsage;
 import android.content.Context;
 import android.os.AsyncResult;
 import android.os.Handler;
 import android.os.Message;
 import android.os.Registrant;
 import android.os.RegistrantList;
-import android.telephony.Rlog;
+import android.os.SystemClock;
 import android.telephony.SubscriptionInfo;
 import android.telephony.TelephonyManager;
 import android.text.TextUtils;
+import android.util.Pair;
 
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.telephony.CommandsInterface;
 import com.android.internal.telephony.MccTable;
-import com.android.internal.util.ArrayUtils;
+import com.android.internal.telephony.util.ArrayUtils;
+import com.android.telephony.Rlog;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
@@ -77,35 +79,26 @@
     };
 
     // ***** Instance Variables
-    @UnsupportedAppUsage
     protected AtomicBoolean mDestroyed = new AtomicBoolean(false);
     protected AtomicBoolean mLoaded = new AtomicBoolean(false);
-    @UnsupportedAppUsage
     protected Context mContext;
-    @UnsupportedAppUsage
     protected CommandsInterface mCi;
-    @UnsupportedAppUsage
     protected IccFileHandler mFh;
-    @UnsupportedAppUsage
     protected UiccCardApplication mParentApp;
-    @UnsupportedAppUsage
     protected TelephonyManager mTelephonyManager;
 
     protected RegistrantList mRecordsLoadedRegistrants = new RegistrantList();
     protected RegistrantList mLockedRecordsLoadedRegistrants = new RegistrantList();
     protected RegistrantList mNetworkLockedRecordsLoadedRegistrants = new RegistrantList();
     protected RegistrantList mImsiReadyRegistrants = new RegistrantList();
-    @UnsupportedAppUsage
     protected RegistrantList mRecordsEventsRegistrants = new RegistrantList();
     protected RegistrantList mNewSmsRegistrants = new RegistrantList();
     protected RegistrantList mNetworkSelectionModeAutomaticRegistrants = new RegistrantList();
     protected RegistrantList mSpnUpdatedRegistrants = new RegistrantList();
     protected RegistrantList mRecordsOverrideRegistrants = new RegistrantList();
 
-    @UnsupportedAppUsage
     protected int mRecordsToLoad;  // number of pending load requests
 
-    @UnsupportedAppUsage
     protected AdnRecordCache mAdnCache;
 
     // ***** Cached SIM State; cleared on channel close
@@ -120,34 +113,28 @@
     protected boolean mRecordsRequested = false; // true if we've made requests for the sim records
     protected int mLockedRecordsReqReason = LOCKED_RECORDS_REQ_REASON_NONE;
 
-    @UnsupportedAppUsage
-    protected String mIccId;  // Includes only decimals (no hex)
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PROTECTED)
+    public String mIccId;  // Includes only decimals (no hex)
 
     protected String mFullIccId;  // Includes hex characters in ICCID
     protected String mMsisdn = null;  // My mobile number
     protected String mMsisdnTag = null;
     protected String mNewMsisdn = null;
     protected String mNewMsisdnTag = null;
-    @UnsupportedAppUsage
     protected String mVoiceMailNum = null;
     protected String mVoiceMailTag = null;
     protected String mNewVoiceMailNum = null;
     protected String mNewVoiceMailTag = null;
-    @UnsupportedAppUsage
     protected boolean mIsVoiceMailFixed = false;
-    @UnsupportedAppUsage
     protected String mImsi; // IMSI must be only valid numeric characters 0-9 without padding 'f's
-    @UnsupportedAppUsage
-    private IccIoResult auth_rsp;
 
-    @UnsupportedAppUsage
     protected int mMncLength = UNINITIALIZED;
     protected int mMailboxIndex = 0; // 0 is no mailbox dailing number associated
 
-    @UnsupportedAppUsage
+    protected int mSmsCountOnIcc = 0;
+
     private String mSpn;
 
-    @UnsupportedAppUsage
     protected String mGid1;
     protected String mGid2;
 
@@ -171,9 +158,6 @@
     protected String[] mEhplmns;
     protected String[] mFplmns;
 
-    @UnsupportedAppUsage
-    private final Object mLock = new Object();
-
     CarrierTestOverride mCarrierTestOverride;
 
     //Arbitrary offset for the Handler
@@ -181,8 +165,7 @@
     protected static final int HANDLER_ACTION_NONE = HANDLER_ACTION_BASE + 0;
     protected static final int HANDLER_ACTION_SEND_RESPONSE = HANDLER_ACTION_BASE + 1;
     protected static AtomicInteger sNextRequestId = new AtomicInteger(1);
-    protected final HashMap<Integer, Message> mPendingResponses = new HashMap<>();
-
+    protected final HashMap<Integer, Pair<Message, Object>> mPendingTransactions = new HashMap<>();
     // ***** Constants
 
     // Markers for mncLength
@@ -214,8 +197,14 @@
 
     public static final int EVENT_GET_ICC_RECORD_DONE = 100;
     public static final int EVENT_REFRESH = 31; // ICC refresh occurred
-    protected static final int EVENT_APP_READY = 1;
-    private static final int EVENT_AKA_AUTHENTICATE_DONE          = 90;
+    private static final int EVENT_AKA_AUTHENTICATE_DONE = 90;
+    protected static final int EVENT_GET_SMS_RECORD_SIZE_DONE = 28;
+
+    protected static final int SYSTEM_EVENT_BASE = 0x100;
+    protected static final int EVENT_APP_READY = 1 + SYSTEM_EVENT_BASE;
+    protected static final int EVENT_APP_LOCKED = 2 + SYSTEM_EVENT_BASE;
+    protected static final int EVENT_APP_NETWORK_LOCKED = 3 + SYSTEM_EVENT_BASE;
+    protected static final int EVENT_APP_DETECTED = 4 + SYSTEM_EVENT_BASE;
 
     public static final int CALL_FORWARDING_STATUS_DISABLED = 0;
     public static final int CALL_FORWARDING_STATUS_ENABLED = 1;
@@ -224,6 +213,20 @@
     public static final int DEFAULT_VOICE_MESSAGE_COUNT = -2;
     public static final int UNKNOWN_VOICE_MESSAGE_COUNT = -1;
 
+    // Maximum time in millisecond to wait for a IccSim Challenge before assuming it will not
+    // arrive and returning null to the callers.
+    private static final long ICC_SIM_CHALLENGE_TIMEOUT_MILLIS = 2500;
+
+    /**
+     * There are two purposes for this class. First, each instance of AuthAsyncResponse acts as a
+     * lock to for calling thead to wait in getIccSimChallengeResponse(). Second, pass the IMS
+     * authentication response to the getIccSimChallengeResponse().
+     */
+    private static class AuthAsyncResponse {
+        public IccIoResult authRsp;
+        public Throwable exception;
+    }
+
     @Override
     public String toString() {
         String iccIdToPrint = SubscriptionInfo.givePrintableIccid(mFullIccId);
@@ -283,6 +286,11 @@
 
         mCarrierTestOverride = new CarrierTestOverride();
         mCi.registerForIccRefresh(this, EVENT_REFRESH, null);
+
+        mParentApp.registerForReady(this, EVENT_APP_READY, null);
+        mParentApp.registerForDetected(this, EVENT_APP_DETECTED, null);
+        mParentApp.registerForLocked(this, EVENT_APP_LOCKED, null);
+        mParentApp.registerForNetworkLocked(this, EVENT_APP_NETWORK_LOCKED, null);
     }
 
     // Override IccRecords for testing
@@ -300,14 +308,12 @@
     public void dispose() {
         mDestroyed.set(true);
 
-        // It is possible that there is another thread waiting for the response
-        // to requestIccSimAuthentication() in getIccSimChallengeResponse().
-        auth_rsp = null;
-        synchronized (mLock) {
-            mLock.notifyAll();
-        }
-
         mCi.unregisterForIccRefresh(this);
+        mParentApp.unregisterForReady(this);
+        mParentApp.unregisterForDetected(this);
+        mParentApp.unregisterForLocked(this);
+        mParentApp.unregisterForNetworkLocked(this);
+
         mParentApp = null;
         mFh = null;
         mCi = null;
@@ -318,7 +324,18 @@
         mLoaded.set(false);
     }
 
-    public abstract void onReady();
+    protected abstract void onReady();
+
+    protected void onDetected() {
+        mRecordsRequested = false;
+        mLoaded.set(false);
+    }
+
+    protected void onLocked() {
+        // The LOADED state should not be indicated while the lock is effective.
+        mRecordsRequested = false;
+        mLoaded.set(false);
+    }
 
     //***** Public Methods
     public AdnRecordCache getAdnCache() {
@@ -326,24 +343,42 @@
     }
 
     /**
-     * Adds a message to the pending requests list by generating a unique
-     * (integer) hash key and returning it. The message should never be null.
+     * Adds a message to the pending requests list by generating a unique (integer)
+     * hash key and returning it. The message should never be null.
+     *
+     * @param msg Message of the transaction to be stored
+     * @return the unique (integer) hash key to retrieve the pending transaction
      */
-    public int storePendingResponseMessage(Message msg) {
+    public int storePendingTransaction(Message msg) {
+        return storePendingTransaction(msg, null);
+    }
+
+    /**
+     * Adds a message and obj pair to the pending requests list by generating a unique (integer)
+     * hash key and returning it. The message should never be null.
+     *
+     * @param msg Message of the transaction to be stored
+     * @param obj Object of the transaction to be stored
+     * @return the unique (integer) hash key to retrieve the pending transaction
+     */
+    public int storePendingTransaction(Message msg, Object obj) {
         int key = sNextRequestId.getAndIncrement();
-        synchronized (mPendingResponses) {
-            mPendingResponses.put(key, msg);
+        Pair<Message, Object> pair = new Pair<Message, Object>(msg, obj);
+        synchronized (mPendingTransactions) {
+            mPendingTransactions.put(key, pair);
         }
         return key;
     }
 
     /**
-     * Returns the pending request, if any or null
+     * Returns the pending transaction and free it from memory, if any or null
+     *
+     * @param key key of the entry to retrieve
+     * @return The pending transaction.
      */
-    public Message retrievePendingResponseMessage(Integer key) {
-        Message m;
-        synchronized (mPendingResponses) {
-            return mPendingResponses.remove(key);
+    public Pair<Message, Object> retrievePendingTransaction(Integer key) {
+        synchronized (mPendingTransactions) {
+            return mPendingTransactions.remove(key);
         }
     }
 
@@ -353,13 +388,14 @@
      * hex digits.
      * @return ICC ID without hex digits
      */
-    @UnsupportedAppUsage
     public String getIccId() {
-        if (mCarrierTestOverride.isInTestMode() && mCarrierTestOverride.getFakeIccid() != null) {
-            return mCarrierTestOverride.getFakeIccid();
-        } else {
-            return mIccId;
+        if (mCarrierTestOverride.isInTestMode()) {
+            String fakeIccId = mCarrierTestOverride.getFakeIccid();
+            if (fakeIccId != null) {
+                return fakeIccId;
+            }
         }
+        return mIccId;
     }
 
     /**
@@ -370,7 +406,6 @@
         return mFullIccId;
     }
 
-    @UnsupportedAppUsage
     public void registerForRecordsLoaded(Handler h, int what, Object obj) {
         if (mDestroyed.get()) {
             return;
@@ -383,7 +418,7 @@
             r.notifyRegistrant(new AsyncResult(null, null, null));
         }
     }
-    @UnsupportedAppUsage
+
     public void unregisterForRecordsLoaded(Handler h) {
         mRecordsLoadedRegistrants.remove(h);
     }
@@ -483,7 +518,6 @@
         mSpnUpdatedRegistrants.remove(h);
     }
 
-    @UnsupportedAppUsage
     public void registerForRecordsEvents(Handler h, int what, Object obj) {
         Registrant r = new Registrant (h, what, obj);
         mRecordsEventsRegistrants.add(r);
@@ -493,28 +527,25 @@
         r.notifyResult(EVENT_MWI);
         r.notifyResult(EVENT_CFI);
     }
-    @UnsupportedAppUsage
+
     public void unregisterForRecordsEvents(Handler h) {
         mRecordsEventsRegistrants.remove(h);
     }
 
-    @UnsupportedAppUsage
     public void registerForNewSms(Handler h, int what, Object obj) {
         Registrant r = new Registrant (h, what, obj);
         mNewSmsRegistrants.add(r);
     }
-    @UnsupportedAppUsage
+
     public void unregisterForNewSms(Handler h) {
         mNewSmsRegistrants.remove(h);
     }
 
-    @UnsupportedAppUsage
     public void registerForNetworkSelectionModeAutomatic(
             Handler h, int what, Object obj) {
         Registrant r = new Registrant (h, what, obj);
         mNetworkSelectionModeAutomaticRegistrants.add(r);
     }
-    @UnsupportedAppUsage
     public void unregisterForNetworkSelectionModeAutomatic(Handler h) {
         mNetworkSelectionModeAutomaticRegistrants.remove(h);
     }
@@ -526,13 +557,14 @@
      *
      * @return null if SIM is not yet ready or unavailable
      */
-    @UnsupportedAppUsage
     public String getIMSI() {
-        if (mCarrierTestOverride.isInTestMode() && mCarrierTestOverride.getFakeIMSI() != null) {
-            return mCarrierTestOverride.getFakeIMSI();
-        } else {
-            return mImsi;
+        if (mCarrierTestOverride.isInTestMode()) {
+            String fakeImsi = mCarrierTestOverride.getFakeIMSI();
+            if (fakeImsi != null) {
+                return fakeImsi;
+            }
         }
+        return mImsi;
     }
 
     /**
@@ -621,7 +653,6 @@
         return null;
     }
 
-    @UnsupportedAppUsage
     public String getMsisdnNumber() {
         return mMsisdn;
     }
@@ -630,13 +661,14 @@
      * Get the Group Identifier Level 1 (GID1) on a SIM for GSM.
      * @return null if SIM is not yet ready
      */
-    @UnsupportedAppUsage
     public String getGid1() {
-        if (mCarrierTestOverride.isInTestMode() && mCarrierTestOverride.getFakeGid1() != null) {
-            return mCarrierTestOverride.getFakeGid1();
-        } else {
-            return mGid1;
+        if (mCarrierTestOverride.isInTestMode()) {
+            String fakeGid1 = mCarrierTestOverride.getFakeGid1();
+            if (fakeGid1 != null) {
+                return fakeGid1;
+            }
         }
+        return mGid1;
     }
 
     /**
@@ -644,11 +676,13 @@
      * @return null if SIM is not yet ready
      */
     public String getGid2() {
-        if (mCarrierTestOverride.isInTestMode() && mCarrierTestOverride.getFakeGid2() != null) {
-            return mCarrierTestOverride.getFakeGid2();
-        } else {
-            return mGid2;
+        if (mCarrierTestOverride.isInTestMode()) {
+            String fakeGid2 = mCarrierTestOverride.getFakeGid2();
+            if (fakeGid2 != null) {
+                return fakeGid2;
+            }
         }
+        return mGid2;
     }
 
     /**
@@ -656,15 +690,15 @@
      * @return null if SIM is not yet ready
      */
     public String getPnnHomeName() {
-        if (mCarrierTestOverride.isInTestMode()
-                && mCarrierTestOverride.getFakePnnHomeName() != null) {
-            return mCarrierTestOverride.getFakePnnHomeName();
-        } else {
-            return mPnnHomeName;
+        if (mCarrierTestOverride.isInTestMode()) {
+            String fakePnnHomeName = mCarrierTestOverride.getFakePnnHomeName();
+            if (fakePnnHomeName != null) {
+                return fakePnnHomeName;
+            }
         }
+        return mPnnHomeName;
     }
 
-    @UnsupportedAppUsage
     public void setMsisdnNumber(String alphaTag, String number,
             Message onComplete) {
         loge("setMsisdn() should not be invoked on base IccRecords");
@@ -687,10 +721,12 @@
      *
      * @return null if SIM is not yet ready or no RUIM entry
      */
-    @UnsupportedAppUsage
     public String getServiceProviderName() {
-        if (mCarrierTestOverride.isInTestMode() && mCarrierTestOverride.getFakeSpn() != null) {
-            return mCarrierTestOverride.getFakeSpn();
+        if (mCarrierTestOverride.isInTestMode()) {
+            String fakeSpn = mCarrierTestOverride.getFakeSpn();
+            if (fakeSpn != null) {
+                return fakeSpn;
+            }
         }
         return mSpn;
     }
@@ -771,7 +807,6 @@
      */
     public abstract void onRefresh(boolean fileChanged, int[] fileList);
 
-    @UnsupportedAppUsage
     public boolean getRecordsLoaded() {
         return mRecordsToLoad == 0 && mRecordsRequested;
     }
@@ -792,6 +827,26 @@
         AsyncResult ar;
 
         switch (msg.what) {
+            case EVENT_APP_READY:
+                mLockedRecordsReqReason = LOCKED_RECORDS_REQ_REASON_NONE;
+                onReady();
+                break;
+
+            case EVENT_APP_DETECTED:
+                mLockedRecordsReqReason = LOCKED_RECORDS_REQ_REASON_NONE;
+                onDetected();
+                break;
+
+            case EVENT_APP_LOCKED:
+                mLockedRecordsReqReason = LOCKED_RECORDS_REQ_REASON_LOCKED;
+                onLocked();
+                break;
+
+            case EVENT_APP_NETWORK_LOCKED:
+                mLockedRecordsReqReason = LOCKED_RECORDS_REQ_REASON_NETWORK_LOCKED;
+                onLocked();
+                break;
+
             case EVENT_GET_ICC_RECORD_DONE:
                 try {
                     ar = (AsyncResult) msg.obj;
@@ -823,25 +878,59 @@
                 break;
 
             case EVENT_AKA_AUTHENTICATE_DONE:
-                ar = (AsyncResult)msg.obj;
-                auth_rsp = null;
+                ar = (AsyncResult) msg.obj;
+                AuthAsyncResponse rsp = (AuthAsyncResponse) ar.userObj;
                 if (DBG) log("EVENT_AKA_AUTHENTICATE_DONE");
-                if (ar.exception != null) {
-                    loge("Exception ICC SIM AKA: " + ar.exception);
-                } else {
-                    try {
-                        auth_rsp = (IccIoResult)ar.result;
-                        if (DBG) log("ICC SIM AKA: auth_rsp = " + auth_rsp);
-                    } catch (Exception e) {
-                        loge("Failed to parse ICC SIM AKA contents: " + e);
+
+                synchronized (rsp) {
+                    if (ar.exception != null) {
+                        rsp.exception = ar.exception;
+                        loge("Exception ICC SIM AKA: " + ar.exception);
+                    } else if (ar.result == null) {
+                        rsp.exception = new NullPointerException(
+                                "Null SIM authentication response");
+                        loge("EVENT_AKA_AUTHENTICATE_DONE: null response");
+                    } else {
+                        try {
+                            rsp.authRsp = (IccIoResult) ar.result;
+                            if (VDBG) log("ICC SIM AKA: authRsp = " + rsp.authRsp);
+                        } catch (ClassCastException e) {
+                            rsp.exception = e;
+                            loge("Failed to parse ICC SIM AKA contents: " + e);
+                        }
                     }
-                }
-                synchronized (mLock) {
-                    mLock.notifyAll();
+                    rsp.notifyAll();
                 }
 
                 break;
 
+            case EVENT_GET_SMS_RECORD_SIZE_DONE:
+                ar = (AsyncResult) msg.obj;
+
+                if (ar.exception != null) {
+                    onRecordLoaded();
+                    loge("Exception in EVENT_GET_SMS_RECORD_SIZE_DONE " + ar.exception);
+                    break;
+                }
+
+                int[] recordSize = (int[])ar.result;
+                try {
+                    // recordSize[0]  is the record length
+                    // recordSize[1]  is the total length of the EF file
+                    // recordSize[2]  is the number of records in the EF file
+                    mSmsCountOnIcc = recordSize[2];
+                    log("EVENT_GET_SMS_RECORD_SIZE_DONE Size " + recordSize[0]
+                            + " total " + recordSize[1]
+                                    + " record " + recordSize[2]);
+                } catch (ArrayIndexOutOfBoundsException exc) {
+                    mSmsCountOnIcc = -1;
+                    loge("ArrayIndexOutOfBoundsException in EVENT_GET_SMS_RECORD_SIZE_DONE: "
+                            + exc.toString());
+                } finally {
+                    onRecordLoaded();
+                }
+                break;
+
             default:
                 super.handleMessage(msg);
         }
@@ -892,7 +981,6 @@
 
     protected abstract void handleFileUpdate(int efid);
 
-    @UnsupportedAppUsage
     protected void handleRefresh(IccRefreshResponse refreshResponse){
         if (refreshResponse == null) {
             if (DBG) log("handleRefresh received without input");
@@ -977,6 +1065,8 @@
         if (!ArrayUtils.isEmpty(spdi)) {
             hplmns = ArrayUtils.concatElements(String.class, hplmns, spdi);
         }
+        // If hplmns don't contain hplmn, we need to add hplmn to hplmns
+        hplmns = ArrayUtils.appendElement(String.class, hplmns, hplmn);
         return hplmns;
     }
 
@@ -995,7 +1085,6 @@
      * or is not valid for the type of IccCard. Generally used for
      * GSM/UMTS and the like SIMS
      */
-    @UnsupportedAppUsage
     public String getOperatorNumeric() {
         return null;
     }
@@ -1016,7 +1105,6 @@
      * @param enable
      * @param number to which CFU is enabled
      */
-    @UnsupportedAppUsage
     public void setVoiceCallForwardingFlag(int line, boolean enable, String number) {
     }
 
@@ -1044,7 +1132,6 @@
      *
      * @param s is the string to write
      */
-    @UnsupportedAppUsage
     protected abstract void log(String s);
 
     /**
@@ -1081,66 +1168,73 @@
         return null;
     }
 
-    @UnsupportedAppUsage
     public UsimServiceTable getUsimServiceTable() {
         return null;
     }
 
-    protected void setSystemProperty(String key, String val) {
-        TelephonyManager.getDefault().setTelephonyProperty(mParentApp.getPhoneId(), key, val);
-
-        log("[key, value]=" + key + ", " +  val);
-    }
-
     /**
+     * Solve authentication leakage issue. See b/147463955.
      * Returns the response of the SIM application on the UICC to authentication
      * challenge/response algorithm. The data string and challenge response are
      * Base64 encoded Strings.
      * Can support EAP-SIM, EAP-AKA with results encoded per 3GPP TS 31.102.
      *
-     * @param authContext parameter P2 that specifies the authentication context per 3GPP TS 31.102 (Section 7.1.2)
+     * @param authContext parameter P2 that specifies the authentication context
+     * per 3GPP TS 31.102 (Section 7.1.2)
      * @param data authentication challenge data
      * @return challenge response
      */
-    @UnsupportedAppUsage
+    @Nullable
     public String getIccSimChallengeResponse(int authContext, String data) {
-        if (DBG) log("getIccSimChallengeResponse:");
+        if (VDBG) log("getIccSimChallengeResponse:");
 
-        try {
-            synchronized(mLock) {
-                CommandsInterface ci = mCi;
-                UiccCardApplication parentApp = mParentApp;
-                if (ci != null && parentApp != null) {
-                    ci.requestIccSimAuthentication(authContext, data,
-                            parentApp.getAid(),
-                            obtainMessage(EVENT_AKA_AUTHENTICATE_DONE));
-                    try {
-                        mLock.wait();
-                    } catch (InterruptedException e) {
-                        loge("getIccSimChallengeResponse: Fail, interrupted"
-                                + " while trying to request Icc Sim Auth");
-                        return null;
-                    }
-                } else {
-                    loge( "getIccSimChallengeResponse: "
-                            + "Fail, ci or parentApp is null");
-                    return null;
+        //final here is for defensive copy.
+        final CommandsInterface ci = mCi;
+        final UiccCardApplication parentApp = mParentApp;
+        if (ci == null || parentApp == null) {
+            loge("getIccSimChallengeResponse: Fail, ci or parentApp is null");
+            return null;
+        }
+
+        AuthAsyncResponse rsp = new AuthAsyncResponse();
+
+        synchronized (rsp) {
+            ci.requestIccSimAuthentication(authContext, data, parentApp.getAid(),
+                    obtainMessage(EVENT_AKA_AUTHENTICATE_DONE, 0, 0, rsp));
+            //TODO: factor wait with timeout into a separate method
+            final long startTime = SystemClock.elapsedRealtime();
+            do {
+                try {
+                    long sleepTime = startTime + ICC_SIM_CHALLENGE_TIMEOUT_MILLIS
+                            - SystemClock.elapsedRealtime();
+                    if (sleepTime > 0) rsp.wait(sleepTime);
+                } catch (InterruptedException e) {
+                    Rlog.w("IccRecords", "getIccSimChallengeResponse: InterruptedException.");
                 }
+            } while (SystemClock.elapsedRealtime() - startTime < ICC_SIM_CHALLENGE_TIMEOUT_MILLIS
+                    && rsp.authRsp == null && rsp.exception == null);
+
+            if (SystemClock.elapsedRealtime() - startTime >= ICC_SIM_CHALLENGE_TIMEOUT_MILLIS
+                    && rsp.authRsp == null && rsp.exception == null) {
+                loge("getIccSimChallengeResponse timeout!");
+                return null;
             }
-        } catch(Exception e) {
-            loge( "getIccSimChallengeResponse: "
-                    + "Fail while trying to request Icc Sim Auth");
-            return null;
+
+            if (rsp.exception != null) {
+                loge("getIccSimChallengeResponse exception: " + rsp.exception);
+                //TODO: propagate better exceptions up to the user now that we have them available
+                //in the call stack.
+                return null;
+            }
+
+            if (rsp.authRsp == null) {
+                loge("getIccSimChallengeResponse: No authentication response");
+                return null;
+            }
         }
+        if (VDBG) log("getIccSimChallengeResponse: return rsp.authRsp");
 
-        if (auth_rsp == null) {
-            loge("getIccSimChallengeResponse: No authentication response");
-            return null;
-        }
-
-        if (DBG) log("getIccSimChallengeResponse: return auth_rsp");
-
-        return android.util.Base64.encodeToString(auth_rsp.payload, android.util.Base64.NO_WRAP);
+        return rsp.authRsp.payload != null ? new String(rsp.authRsp.payload) : null;
     }
 
     /**
@@ -1181,6 +1275,14 @@
         return carrierNameDisplayCondition;
     }
 
+    /**
+     * Get SMS capacity count on ICC card.
+     */
+    public int getSmsCapacityOnIcc() {
+        if (DBG) log("getSmsCapacityOnIcc: " + mSmsCountOnIcc);
+        return mSmsCountOnIcc;
+    }
+
     public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
         pw.println("IccRecords: " + this);
         pw.println(" mDestroyed=" + mDestroyed);
diff --git a/src/java/com/android/internal/telephony/uicc/IccRefreshResponse.java b/src/java/com/android/internal/telephony/uicc/IccRefreshResponse.java
index 7d0f845..30fd36f 100644
--- a/src/java/com/android/internal/telephony/uicc/IccRefreshResponse.java
+++ b/src/java/com/android/internal/telephony/uicc/IccRefreshResponse.java
@@ -16,7 +16,7 @@
 
 package com.android.internal.telephony.uicc;
 
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
 
 /**
  * See also RIL_SimRefresh in include/telephony/ril.h
@@ -39,6 +39,9 @@
                                                   from 0xA0, 0x00 -> 0x41,
                                                   0x30, 0x30, 0x30 */
                                                /* Example: a0000000871002f310ffff89080000ff */
+    @UnsupportedAppUsage
+    public IccRefreshResponse() {
+    }
 
     @Override
     public String toString() {
diff --git a/src/java/com/android/internal/telephony/uicc/IccServiceTable.java b/src/java/com/android/internal/telephony/uicc/IccServiceTable.java
index b2e2f27..fe6e433 100644
--- a/src/java/com/android/internal/telephony/uicc/IccServiceTable.java
+++ b/src/java/com/android/internal/telephony/uicc/IccServiceTable.java
@@ -16,8 +16,9 @@
 
 package com.android.internal.telephony.uicc;
 
-import android.annotation.UnsupportedAppUsage;
-import android.telephony.Rlog;
+import android.compat.annotation.UnsupportedAppUsage;
+
+import com.android.telephony.Rlog;
 
 /**
  * Wrapper class for an ICC EF containing a bit field of enabled services.
diff --git a/src/java/com/android/internal/telephony/uicc/IsimFileHandler.java b/src/java/com/android/internal/telephony/uicc/IsimFileHandler.java
index 6fe16c9..fe900cb 100644
--- a/src/java/com/android/internal/telephony/uicc/IsimFileHandler.java
+++ b/src/java/com/android/internal/telephony/uicc/IsimFileHandler.java
@@ -16,9 +16,8 @@
 
 package com.android.internal.telephony.uicc;
 
-import android.telephony.Rlog;
-
 import com.android.internal.telephony.CommandsInterface;
+import com.android.telephony.Rlog;
 
 /**
  * {@hide}
diff --git a/src/java/com/android/internal/telephony/uicc/IsimRecords.java b/src/java/com/android/internal/telephony/uicc/IsimRecords.java
index 65cfd6f..3de7b3d 100644
--- a/src/java/com/android/internal/telephony/uicc/IsimRecords.java
+++ b/src/java/com/android/internal/telephony/uicc/IsimRecords.java
@@ -16,7 +16,7 @@
 
 package com.android.internal.telephony.uicc;
 
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
 
 /**
  * {@hide}
diff --git a/src/java/com/android/internal/telephony/uicc/IsimUiccRecords.java b/src/java/com/android/internal/telephony/uicc/IsimUiccRecords.java
index f75849d..b44eda0 100644
--- a/src/java/com/android/internal/telephony/uicc/IsimUiccRecords.java
+++ b/src/java/com/android/internal/telephony/uicc/IsimUiccRecords.java
@@ -16,15 +16,16 @@
 
 package com.android.internal.telephony.uicc;
 
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
 import android.content.Context;
 import android.content.Intent;
 import android.os.AsyncResult;
 import android.os.Message;
-import android.telephony.Rlog;
+import android.telephony.SubscriptionManager;
 
 import com.android.internal.telephony.CommandsInterface;
 import com.android.internal.telephony.gsm.SimTlv;
+import com.android.telephony.Rlog;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
@@ -88,17 +89,12 @@
         mRecordsToLoad = 0;
         // Start off by setting empty state
         resetRecords();
-
-        mParentApp.registerForReady(this, EVENT_APP_READY, null);
         if (DBG) log("IsimUiccRecords X ctor this=" + this);
     }
 
     @Override
     public void dispose() {
         log("Disposing " + this);
-        //Unregister for all events
-        mCi.unregisterForIccRefresh(this);
-        mParentApp.unregisterForReady(this);
         resetRecords();
         super.dispose();
     }
@@ -116,10 +112,6 @@
 
         try {
             switch (msg.what) {
-                case EVENT_APP_READY:
-                    onReady();
-                    break;
-
                 case EVENT_REFRESH:
                     broadcastRefresh();
                     super.handleMessage(msg);
@@ -352,6 +344,7 @@
                 mRecordsToLoad++;
 
             default:
+                mLoaded.set(false);
                 fetchIsimRecords();
                 break;
         }
@@ -360,6 +353,7 @@
     private void broadcastRefresh() {
         Intent intent = new Intent(INTENT_ISIM_REFRESH);
         log("send ISim REFRESH: " + INTENT_ISIM_REFRESH);
+        SubscriptionManager.putPhoneIdAndSubIdExtra(intent, mParentApp.getPhoneId());
         mContext.sendBroadcast(intent);
     }
 
@@ -441,12 +435,20 @@
     @UnsupportedAppUsage
     @Override
     protected void log(String s) {
-        if (DBG) Rlog.d(LOG_TAG, "[ISIM] " + s);
+        if (mParentApp != null) {
+            Rlog.d(LOG_TAG, "[ISIM-" + mParentApp.getPhoneId() + "] " + s);
+        } else {
+            Rlog.d(LOG_TAG, "[ISIM] " + s);
+        }
     }
 
     @Override
     protected void loge(String s) {
-        if (DBG) Rlog.e(LOG_TAG, "[ISIM] " + s);
+        if (mParentApp != null) {
+            Rlog.e(LOG_TAG, "[ISIM-" + mParentApp.getPhoneId() + "] " + s);
+        } else {
+            Rlog.e(LOG_TAG, "[ISIM] " + s);
+        }
     }
 
     @Override
diff --git a/src/java/com/android/internal/telephony/uicc/PlmnActRecord.java b/src/java/com/android/internal/telephony/uicc/PlmnActRecord.java
index 4d0b484..61352d9 100755
--- a/src/java/com/android/internal/telephony/uicc/PlmnActRecord.java
+++ b/src/java/com/android/internal/telephony/uicc/PlmnActRecord.java
@@ -19,7 +19,8 @@
 import android.annotation.IntDef;
 import android.os.Parcel;
 import android.os.Parcelable;
-import android.telephony.Rlog;
+
+import com.android.telephony.Rlog;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
diff --git a/src/java/com/android/internal/telephony/uicc/RuimFileHandler.java b/src/java/com/android/internal/telephony/uicc/RuimFileHandler.java
index 58e939f..2323b5a 100644
--- a/src/java/com/android/internal/telephony/uicc/RuimFileHandler.java
+++ b/src/java/com/android/internal/telephony/uicc/RuimFileHandler.java
@@ -17,9 +17,9 @@
 package com.android.internal.telephony.uicc;
 
 import android.os.*;
-import android.telephony.Rlog;
 
 import com.android.internal.telephony.CommandsInterface;
+import com.android.telephony.Rlog;
 
 /**
  * {@hide}
diff --git a/src/java/com/android/internal/telephony/uicc/RuimRecords.java b/src/java/com/android/internal/telephony/uicc/RuimRecords.java
index ff4aad8..28ce2d2 100644
--- a/src/java/com/android/internal/telephony/uicc/RuimRecords.java
+++ b/src/java/com/android/internal/telephony/uicc/RuimRecords.java
@@ -16,15 +16,12 @@
 
 package com.android.internal.telephony.uicc;
 
-import static com.android.internal.telephony.TelephonyProperties.PROPERTY_TEST_CSIM;
-
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
 import android.content.Context;
 import android.content.res.Resources;
 import android.os.AsyncResult;
 import android.os.Message;
-import android.os.SystemProperties;
-import android.telephony.Rlog;
+import android.sysprop.TelephonyProperties;
 import android.telephony.SubscriptionInfo;
 import android.telephony.SubscriptionManager;
 import android.text.TextUtils;
@@ -37,6 +34,7 @@
 import com.android.internal.telephony.cdma.sms.UserData;
 import com.android.internal.telephony.uicc.IccCardApplicationStatus.AppType;
 import com.android.internal.util.BitwiseInputStream;
+import com.android.telephony.Rlog;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
@@ -119,20 +117,12 @@
 
         // Start off by setting empty state
         resetRecords();
-
-        mParentApp.registerForReady(this, EVENT_APP_READY, null);
-        mParentApp.registerForLocked(this, EVENT_APP_LOCKED, null);
-        mParentApp.registerForNetworkLocked(this, EVENT_APP_NETWORK_LOCKED, null);
         if (DBG) log("RuimRecords X ctor this=" + this);
     }
 
     @Override
     public void dispose() {
         if (DBG) log("Disposing RuimRecords " + this);
-        //Unregister for all events
-        mParentApp.unregisterForReady(this);
-        mParentApp.unregisterForLocked(this);
-        mParentApp.unregisterForNetworkLocked(this);
         resetRecords();
         super.dispose();
     }
@@ -334,7 +324,7 @@
                     // SPN is checked to have characters in printable ASCII
                     // range. If not, they are decoded with
                     // ENCODING_GSM_7BIT_ALPHABET scheme.
-                    if (TextUtils.isPrintableAsciiOnly(spn)) {
+                    if (isPrintableAsciiOnly(spn)) {
                         setServiceProviderName(spn);
                     } else {
                         if (DBG) log("Some corruption in SPN decoding = " + spn);
@@ -359,6 +349,22 @@
         }
     }
 
+    private static boolean isPrintableAsciiOnly(final CharSequence str) {
+        final int len = str.length();
+        for (int i = 0; i < len; i++) {
+            if (!isPrintableAscii(str.charAt(i))) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    private static boolean isPrintableAscii(final char c) {
+        final int asciiFirst = 0x20;
+        final int asciiLast = 0x7E;  // included
+        return (asciiFirst <= c && c <= asciiLast) || c == '\r' || c == '\n';
+    }
+
     private class EfCsimMdnLoaded implements IccRecordLoaded {
         @Override
         public String getEfName() {
@@ -613,15 +619,6 @@
 
         try {
             switch (msg.what) {
-            case EVENT_APP_READY:
-                onReady();
-                break;
-
-                case EVENT_APP_LOCKED:
-                case EVENT_APP_NETWORK_LOCKED:
-                    onLocked(msg.what);
-                    break;
-
             case EVENT_GET_DEVICE_IDENTITY_DONE:
                 log("Event EVENT_GET_DEVICE_IDENTITY_DONE Received");
             break;
@@ -837,10 +834,10 @@
         mCi.getCDMASubscription(obtainMessage(EVENT_GET_CDMA_SUBSCRIPTION_DONE));
     }
 
-    private void onLocked(int msg) {
+    @Override
+    protected void onLocked() {
         if (DBG) log("only fetch EF_ICCID in locked state");
-        mLockedRecordsReqReason = msg == EVENT_APP_LOCKED ? LOCKED_RECORDS_REQ_REASON_LOCKED :
-                LOCKED_RECORDS_REQ_REASON_NETWORK_LOCKED;
+        super.onLocked();
 
         mFh.loadEFTransparent(EF_ICCID, obtainMessage(EVENT_GET_ICCID_DONE));
         mRecordsToLoad++;
@@ -892,6 +889,8 @@
         mFh.loadEFTransparent(EF_CSIM_MIPUPP,
                 obtainMessage(EVENT_GET_ICC_RECORD_DONE, new EfCsimMipUppLoaded()));
         mRecordsToLoad++;
+        mFh.getEFLinearRecordSize(EF_SMS, obtainMessage(EVENT_GET_SMS_RECORD_SIZE_DONE));
+        mRecordsToLoad++;
 
         if (DBG) log("fetchRuimRecords " + mRecordsToLoad + " requested: " + mRecordsRequested);
         // Further records that can be inserted are Operator/OEM dependent
@@ -903,9 +902,9 @@
         // to determine if the SIM is provisioned.  Otherwise,
         // consider the SIM is provisioned. (for case of ordinal
         // USIM only UICC.)
-        // If PROPERTY_TEST_CSIM is defined, bypess provision check
-        // and consider the SIM is provisioned.
-        if (SystemProperties.getBoolean(PROPERTY_TEST_CSIM, false)) {
+        // If test_csim is true, bypess provision check and
+        // consider the SIM is provisioned.
+        if (TelephonyProperties.test_csim().orElse(false)) {
             return true;
         }
 
@@ -937,6 +936,7 @@
 
     @Override
     protected void handleFileUpdate(int efid) {
+        mLoaded.set(false);
         mAdnCache.reset();
         fetchRuimRecords();
     }
@@ -965,13 +965,21 @@
     @UnsupportedAppUsage
     @Override
     protected void log(String s) {
-        Rlog.d(LOG_TAG, "[RuimRecords] " + s);
+        if (mParentApp != null) {
+            Rlog.d(LOG_TAG, "[RuimRecords-" + mParentApp.getPhoneId() + "] " + s);
+        } else {
+            Rlog.d(LOG_TAG, "[RuimRecords] " + s);
+        }
     }
 
     @UnsupportedAppUsage
     @Override
     protected void loge(String s) {
-        Rlog.e(LOG_TAG, "[RuimRecords] " + s);
+        if (mParentApp != null) {
+            Rlog.e(LOG_TAG, "[RuimRecords-" + mParentApp.getPhoneId() + "] " + s);
+        } else {
+            Rlog.e(LOG_TAG, "[RuimRecords] " + s);
+        }
     }
 
     @Override
diff --git a/src/java/com/android/internal/telephony/uicc/SIMFileHandler.java b/src/java/com/android/internal/telephony/uicc/SIMFileHandler.java
index 3be0c99..be165c1 100644
--- a/src/java/com/android/internal/telephony/uicc/SIMFileHandler.java
+++ b/src/java/com/android/internal/telephony/uicc/SIMFileHandler.java
@@ -16,9 +16,8 @@
 
 package com.android.internal.telephony.uicc;
 
-import android.telephony.Rlog;
-
 import com.android.internal.telephony.CommandsInterface;
+import com.android.telephony.Rlog;
 
 /**
  * {@hide}
diff --git a/src/java/com/android/internal/telephony/uicc/SIMRecords.java b/src/java/com/android/internal/telephony/uicc/SIMRecords.java
old mode 100755
new mode 100644
index 4f5f370..1118045
--- a/src/java/com/android/internal/telephony/uicc/SIMRecords.java
+++ b/src/java/com/android/internal/telephony/uicc/SIMRecords.java
@@ -16,7 +16,10 @@
 
 package com.android.internal.telephony.uicc;
 
-import android.annotation.UnsupportedAppUsage;
+import static android.telephony.SmsManager.STATUS_ON_ICC_READ;
+import static android.telephony.SmsManager.STATUS_ON_ICC_UNREAD;
+
+import android.compat.annotation.UnsupportedAppUsage;
 import android.content.Context;
 import android.content.res.Resources;
 import android.os.AsyncResult;
@@ -24,10 +27,10 @@
 import android.os.PersistableBundle;
 import android.telephony.CarrierConfigManager;
 import android.telephony.PhoneNumberUtils;
-import android.telephony.Rlog;
 import android.telephony.SmsMessage;
 import android.telephony.SubscriptionInfo;
 import android.text.TextUtils;
+import android.util.Pair;
 
 import com.android.internal.telephony.CommandsInterface;
 import com.android.internal.telephony.MccTable;
@@ -35,6 +38,7 @@
 import com.android.internal.telephony.SubscriptionController;
 import com.android.internal.telephony.gsm.SimTlv;
 import com.android.internal.telephony.uicc.IccCardApplicationStatus.AppType;
+import com.android.telephony.Rlog;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
@@ -135,6 +139,10 @@
     private static final int CFIS_ADN_CAPABILITY_ID_OFFSET = 14;
     private static final int CFIS_ADN_EXTENSION_ID_OFFSET = 15;
 
+    // 3GPP specification constants
+    // Spec reference TS 31.102 section 4.2.16
+    private static final int FPLMN_BYTE_SIZE = 3;
+
     // ***** Event Constants
     private static final int SIM_RECORD_EVENT_BASE = 0x00;
     private static final int EVENT_GET_IMSI_DONE = 3 + SIM_RECORD_EVENT_BASE;
@@ -169,13 +177,8 @@
     private static final int EVENT_GET_HPLMN_W_ACT_DONE = 39 + SIM_RECORD_EVENT_BASE;
     private static final int EVENT_GET_EHPLMN_DONE = 40 + SIM_RECORD_EVENT_BASE;
     private static final int EVENT_GET_FPLMN_DONE = 41 + SIM_RECORD_EVENT_BASE;
-
-    // TODO: Possibly move these to IccRecords.java
-    private static final int SYSTEM_EVENT_BASE = 0x100;
-    private static final int EVENT_APP_LOCKED = 2 + SYSTEM_EVENT_BASE;
-    private static final int EVENT_APP_NETWORK_LOCKED = 3 + SYSTEM_EVENT_BASE;
-
-
+    private static final int EVENT_GET_FPLMN_SIZE_DONE = 42 + SIM_RECORD_EVENT_BASE;
+    private static final int EVENT_SET_FPLMN_DONE = 43 + SIM_RECORD_EVENT_BASE;
     // ***** Constructor
 
     public SIMRecords(UiccCardApplication app, Context c, CommandsInterface ci) {
@@ -195,9 +198,6 @@
 
         // Start off by setting empty state
         resetRecords();
-        mParentApp.registerForReady(this, EVENT_APP_READY, null);
-        mParentApp.registerForLocked(this, EVENT_APP_LOCKED, null);
-        mParentApp.registerForNetworkLocked(this, EVENT_APP_NETWORK_LOCKED, null);
         if (DBG) log("SIMRecords X ctor this=" + this);
     }
 
@@ -206,9 +206,6 @@
         if (DBG) log("Disposing SIMRecords this=" + this);
         //Unregister for all events
         mCi.unSetOnSmsOnSim(this);
-        mParentApp.unregisterForReady(this);
-        mParentApp.unregisterForLocked(this);
-        mParentApp.unregisterForNetworkLocked(this);
         resetRecords();
         super.dispose();
     }
@@ -627,15 +624,6 @@
 
         try {
             switch (msg.what) {
-                case EVENT_APP_READY:
-                    onReady();
-                    break;
-
-                case EVENT_APP_LOCKED:
-                case EVENT_APP_NETWORK_LOCKED:
-                    onLocked(msg.what);
-                    break;
-
                 /* IO events */
                 case EVENT_GET_IMSI_DONE:
                     isRecordLoadResponse = true;
@@ -944,7 +932,7 @@
                     break;
 
                 case EVENT_MARK_SMS_READ_DONE:
-                    Rlog.i("ENF", "marked read: sms " + msg.arg1);
+                    log("marked read: sms " + msg.arg1);
                     break;
 
 
@@ -1208,17 +1196,21 @@
                     data = (byte[]) ar.result;
                     if (ar.exception != null || data == null) {
                         loge("Failed getting Forbidden PLMNs: " + ar.exception);
-                        break;
                     } else {
                         mFplmns = parseBcdPlmnList(data, "Forbidden");
                     }
                     if (msg.arg1 == HANDLER_ACTION_SEND_RESPONSE) {
                         if (VDBG) logv("getForbiddenPlmns(): send async response");
                         isRecordLoadResponse = false;
-                        Message response = retrievePendingResponseMessage(msg.arg2);
+                        int key = msg.arg2;
+                        Message response = retrievePendingTransaction(key).first;
                         if (response != null) {
-                            AsyncResult.forMessage(
-                                    response, Arrays.copyOf(mFplmns, mFplmns.length), null);
+                            if (ar.exception == null && data != null && mFplmns != null) {
+                                AsyncResult.forMessage(response, Arrays.copyOf(mFplmns,
+                                        mFplmns.length), null);
+                            } else {
+                                AsyncResult.forMessage(response, null, ar.exception);
+                            }
                             response.sendToTarget();
                         } else {
                             loge("Failed to retrieve a response message for FPLMN");
@@ -1227,6 +1219,60 @@
                     }
                     break;
 
+                case EVENT_GET_FPLMN_SIZE_DONE:
+                    ar = (AsyncResult) msg.obj;
+                    if (ar.exception != null) {
+                        Message response = (Message) ar.userObj;
+                        AsyncResult.forMessage(response).exception = ar.exception;
+                        response.sendToTarget();
+                        break;
+                    }
+                    int key = msg.arg2;
+                    Pair<Message, Object> transaction = retrievePendingTransaction(key);
+                    Message response = transaction.first;
+                    List<String> fplmns = (List<String>) transaction.second;
+                    int dataLength = (int) ar.result;
+                    if (dataLength < 0 || dataLength % FPLMN_BYTE_SIZE != 0) {
+                        loge("Failed to retrieve a correct fplmn size: " + dataLength);
+                        AsyncResult.forMessage(response, -1, null);
+                        response.sendToTarget();
+                        break;
+                    }
+
+                    int maxWritebaleFplmns = dataLength / FPLMN_BYTE_SIZE;
+                    List<String> fplmnsToWrite;
+                    if (fplmns.size() <= maxWritebaleFplmns) {
+                        fplmnsToWrite = fplmns;
+                    } else {
+                        fplmnsToWrite = fplmns.subList(0, maxWritebaleFplmns);
+                    }
+                    key = storePendingTransaction(response, fplmnsToWrite);
+                    byte[] encodededFplmns = IccUtils.encodeFplmns(fplmns, dataLength);
+                    mFh.updateEFTransparent(
+                            EF_FPLMN,
+                            encodededFplmns,
+                            obtainMessage(
+                                    EVENT_SET_FPLMN_DONE,
+                                    msg.arg1,
+                                    key));
+                    break;
+
+                case EVENT_SET_FPLMN_DONE:
+                    ar = (AsyncResult) msg.obj;
+                    if (ar.exception != null) {
+                        loge("Failed setting Forbidden PLMNs: " + ar.exception);
+                    } else {
+                        transaction = retrievePendingTransaction(msg.arg2);
+                        response = transaction.first;
+                        mFplmns = ((List<String>) transaction.second).toArray(new String[0]);
+                        if (msg.arg1 == HANDLER_ACTION_SEND_RESPONSE) {
+                            AsyncResult.forMessage(response, mFplmns.length, null);
+                            response.sendToTarget();
+                        }
+                        log("Successfully setted fplmns " + ar.result);
+                    }
+                    break;
+
                 default:
                     super.handleMessage(msg);   // IccRecords handles generic record load responses
             }
@@ -1302,6 +1348,7 @@
                 // For now, fetch all records if this is not a
                 // voicemail number.
                 // TODO: Handle other cases, instead of fetching all.
+                mLoaded.set(false);
                 mAdnCache.reset();
                 fetchSimRecords();
                 break;
@@ -1312,21 +1359,18 @@
      * Dispatch 3GPP format message to registrant ({@code GsmCdmaPhone}) to pass to the 3GPP SMS
      * dispatcher for delivery.
      */
-    private int dispatchGsmMessage(SmsMessage message) {
+    private void dispatchGsmMessage(SmsMessage message) {
         mNewSmsRegistrants.notifyResult(message);
-        return 0;
     }
 
     private void handleSms(byte[] ba) {
-        if (ba[0] != 0)
-            Rlog.d("ENF", "status : " + ba[0]);
+        if (DBG) log("handleSms status : " + ba[0]);
 
-        // 3GPP TS 51.011 v5.0.0 (20011-12)  10.5.3
-        // 3 == "received by MS from network; message to be read"
-        if (ba[0] == 3) {
+        // ba[0] is status byte. (see 3GPP TS 51.011 10.5.3)
+        if ((ba[0] & 0x07) == STATUS_ON_ICC_UNREAD) {
             int n = ba.length;
 
-            // Note: Data may include trailing FF's.  That's OK; message
+            // Note: Data may include trailing FF's. That's OK; message
             // should still parse correctly.
             byte[] pdu = new byte[n - 1];
             System.arraycopy(ba, 1, pdu, 0, n - 1);
@@ -1336,23 +1380,19 @@
         }
     }
 
-
     private void handleSmses(ArrayList<byte[]> messages) {
         int count = messages.size();
 
         for (int i = 0; i < count; i++) {
             byte[] ba = messages.get(i);
 
-            if (ba[0] != 0)
-                Rlog.i("ENF", "status " + i + ": " + ba[0]);
+            if (DBG) log("handleSmses status " + i + ": " + ba[0]);
 
-            // 3GPP TS 51.011 v5.0.0 (20011-12)  10.5.3
-            // 3 == "received by MS from network; message to be read"
-
-            if (ba[0] == 3) {
+            // ba[0] is status byte. (see 3GPP TS 51.011 10.5.3)
+            if ((ba[0] & 0x07) == STATUS_ON_ICC_UNREAD) {
                 int n = ba.length;
 
-                // Note: Data may include trailing FF's.  That's OK; message
+                // Note: Data may include trailing FF's. That's OK; message
                 // should still parse correctly.
                 byte[] pdu = new byte[n - 1];
                 System.arraycopy(ba, 1, pdu, 0, n - 1);
@@ -1360,10 +1400,7 @@
 
                 dispatchGsmMessage(message);
 
-                // 3GPP TS 51.011 v5.0.0 (20011-12)  10.5.3
-                // 1 == "received by MS from network; message read"
-
-                ba[0] = 1;
+                ba[0] = (byte) STATUS_ON_ICC_READ;
 
                 if (false) { // FIXME: writing seems to crash RdoServD
                     mFh.updateEFLinearFixed(EF_SMS,
@@ -1478,20 +1515,34 @@
      * in the result field of an AsyncResult in the response.obj.
      */
     public void getForbiddenPlmns(Message response) {
-        int key = storePendingResponseMessage(response);
+        int key = storePendingTransaction(response);
         mFh.loadEFTransparent(EF_FPLMN, obtainMessage(
                     EVENT_GET_FPLMN_DONE, HANDLER_ACTION_SEND_RESPONSE, key));
     }
 
+    /**
+     * Set the forbidden PLMNs on the sim
+     *
+     * @param response Response to be send back.
+     * @param fplmns List of fplmns to be written to SIM.
+     */
+    public void setForbiddenPlmns(Message response, List<String> fplmns) {
+        int key = storePendingTransaction(response, fplmns);
+        mFh.getEFTransparentRecordSize(
+                EF_FPLMN,
+                obtainMessage(EVENT_GET_FPLMN_SIZE_DONE, HANDLER_ACTION_SEND_RESPONSE, key));
+    }
+
+
     @Override
     public void onReady() {
         fetchSimRecords();
     }
 
-    private void onLocked(int msg) {
+    @Override
+    protected void onLocked() {
         if (DBG) log("only fetch EF_LI, EF_PL and EF_ICCID in locked state");
-        mLockedRecordsReqReason = msg == EVENT_APP_LOCKED ? LOCKED_RECORDS_REQ_REASON_LOCKED :
-                LOCKED_RECORDS_REQ_REASON_NETWORK_LOCKED;
+        super.onLocked();
 
         loadEfLiAndEfPl();
 
@@ -1602,6 +1653,8 @@
         mRecordsToLoad++;
 
         loadEfLiAndEfPl();
+        mFh.getEFLinearRecordSize(EF_SMS, obtainMessage(EVENT_GET_SMS_RECORD_SIZE_DONE));
+        mRecordsToLoad++;
 
         // XXX should seek instead of examining them all
         if (false) { // XXX
@@ -1633,6 +1686,8 @@
     /**
      * States of Get SPN Finite State Machine which only used by getSpnFsm()
      */
+    @UnsupportedAppUsage(implicitMember =
+            "values()[Lcom/android/internal/telephony/uicc/SIMRecords$GetSpnFsmState;")
     private enum GetSpnFsmState {
         IDLE,               // No initialized
         @UnsupportedAppUsage
@@ -1827,7 +1882,7 @@
                 tmpSpdi.add(plmnCode);
             }
         }
-        mSpdi = (String[]) tmpSpdi.toArray();
+        mSpdi = tmpSpdi.toArray(new String[tmpSpdi.size()]);
     }
 
     /**
@@ -1865,22 +1920,38 @@
     @UnsupportedAppUsage
     @Override
     protected void log(String s) {
-        Rlog.d(LOG_TAG, "[SIMRecords] " + s);
+        if (mParentApp != null) {
+            Rlog.d(LOG_TAG, "[SIMRecords-" + mParentApp.getPhoneId() + "] " + s);
+        } else {
+            Rlog.d(LOG_TAG, "[SIMRecords] " + s);
+        }
     }
 
     @UnsupportedAppUsage
     @Override
     protected void loge(String s) {
-        Rlog.e(LOG_TAG, "[SIMRecords] " + s);
+        if (mParentApp != null) {
+            Rlog.e(LOG_TAG, "[SIMRecords-" + mParentApp.getPhoneId() + "] " + s);
+        } else {
+            Rlog.e(LOG_TAG, "[SIMRecords] " + s);
+        }
     }
 
     protected void logw(String s, Throwable tr) {
-        Rlog.w(LOG_TAG, "[SIMRecords] " + s, tr);
+        if (mParentApp != null) {
+            Rlog.w(LOG_TAG, "[SIMRecords-" + mParentApp.getPhoneId() + "] " + s, tr);
+        } else {
+            Rlog.w(LOG_TAG, "[SIMRecords] " + s, tr);
+        }
     }
 
     @UnsupportedAppUsage
     protected void logv(String s) {
-        Rlog.v(LOG_TAG, "[SIMRecords] " + s);
+        if (mParentApp != null) {
+            Rlog.v(LOG_TAG, "[SIMRecords-" + mParentApp.getPhoneId() + "] " + s);
+        } else {
+            Rlog.v(LOG_TAG, "[SIMRecords] " + s);
+        }
     }
 
     /**
diff --git a/src/java/com/android/internal/telephony/uicc/ShowInstallAppNotificationReceiver.java b/src/java/com/android/internal/telephony/uicc/ShowInstallAppNotificationReceiver.java
index 6c89448..7891c7c 100644
--- a/src/java/com/android/internal/telephony/uicc/ShowInstallAppNotificationReceiver.java
+++ b/src/java/com/android/internal/telephony/uicc/ShowInstallAppNotificationReceiver.java
@@ -38,7 +38,7 @@
     public void onReceive(Context context, Intent intent) {
         String pkgName = intent.getStringExtra(EXTRA_PACKAGE_NAME);
 
-        if (!UiccProfile.isPackageInstalled(context, pkgName)) {
+        if (!UiccProfile.isPackageBundled(context, pkgName)) {
             InstallCarrierAppUtils.showNotification(context, pkgName);
             InstallCarrierAppUtils.registerPackageInstallReceiver(context);
         }
diff --git a/src/java/com/android/internal/telephony/uicc/UiccCard.java b/src/java/com/android/internal/telephony/uicc/UiccCard.java
index 985bfa8..73e26a9 100644
--- a/src/java/com/android/internal/telephony/uicc/UiccCard.java
+++ b/src/java/com/android/internal/telephony/uicc/UiccCard.java
@@ -16,7 +16,7 @@
 
 package com.android.internal.telephony.uicc;
 
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
 import android.content.Context;
 import android.content.Intent;
 import android.content.pm.PackageInfo;
@@ -24,7 +24,6 @@
 import android.content.pm.Signature;
 import android.os.Handler;
 import android.os.Message;
-import android.telephony.Rlog;
 import android.telephony.TelephonyManager;
 import android.text.TextUtils;
 
@@ -33,6 +32,7 @@
 import com.android.internal.telephony.uicc.IccCardApplicationStatus.AppType;
 import com.android.internal.telephony.uicc.IccCardStatus.CardState;
 import com.android.internal.telephony.uicc.IccCardStatus.PinState;
+import com.android.telephony.Rlog;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
diff --git a/src/java/com/android/internal/telephony/uicc/UiccCardApplication.java b/src/java/com/android/internal/telephony/uicc/UiccCardApplication.java
index a628dbd..6656507 100644
--- a/src/java/com/android/internal/telephony/uicc/UiccCardApplication.java
+++ b/src/java/com/android/internal/telephony/uicc/UiccCardApplication.java
@@ -16,14 +16,13 @@
 
 package com.android.internal.telephony.uicc;
 
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
 import android.content.Context;
 import android.os.AsyncResult;
 import android.os.Handler;
 import android.os.Message;
 import android.os.Registrant;
 import android.os.RegistrantList;
-import android.telephony.Rlog;
 
 import com.android.internal.telephony.CommandsInterface;
 import com.android.internal.telephony.PhoneConstants;
@@ -31,6 +30,7 @@
 import com.android.internal.telephony.uicc.IccCardApplicationStatus.AppType;
 import com.android.internal.telephony.uicc.IccCardApplicationStatus.PersoSubState;
 import com.android.internal.telephony.uicc.IccCardStatus.PinState;
+import com.android.telephony.Rlog;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
@@ -95,6 +95,7 @@
     private boolean mDestroyed;//set to true once this App is commanded to be disposed of.
 
     private RegistrantList mReadyRegistrants = new RegistrantList();
+    private RegistrantList mDetectedRegistrants = new RegistrantList();
     private RegistrantList mPinLockedRegistrants = new RegistrantList();
     private RegistrantList mNetworkLockedRegistrants = new RegistrantList();
 
@@ -141,6 +142,7 @@
             AppType oldAppType = mAppType;
             AppState oldAppState = mAppState;
             PersoSubState oldPersoSubState = mPersoSubState;
+            PinState oldPin1State = mPin1State;
             mAppType = as.app_type;
             mAuthContext = getAuthContext(mAppType);
             mAppState = as.app_state;
@@ -159,7 +161,7 @@
             }
 
             if (mPersoSubState != oldPersoSubState &&
-                    mPersoSubState == PersoSubState.PERSOSUBSTATE_SIM_NETWORK) {
+                    PersoSubState.isPersoLocked(mPersoSubState)) {
                 notifyNetworkLockedRegistrantsIfNeeded(null);
             }
 
@@ -173,6 +175,10 @@
                 }
                 notifyPinLockedRegistrantsIfNeeded(null);
                 notifyReadyRegistrantsIfNeeded(null);
+                notifyDetectedRegistrantsIfNeeded(null);
+            } else {
+                if (mPin1State != oldPin1State)
+                    queryPin1State();
             }
         }
     }
@@ -441,6 +447,20 @@
         }
     }
 
+    public void registerForDetected(Handler h, int what, Object obj) {
+        synchronized (mLock) {
+            Registrant r = new Registrant(h, what, obj);
+            mDetectedRegistrants.add(r);
+            notifyDetectedRegistrantsIfNeeded(r);
+        }
+    }
+
+    public void unregisterForDetected(Handler h) {
+        synchronized (mLock) {
+            mDetectedRegistrants.remove(h);
+        }
+    }
+
     /**
      * Notifies handler of any transition into State.isPinLocked()
      */
@@ -507,6 +527,26 @@
      *
      * @param r Registrant to be notified. If null - all registrants will be notified
      */
+    private void notifyDetectedRegistrantsIfNeeded(Registrant r) {
+        if (mDestroyed) {
+            return;
+        }
+        if (mAppState == AppState.APPSTATE_DETECTED) {
+            if (r == null) {
+                if (DBG) log("Notifying registrants: DETECTED");
+                mDetectedRegistrants.notifyRegistrants();
+            } else {
+                if (DBG) log("Notifying 1 registrant: DETECTED");
+                r.notifyRegistrant(new AsyncResult(null, null, null));
+            }
+        }
+    }
+
+    /**
+     * Notifies specified registrant, assume mLock is held.
+     *
+     * @param r Registrant to be notified. If null - all registrants will be notified
+     */
     private void notifyPinLockedRegistrantsIfNeeded(Registrant r) {
         if (mDestroyed) {
             return;
@@ -541,13 +581,14 @@
         }
 
         if (mAppState == AppState.APPSTATE_SUBSCRIPTION_PERSO &&
-                mPersoSubState == PersoSubState.PERSOSUBSTATE_SIM_NETWORK) {
+                PersoSubState.isPersoLocked(mPersoSubState)) {
+            AsyncResult ar = new AsyncResult(null, mPersoSubState.ordinal(), null);
             if (r == null) {
-                if (DBG) log("Notifying registrants: NETWORK_LOCKED");
-                mNetworkLockedRegistrants.notifyRegistrants();
+                if (DBG) log("Notifying registrants: NETWORK_LOCKED with mPersoSubState" + mPersoSubState);
+                mNetworkLockedRegistrants.notifyRegistrants(ar);
             } else {
-                if (DBG) log("Notifying 1 registrant: NETWORK_LOCED");
-                r.notifyRegistrant(new AsyncResult(null, null, null));
+                if (DBG) log("Notifying 1 registrant: NETWORK_LOCKED with mPersoSubState" + mPersoSubState);
+                r.notifyRegistrant(ar);
             }
         }
     }
@@ -718,6 +759,14 @@
         }
     }
 
+    public void supplySimDepersonalization(PersoSubState persoType,
+                                           String pin, Message onComplete) {
+        synchronized (mLock) {
+            if (DBG) log("supplySimDepersonalization");
+            mCi.supplySimDepersonalization(persoType, pin, onComplete);
+        }
+    }
+
     /**
      * Check whether ICC pin lock is enabled
      * This is a sync call which returns the cached pin enabled state
diff --git a/src/java/com/android/internal/telephony/uicc/UiccCarrierPrivilegeRules.java b/src/java/com/android/internal/telephony/uicc/UiccCarrierPrivilegeRules.java
index 55718cb..e1bc0b7 100644
--- a/src/java/com/android/internal/telephony/uicc/UiccCarrierPrivilegeRules.java
+++ b/src/java/com/android/internal/telephony/uicc/UiccCarrierPrivilegeRules.java
@@ -17,7 +17,7 @@
 package com.android.internal.telephony.uicc;
 
 import android.annotation.Nullable;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
 import android.content.Intent;
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
@@ -27,13 +27,13 @@
 import android.os.Binder;
 import android.os.Handler;
 import android.os.Message;
-import android.telephony.Rlog;
 import android.telephony.TelephonyManager;
 import android.telephony.UiccAccessRule;
 import android.text.TextUtils;
 import android.util.LocalLog;
 
 import com.android.internal.telephony.CommandException;
+import com.android.telephony.Rlog;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
diff --git a/src/java/com/android/internal/telephony/uicc/UiccController.java b/src/java/com/android/internal/telephony/uicc/UiccController.java
index abe6791..767005d 100644
--- a/src/java/com/android/internal/telephony/uicc/UiccController.java
+++ b/src/java/com/android/internal/telephony/uicc/UiccController.java
@@ -19,8 +19,10 @@
 import static android.telephony.TelephonyManager.UNINITIALIZED_CARD_ID;
 import static android.telephony.TelephonyManager.UNSUPPORTED_CARD_ID;
 
-import android.annotation.UnsupportedAppUsage;
+import static java.util.Arrays.copyOf;
+
 import android.app.BroadcastOptions;
+import android.compat.annotation.UnsupportedAppUsage;
 import android.content.Context;
 import android.content.Intent;
 import android.content.SharedPreferences;
@@ -32,8 +34,9 @@
 import android.os.RegistrantList;
 import android.os.storage.StorageManager;
 import android.preference.PreferenceManager;
+import android.sysprop.TelephonyProperties;
 import android.telephony.CarrierConfigManager;
-import android.telephony.Rlog;
+import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyManager;
 import android.telephony.UiccCardInfo;
 import android.text.TextUtils;
@@ -43,11 +46,14 @@
 import com.android.internal.telephony.CommandException;
 import com.android.internal.telephony.CommandsInterface;
 import com.android.internal.telephony.IccCardConstants;
+import com.android.internal.telephony.PhoneConfigurationManager;
 import com.android.internal.telephony.PhoneConstants;
 import com.android.internal.telephony.PhoneFactory;
 import com.android.internal.telephony.RadioConfig;
 import com.android.internal.telephony.SubscriptionInfoUpdater;
 import com.android.internal.telephony.uicc.euicc.EuiccCard;
+import com.android.internal.telephony.util.TelephonyUtils;
+import com.android.telephony.Rlog;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
@@ -121,6 +127,8 @@
     private static final int EVENT_RADIO_UNAVAILABLE = 7;
     private static final int EVENT_SIM_REFRESH = 8;
     private static final int EVENT_EID_READY = 9;
+    private static final int EVENT_MULTI_SIM_CONFIG_CHANGED = 10;
+    // NOTE: any new EVENT_* values must be added to eventToString.
 
     // this needs to be here, because on bootup we dont know which index maps to which UiccSlot
     @UnsupportedAppUsage
@@ -156,6 +164,15 @@
     // SharedPreference key for saving the known card strings (ICCIDs and EIDs) ordered by card ID
     private static final String CARD_STRINGS = "card_strings";
 
+    // Whether the device has an eUICC built in.
+    private boolean mHasBuiltInEuicc = false;
+
+    // Whether the device has a currently active built in eUICC
+    private boolean mHasActiveBuiltInEuicc = false;
+
+    // The physical slots which correspond to built-in eUICCs
+    private final int[] mEuiccSlots;
+
     // SharedPreferences key for saving the default euicc card ID
     private static final String DEFAULT_CARD = "default_card";
 
@@ -163,7 +180,8 @@
     private static final Object mLock = new Object();
     @UnsupportedAppUsage
     private static UiccController mInstance;
-    private static ArrayList<IccSlotStatus> sLastSlotStatus;
+    @VisibleForTesting
+    public static ArrayList<IccSlotStatus> sLastSlotStatus;
 
     @UnsupportedAppUsage
     @VisibleForTesting
@@ -175,30 +193,31 @@
     private RadioConfig mRadioConfig;
 
     // LocalLog buffer to hold important SIM related events for debugging
-    static LocalLog sLocalLog = new LocalLog(100);
+    private static LocalLog sLocalLog = new LocalLog(TelephonyUtils.IS_DEBUGGABLE ? 250 : 100);
 
-    public static UiccController make(Context c, CommandsInterface[] ci) {
+    /**
+     * API to make UiccController singleton if not already created.
+     */
+    public static UiccController make(Context c) {
         synchronized (mLock) {
             if (mInstance != null) {
                 throw new RuntimeException("UiccController.make() should only be called once");
             }
-            mInstance = new UiccController(c, ci);
+            mInstance = new UiccController(c);
             return mInstance;
         }
     }
 
-    private UiccController(Context c, CommandsInterface []ci) {
+    private UiccController(Context c) {
         if (DBG) log("Creating UiccController");
         mContext = c;
-        mCis = ci;
-        if (DBG) {
-            String logStr = "config_num_physical_slots = " + c.getResources().getInteger(
-                    com.android.internal.R.integer.config_num_physical_slots);
-            log(logStr);
-            sLocalLog.log(logStr);
-        }
+        mCis = PhoneFactory.getCommandsInterfaces();
         int numPhysicalSlots = c.getResources().getInteger(
                 com.android.internal.R.integer.config_num_physical_slots);
+        numPhysicalSlots = TelephonyProperties.sim_slots_count().orElse(numPhysicalSlots);
+        if (DBG) {
+            logWithLocalLog("config_num_physical_slots = " + numPhysicalSlots);
+        }
         // Minimum number of physical slot count should be equals to or greater than phone count,
         // if it is less than phone count use phone count as physical slot count.
         if (numPhysicalSlots < mCis.length) {
@@ -206,7 +225,7 @@
         }
 
         mUiccSlots = new UiccSlot[numPhysicalSlots];
-        mPhoneIdToSlotId = new int[ci.length];
+        mPhoneIdToSlotId = new int[mCis.length];
         Arrays.fill(mPhoneIdToSlotId, INVALID_SLOT_ID);
         if (VDBG) logPhoneIdToSlotIdMapping();
         mRadioConfig = RadioConfig.getInstance(mContext);
@@ -214,16 +233,12 @@
         for (int i = 0; i < mCis.length; i++) {
             mCis[i].registerForIccStatusChanged(this, EVENT_ICC_STATUS_CHANGED, i);
 
-            // TODO remove this once modem correctly notifies the unsols
-            // If the device is unencrypted or has been decrypted or FBE is supported,
-            // i.e. not in CryptKeeper bounce, read SIM when radio state is available.
-            // Else wait for radio to be on. This is needed for the scenario when SIM is locked --
-            // to avoid overlap of CryptKeeper and SIM unlock screen.
             if (!StorageManager.inCryptKeeperBounce()) {
                 mCis[i].registerForAvailable(this, EVENT_RADIO_AVAILABLE, i);
             } else {
                 mCis[i].registerForOn(this, EVENT_RADIO_ON, i);
             }
+
             mCis[i].registerForNotAvailable(this, EVENT_RADIO_UNAVAILABLE, i);
             mCis[i].registerForIccRefresh(this, EVENT_SIM_REFRESH, i);
         }
@@ -231,6 +246,13 @@
         mLauncher = new UiccStateChangedLauncher(c, this);
         mCardStrings = loadCardStrings();
         mDefaultEuiccCardId = UNINITIALIZED_CARD_ID;
+
+        mEuiccSlots = mContext.getResources()
+                .getIntArray(com.android.internal.R.array.non_removable_euicc_slots);
+        mHasBuiltInEuicc = hasBuiltInEuicc();
+
+        PhoneConfigurationManager.registerForMultiSimConfigChange(
+                this, EVENT_MULTI_SIM_CONFIG_CHANGED, null);
     }
 
     /**
@@ -338,6 +360,7 @@
 
     /** Map logicalSlot to physicalSlot, and activate the physicalSlot if it is inactive. */
     public void switchSlots(int[] physicalSlots, Message response) {
+        logWithLocalLog("switchSlots: " + Arrays.toString(physicalSlots));
         mRadioConfig.setSimSlotsMapping(physicalSlots, response);
     }
 
@@ -426,12 +449,11 @@
     @UnsupportedAppUsage
     public void registerForIccChanged(Handler h, int what, Object obj) {
         synchronized (mLock) {
-            Registrant r = new Registrant (h, what, obj);
-            mIccChangedRegistrants.add(r);
-            //Notify registrant right after registering, so that it will get the latest ICC status,
-            //otherwise which may not happen until there is an actual change in ICC status.
-            r.notifyRegistrant();
+            mIccChangedRegistrants.addUnique(h, what, obj);
         }
+        //Notify registrant right after registering, so that it will get the latest ICC status,
+        //otherwise which may not happen until there is an actual change in ICC status.
+        Message.obtain(h, what, new AsyncResult(obj, null, null)).sendToTarget();
     }
 
     public void unregisterForIccChanged(Handler h) {
@@ -444,14 +466,15 @@
     public void handleMessage (Message msg) {
         synchronized (mLock) {
             Integer phoneId = getCiIndex(msg);
+            String eventName = eventToString(msg.what);
 
             if (phoneId < 0 || phoneId >= mCis.length) {
                 Rlog.e(LOG_TAG, "Invalid phoneId : " + phoneId + " received with event "
-                        + msg.what);
+                        + eventName);
                 return;
             }
 
-            sLocalLog.log("handleMessage: Received " + msg.what + " for phoneId " + phoneId);
+            logWithLocalLog("handleMessage: Received " + eventName + " for phoneId " + phoneId);
 
             AsyncResult ar = (AsyncResult)msg.obj;
             switch (msg.what) {
@@ -505,6 +528,11 @@
                     if (DBG) log("Received EVENT_EID_READY");
                     onEidReady(ar, phoneId);
                     break;
+                case EVENT_MULTI_SIM_CONFIG_CHANGED:
+                    if (DBG) log("Received EVENT_MULTI_SIM_CONFIG_CHANGED");
+                    int activeModemCount = (int) ((AsyncResult) msg.obj).result;
+                    onMultiSimConfigChanged(activeModemCount);
+                    break;
                 default:
                     Rlog.e(LOG_TAG, " Unknown Event " + msg.what);
                     break;
@@ -512,9 +540,42 @@
         }
     }
 
+    private void onMultiSimConfigChanged(int newActiveModemCount) {
+        int prevActiveModemCount = mCis.length;
+        mCis = PhoneFactory.getCommandsInterfaces();
+
+        logWithLocalLog("onMultiSimConfigChanged: prevActiveModemCount " + prevActiveModemCount
+                + ", newActiveModemCount " + newActiveModemCount);
+
+        // Resize array.
+        mPhoneIdToSlotId = copyOf(mPhoneIdToSlotId, newActiveModemCount);
+
+        // Register for new active modem for ss -> ds switch.
+        // For ds -> ss switch, there's no need to unregister as the mCis should unregister
+        // everything itself.
+        for (int i = prevActiveModemCount; i < newActiveModemCount; i++) {
+            mPhoneIdToSlotId[i] = INVALID_SLOT_ID;
+            mCis[i].registerForIccStatusChanged(this, EVENT_ICC_STATUS_CHANGED, i);
+
+            /*
+             * To support FDE (deprecated), additional check is needed:
+             *
+             * if (!StorageManager.inCryptKeeperBounce()) {
+             *     mCis[i].registerForAvailable(this, EVENT_RADIO_AVAILABLE, i);
+             * } else {
+             *     mCis[i].registerForOn(this, EVENT_RADIO_ON, i);
+             * }
+             */
+            mCis[i].registerForAvailable(this, EVENT_RADIO_AVAILABLE, i);
+
+            mCis[i].registerForNotAvailable(this, EVENT_RADIO_UNAVAILABLE, i);
+            mCis[i].registerForIccRefresh(this, EVENT_SIM_REFRESH, i);
+        }
+    }
+
     private Integer getCiIndex(Message msg) {
         AsyncResult ar;
-        Integer index = new Integer(PhoneConstants.DEFAULT_CARD_INDEX);
+        Integer index = new Integer(PhoneConstants.DEFAULT_SLOT_INDEX);
 
         /*
          * The events can be come in two ways. By explicitly sending it using
@@ -534,6 +595,22 @@
         return index;
     }
 
+    private static String eventToString(int event) {
+        switch (event) {
+            case EVENT_ICC_STATUS_CHANGED: return "ICC_STATUS_CHANGED";
+            case EVENT_SLOT_STATUS_CHANGED: return "SLOT_STATUS_CHANGED";
+            case EVENT_GET_ICC_STATUS_DONE: return "GET_ICC_STATUS_DONE";
+            case EVENT_GET_SLOT_STATUS_DONE: return "GET_SLOT_STATUS_DONE";
+            case EVENT_RADIO_ON: return "RADIO_ON";
+            case EVENT_RADIO_AVAILABLE: return "RADIO_AVAILABLE";
+            case EVENT_RADIO_UNAVAILABLE: return "RADIO_UNAVAILABLE";
+            case EVENT_SIM_REFRESH: return "SIM_REFRESH";
+            case EVENT_EID_READY: return "EID_READY";
+            case EVENT_MULTI_SIM_CONFIG_CHANGED: return "MULTI_SIM_CONFIG_CHANGED";
+            default: return "UNKNOWN(" + event + ")";
+        }
+    }
+
     // Easy to use API
     @UnsupportedAppUsage
     public UiccCardApplication getUiccCardApplication(int phoneId, int family) {
@@ -562,23 +639,33 @@
         }
     }
 
-    static void updateInternalIccState(Context context, IccCardConstants.State state, String reason,
-            int phoneId) {
-        updateInternalIccState(context, state, reason, phoneId, false);
+    static void updateInternalIccStateForInactiveSlot(
+            Context context, int prevActivePhoneId, String iccId) {
+        if (SubscriptionManager.isValidPhoneId(prevActivePhoneId)) {
+            // Mark SIM state as ABSENT on previously phoneId.
+            TelephonyManager telephonyManager = (TelephonyManager) context.getSystemService(
+                    Context.TELEPHONY_SERVICE);
+            telephonyManager.setSimStateForPhone(prevActivePhoneId,
+                    IccCardConstants.State.ABSENT.toString());
+        }
+
+        SubscriptionInfoUpdater subInfoUpdator = PhoneFactory.getSubscriptionInfoUpdater();
+        if (subInfoUpdator != null) {
+            subInfoUpdator.updateInternalIccStateForInactiveSlot(prevActivePhoneId, iccId);
+        } else {
+            Rlog.e(LOG_TAG, "subInfoUpdate is null.");
+        }
     }
 
-    // absentAndInactive is a special case when we need to update subscriptions but don't want to
-    // broadcast a state change
     static void updateInternalIccState(Context context, IccCardConstants.State state, String reason,
-            int phoneId, boolean absentAndInactive) {
+            int phoneId) {
         TelephonyManager telephonyManager = (TelephonyManager) context.getSystemService(
                 Context.TELEPHONY_SERVICE);
         telephonyManager.setSimStateForPhone(phoneId, state.toString());
 
         SubscriptionInfoUpdater subInfoUpdator = PhoneFactory.getSubscriptionInfoUpdater();
         if (subInfoUpdator != null) {
-            subInfoUpdator.updateInternalIccState(getIccStateIntentString(state),
-                    reason, phoneId, absentAndInactive);
+            subInfoUpdator.updateInternalIccState(getIccStateIntentString(state), reason, phoneId);
         } else {
             Rlog.e(LOG_TAG, "subInfoUpdate is null.");
         }
@@ -598,7 +685,7 @@
 
         IccCardStatus status = (IccCardStatus)ar.result;
 
-        sLocalLog.log("onGetIccCardStatusDone: phoneId " + index + " IccCardStatus: " + status);
+        logWithLocalLog("onGetIccCardStatusDone: phoneId " + index + " IccCardStatus: " + status);
 
         int slotId = status.physicalSlotIndex;
         if (VDBG) log("onGetIccCardStatusDone: phoneId " + index + " physicalSlotIndex " + slotId);
@@ -640,17 +727,30 @@
             cardString = card.getIccId();
         }
 
-        // EID may be unpopulated if RadioConfig<1.2
-        // If so, just register for EID loaded and skip this stuff
-        if (isEuicc && cardString == null
-                && mDefaultEuiccCardId != UNSUPPORTED_CARD_ID) {
-            ((EuiccCard) card).registerForEidReady(this, EVENT_EID_READY, index);
-        }
-
         if (cardString != null) {
             addCardId(cardString);
         }
 
+        // EID is unpopulated if Radio HAL < 1.4 (RadioConfig < 1.2)
+        // If so, just register for EID loaded and skip this stuff
+        if (isEuicc && mDefaultEuiccCardId != UNSUPPORTED_CARD_ID) {
+            if (cardString == null) {
+                ((EuiccCard) card).registerForEidReady(this, EVENT_EID_READY, index);
+            } else {
+                // If we know the EID from IccCardStatus, just use it to set mDefaultEuiccCardId if
+                // it's not already set.
+                // This is needed in cases where slot status doesn't include EID, and we don't want
+                // to register for EID from APDU because we already know cardString from a previous
+                // APDU
+                if (mDefaultEuiccCardId == UNINITIALIZED_CARD_ID
+                        || mDefaultEuiccCardId == TEMPORARILY_UNSUPPORTED_CARD_ID) {
+                    mDefaultEuiccCardId = convertToPublicCardId(cardString);
+                    logWithLocalLog("IccCardStatus eid=" + cardString + " slot=" + slotId
+                            + " mDefaultEuiccCardId=" + mDefaultEuiccCardId);
+                }
+            }
+        }
+
         if (DBG) log("Notifying IccChangedRegistrants");
         mIccChangedRegistrants.notifyRegistrants(new AsyncResult(null, index, null));
     }
@@ -682,6 +782,19 @@
     }
 
     /**
+     * Converts an integer cardId (public card ID) to a card string.
+     * @param cardId to convert
+     * @return cardString, or null if the cardId is not valid
+     */
+    public String convertToCardString(int cardId) {
+        if (cardId < 0 || cardId >= mCardStrings.size()) {
+            log("convertToCardString: cardId " + cardId + " is not valid");
+            return null;
+        }
+        return mCardStrings.get(cardId);
+    }
+
+    /**
      * Converts the card string (the ICCID/EID, formerly named card ID) to the public int cardId.
      * If the given cardString is an ICCID, trailing Fs will be automatically stripped before trying
      * to match to a card ID.
@@ -784,20 +897,15 @@
         }
         Throwable e = ar.exception;
         if (e != null) {
-            String logStr;
             if (!(e instanceof CommandException) || ((CommandException) e).getCommandError()
                     != CommandException.Error.REQUEST_NOT_SUPPORTED) {
                 // this is not expected; there should be no exception other than
                 // REQUEST_NOT_SUPPORTED
-                logStr = "Unexpected error getting slot status: " + ar.exception;
-                Rlog.e(LOG_TAG, logStr);
-                sLocalLog.log(logStr);
+                logeWithLocalLog("Unexpected error getting slot status: " + ar.exception);
             } else {
                 // REQUEST_NOT_SUPPORTED
-                logStr = "onGetSlotStatusDone: request not supported; marking "
-                        + "mIsSlotStatusSupported to false";
-                log(logStr);
-                sLocalLog.log(logStr);
+                logWithLocalLog("onGetSlotStatusDone: request not supported; marking "
+                        + "mIsSlotStatusSupported to false");
                 mIsSlotStatusSupported = false;
             }
             return;
@@ -809,14 +917,23 @@
             log("onGetSlotStatusDone: No change in slot status");
             return;
         }
+        logWithLocalLog("onGetSlotStatusDone: " + status);
 
         sLastSlotStatus = status;
 
         int numActiveSlots = 0;
         boolean isDefaultEuiccCardIdSet = false;
         boolean anyEuiccIsActive = false;
-        boolean hasEuicc = false;
-        for (int i = 0; i < status.size(); i++) {
+        mHasActiveBuiltInEuicc = false;
+
+        int numSlots = status.size();
+        if (mUiccSlots.length < numSlots) {
+            logeWithLocalLog("The number of the physical slots reported " + numSlots
+                    + " is greater than the expectation " + mUiccSlots.length);
+            numSlots = mUiccSlots.length;
+        }
+
+        for (int i = 0; i < numSlots; i++) {
             IccSlotStatus iss = status.get(i);
             boolean isActive = (iss.slotState == IccSlotStatus.SlotState.SLOTSTATE_ACTIVE);
             if (isActive) {
@@ -826,7 +943,6 @@
                 if (!isValidPhoneIndex(iss.logicalSlotIndex)) {
                     Rlog.e(LOG_TAG, "Skipping slot " + i + " as phone " + iss.logicalSlotIndex
                                + " is not available to communicate with this slot");
-
                 } else {
                     mPhoneIdToSlotId[iss.logicalSlotIndex] = i;
                 }
@@ -847,9 +963,12 @@
             }
 
             if (mUiccSlots[i].isEuicc()) {
-                hasEuicc = true;
                 if (isActive) {
                     anyEuiccIsActive = true;
+
+                    if (isBuiltInEuiccSlot(i)) {
+                        mHasActiveBuiltInEuicc = true;
+                    }
                 }
                 String eid = iss.eid;
                 if (TextUtils.isEmpty(eid)) {
@@ -859,23 +978,74 @@
 
                 addCardId(eid);
 
-                // whenever slot status is received, set default card to the eUICC with the
-                // lowest slot index.
-                if (!isDefaultEuiccCardIdSet) {
+                // whenever slot status is received, set default card to the non-removable eUICC
+                // with the lowest slot index.
+                if (!mUiccSlots[i].isRemovable() && !isDefaultEuiccCardIdSet) {
                     isDefaultEuiccCardIdSet = true;
-                    // TODO(b/122738148) the default eUICC should not be removable
                     mDefaultEuiccCardId = convertToPublicCardId(eid);
-                    log("Using eid=" + eid + " in slot=" + i + " to set mDefaultEuiccCardId="
-                            + mDefaultEuiccCardId);
+                    logWithLocalLog("Using eid=" + eid + " in slot=" + i
+                            + " to set mDefaultEuiccCardId=" + mDefaultEuiccCardId);
                 }
             }
         }
 
-        if (hasEuicc && !anyEuiccIsActive && !isDefaultEuiccCardIdSet) {
-            log("onGetSlotStatusDone: setting TEMPORARILY_UNSUPPORTED_CARD_ID");
+        if (!mHasActiveBuiltInEuicc && !isDefaultEuiccCardIdSet) {
+            // if there are no active built-in eUICCs, then consider setting a removable eUICC to
+            // the default.
+            // Note that on HAL<1.2, it's possible that a built-in eUICC exists, but does not
+            // correspond to any slot in mUiccSlots. This logic is still safe in that case because
+            // SlotStatus is only for HAL >= 1.2
+            for (int i = 0; i < numSlots; i++) {
+                if (mUiccSlots[i].isEuicc()) {
+                    String eid = status.get(i).eid;
+                    if (!TextUtils.isEmpty(eid)) {
+                        isDefaultEuiccCardIdSet = true;
+                        mDefaultEuiccCardId = convertToPublicCardId(eid);
+                        logWithLocalLog("Using eid=" + eid + " from removable eUICC in slot="
+                                + i + " to set mDefaultEuiccCardId=" + mDefaultEuiccCardId);
+                        break;
+                    }
+                }
+            }
+        }
+
+        if (mHasBuiltInEuicc && !anyEuiccIsActive && !isDefaultEuiccCardIdSet) {
+            logWithLocalLog(
+                    "onGetSlotStatusDone: mDefaultEuiccCardId=TEMPORARILY_UNSUPPORTED_CARD_ID");
+            isDefaultEuiccCardIdSet = true;
             mDefaultEuiccCardId = TEMPORARILY_UNSUPPORTED_CARD_ID;
         }
 
+
+        if (!isDefaultEuiccCardIdSet) {
+            if (mDefaultEuiccCardId >= 0) {
+                // if mDefaultEuiccCardId has already been set to an actual eUICC,
+                // don't overwrite mDefaultEuiccCardId unless that eUICC is no longer inserted
+                boolean defaultEuiccCardIdIsStillInserted = false;
+                String cardString = mCardStrings.get(mDefaultEuiccCardId);
+                for (UiccSlot slot : mUiccSlots) {
+                    if (slot.getUiccCard() == null) {
+                        continue;
+                    }
+                    if (cardString.equals(
+                            IccUtils.stripTrailingFs(slot.getUiccCard().getCardId()))) {
+                        defaultEuiccCardIdIsStillInserted = true;
+                    }
+                }
+                if (!defaultEuiccCardIdIsStillInserted) {
+                    logWithLocalLog("onGetSlotStatusDone: mDefaultEuiccCardId="
+                            + mDefaultEuiccCardId
+                            + " is no longer inserted. Setting mDefaultEuiccCardId=UNINITIALIZED");
+                    mDefaultEuiccCardId = UNINITIALIZED_CARD_ID;
+                }
+            } else {
+                // no known eUICCs at all (it's possible that an eUICC is inserted and we just don't
+                // know it's EID)
+                logWithLocalLog("onGetSlotStatusDone: mDefaultEuiccCardId=UNINITIALIZED");
+                mDefaultEuiccCardId = UNINITIALIZED_CARD_ID;
+            }
+        }
+
         if (VDBG) logPhoneIdToSlotIdMapping();
 
         // sanity check: number of active slots should be valid
@@ -898,7 +1068,6 @@
         options.setBackgroundActivityStartsAllowed(true);
         Intent intent = new Intent(TelephonyManager.ACTION_SIM_SLOT_STATUS_CHANGED);
         intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
-        intent.addFlags(Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
         mContext.sendBroadcast(intent, android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE,
                 options.toBundle());
     }
@@ -934,8 +1103,7 @@
         }
 
         IccRefreshResponse resp = (IccRefreshResponse) ar.result;
-        log("onSimRefresh: " + resp);
-        sLocalLog.log("onSimRefresh: " + resp);
+        logWithLocalLog("onSimRefresh: index " + index + ", " + resp);
 
         if (resp == null) {
             Rlog.e(LOG_TAG, "onSimRefresh: received without input");
@@ -981,8 +1149,9 @@
         mCis[index].getIccCardStatus(obtainMessage(EVENT_GET_ICC_STATUS_DONE, index));
     }
 
-    // for RadioConfig 1.2 or higher, the EID comes with the IccSlotStatus
-    // for RadioConfig<1.2 we register for EID ready set mCardStrings and mDefaultEuiccCardId here
+    // for HAL 1.2-1.3 we register for EID ready, set mCardStrings and mDefaultEuiccCardId here.
+    // Note that if there are multiple eUICCs on HAL 1.2-1.3, the default eUICC is the one whose EID
+    // is first loaded
     private void onEidReady(AsyncResult ar, Integer index) {
         if (ar.exception != null) {
             Rlog.e(LOG_TAG, "onEidReady: exception: " + ar.exception);
@@ -994,23 +1163,47 @@
             return;
         }
         int slotId = mPhoneIdToSlotId[index];
-        UiccCard card = mUiccSlots[slotId].getUiccCard();
+        EuiccCard card = (EuiccCard) mUiccSlots[slotId].getUiccCard();
         if (card == null) {
             Rlog.e(LOG_TAG, "onEidReady: UiccCard in slot " + slotId + " is null");
             return;
         }
 
         // set mCardStrings and the defaultEuiccCardId using the now available EID
-        String eid = ((EuiccCard) card).getEid();
+        String eid = card.getEid();
         addCardId(eid);
         if (mDefaultEuiccCardId == UNINITIALIZED_CARD_ID
                 || mDefaultEuiccCardId == TEMPORARILY_UNSUPPORTED_CARD_ID) {
-            // TODO(b/122738148) the default eUICC should not be removable
-            mDefaultEuiccCardId = convertToPublicCardId(eid);
-            log("onEidReady: eid=" + eid + " slot=" + slotId + " mDefaultEuiccCardId="
-                    + mDefaultEuiccCardId);
+            if (!mUiccSlots[slotId].isRemovable()) {
+                mDefaultEuiccCardId = convertToPublicCardId(eid);
+                logWithLocalLog("onEidReady: eid=" + eid + " slot=" + slotId
+                        + " mDefaultEuiccCardId=" + mDefaultEuiccCardId);
+            } else if (!mHasActiveBuiltInEuicc) {
+                // we only set a removable eUICC to the default if there are no active non-removable
+                // eUICCs
+                mDefaultEuiccCardId = convertToPublicCardId(eid);
+                logWithLocalLog("onEidReady: eid=" + eid + " from removable eUICC in slot=" + slotId
+                        + " mDefaultEuiccCardId=" + mDefaultEuiccCardId);
+            }
         }
-        ((EuiccCard) card).unregisterForEidReady(this);
+        card.unregisterForEidReady(this);
+    }
+
+    // Return true if the device has at least one built in eUICC based on the resource overlay
+    private boolean hasBuiltInEuicc() {
+        return mEuiccSlots != null &&  mEuiccSlots.length > 0;
+    }
+
+    private boolean isBuiltInEuiccSlot(int slotIndex) {
+        if (!mHasBuiltInEuicc) {
+            return false;
+        }
+        for (int slot : mEuiccSlots) {
+            if (slot == slotIndex) {
+                return true;
+            }
+        }
+        return false;
     }
 
     /**
@@ -1038,7 +1231,18 @@
         Rlog.d(LOG_TAG, string);
     }
 
-    public void addCardLog(String data) {
+    private void logWithLocalLog(String string) {
+        Rlog.d(LOG_TAG, string);
+        sLocalLog.log("UiccController: " + string);
+    }
+
+    private void logeWithLocalLog(String string) {
+        Rlog.e(LOG_TAG, string);
+        sLocalLog.log("UiccController: " + string);
+    }
+
+    /** The supplied log should also indicate the caller to avoid ambiguity. */
+    public static void addLocalLog(String data) {
         sLocalLog.log(data);
     }
 
@@ -1054,6 +1258,8 @@
         pw.println();
         pw.flush();
         pw.println(" mIsCdmaSupported=" + isCdmaSupported(mContext));
+        pw.println(" mHasBuiltInEuicc=" + mHasBuiltInEuicc);
+        pw.println(" mHasActiveBuiltInEuicc=" + mHasActiveBuiltInEuicc);
         pw.println(" mUiccSlots: size=" + mUiccSlots.length);
         pw.println(" mCardStrings=" + mCardStrings);
         pw.println(" mDefaultEuiccCardId=" + mDefaultEuiccCardId);
diff --git a/src/java/com/android/internal/telephony/uicc/UiccPkcs15.java b/src/java/com/android/internal/telephony/uicc/UiccPkcs15.java
index cc99ae9..3513bb9 100644
--- a/src/java/com/android/internal/telephony/uicc/UiccPkcs15.java
+++ b/src/java/com/android/internal/telephony/uicc/UiccPkcs15.java
@@ -19,9 +19,9 @@
 import android.os.AsyncResult;
 import android.os.Handler;
 import android.os.Message;
-import android.telephony.Rlog;
 
 import com.android.internal.telephony.uicc.UiccCarrierPrivilegeRules.TLV;
+import com.android.telephony.Rlog;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
diff --git a/src/java/com/android/internal/telephony/uicc/UiccProfile.java b/src/java/com/android/internal/telephony/uicc/UiccProfile.java
index 85c3164..e53a1cb 100644
--- a/src/java/com/android/internal/telephony/uicc/UiccProfile.java
+++ b/src/java/com/android/internal/telephony/uicc/UiccProfile.java
@@ -34,10 +34,10 @@
 import android.os.PersistableBundle;
 import android.os.Registrant;
 import android.os.RegistrantList;
+import android.os.UserManager;
 import android.preference.PreferenceManager;
 import android.provider.Settings;
 import android.telephony.CarrierConfigManager;
-import android.telephony.Rlog;
 import android.telephony.ServiceState;
 import android.telephony.SubscriptionInfo;
 import android.telephony.SubscriptionManager;
@@ -58,9 +58,11 @@
 import com.android.internal.telephony.SubscriptionController;
 import com.android.internal.telephony.cat.CatService;
 import com.android.internal.telephony.uicc.IccCardApplicationStatus.AppType;
+import com.android.internal.telephony.uicc.IccCardApplicationStatus.PersoSubState;
 import com.android.internal.telephony.uicc.IccCardStatus.CardState;
 import com.android.internal.telephony.uicc.IccCardStatus.PinState;
 import com.android.internal.telephony.uicc.euicc.EuiccCard;
+import com.android.telephony.Rlog;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
@@ -126,6 +128,7 @@
     private static final int EVENT_SIM_IO_DONE = 12;
     private static final int EVENT_CARRIER_PRIVILEGES_LOADED = 13;
     private static final int EVENT_CARRIER_CONFIG_CHANGED = 14;
+    // NOTE: any new EVENT_* values must be added to eventToString.
 
     private TelephonyManager mTelephonyManager;
 
@@ -145,15 +148,28 @@
             new ContentObserver(new Handler()) {
                 @Override
                 public void onChange(boolean selfChange) {
-                    mContext.getContentResolver().unregisterContentObserver(this);
-                    for (String pkgName : getUninstalledCarrierPackages()) {
-                        InstallCarrierAppUtils.showNotification(mContext, pkgName);
-                        InstallCarrierAppUtils.registerPackageInstallReceiver(mContext);
+                    synchronized (mLock) {
+                        mContext.getContentResolver().unregisterContentObserver(this);
+                        mProvisionCompleteContentObserverRegistered = false;
+                        showCarrierAppNotificationsIfPossible();
                     }
                 }
             };
+    private boolean mProvisionCompleteContentObserverRegistered;
 
-    private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
+    private final BroadcastReceiver mUserUnlockReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            synchronized (mLock) {
+                mContext.unregisterReceiver(this);
+                mUserUnlockReceiverRegistered = false;
+                showCarrierAppNotificationsIfPossible();
+            }
+        }
+    };
+    private boolean mUserUnlockReceiverRegistered;
+
+    private final BroadcastReceiver mCarrierConfigChangedReceiver = new BroadcastReceiver() {
         @Override
         public void onReceive(Context context, Intent intent) {
             if (intent.getAction().equals(CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED)) {
@@ -166,6 +182,7 @@
     public final Handler mHandler = new Handler() {
         @Override
         public void handleMessage(Message msg) {
+            String eventName = eventToString(msg.what);
             // We still need to handle the following response messages even the UiccProfile has been
             // disposed because whoever sent the request may be still waiting for the response.
             if (mDisposed && msg.what != EVENT_OPEN_LOGICAL_CHANNEL_DONE
@@ -173,21 +190,22 @@
                     && msg.what != EVENT_TRANSMIT_APDU_LOGICAL_CHANNEL_DONE
                     && msg.what != EVENT_TRANSMIT_APDU_BASIC_CHANNEL_DONE
                     && msg.what != EVENT_SIM_IO_DONE) {
-                loge("handleMessage: Received " + msg.what
+                loge("handleMessage: Received " + eventName
                         + " after dispose(); ignoring the message");
                 return;
             }
-            loglocal("handleMessage: Received " + msg.what + " for phoneId " + mPhoneId);
+            logWithLocalLog("handleMessage: Received " + eventName + " for phoneId " + mPhoneId);
             switch (msg.what) {
                 case EVENT_NETWORK_LOCKED:
-                    mNetworkLockedRegistrants.notifyRegistrants();
+                    mNetworkLockedRegistrants.notifyRegistrants(new AsyncResult(
+                            null, mUiccApplication.getPersoSubState().ordinal(), null));
                     // intentional fall through
                 case EVENT_RADIO_OFF_OR_UNAVAILABLE:
                 case EVENT_ICC_LOCKED:
                 case EVENT_APP_READY:
                 case EVENT_RECORDS_LOADED:
                 case EVENT_EID_READY:
-                    if (VDBG) log("handleMessage: Received " + msg.what);
+                    if (VDBG) log("handleMessage: Received " + eventName);
                     updateExternalState();
                     break;
 
@@ -220,8 +238,8 @@
                 case EVENT_SIM_IO_DONE:
                     AsyncResult ar = (AsyncResult) msg.obj;
                     if (ar.exception != null) {
-                        loglocal("handleMessage: Exception " + ar.exception);
-                        log("handleMessage: Error in SIM access with exception" + ar.exception);
+                        logWithLocalLog("handleMessage: Error in SIM access with exception "
+                                + ar.exception);
                     }
                     AsyncResult.forMessage((Message) ar.userObj, ar.result, ar.exception);
                     ((Message) ar.userObj).sendToTarget();
@@ -258,7 +276,7 @@
 
         IntentFilter intentfilter = new IntentFilter();
         intentfilter.addAction(CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED);
-        c.registerReceiver(mReceiver, intentfilter);
+        c.registerReceiver(mCarrierConfigChangedReceiver, intentfilter);
     }
 
     /**
@@ -276,11 +294,22 @@
             unregisterAllAppEvents();
             unregisterCurrAppEvents();
 
+            if (mProvisionCompleteContentObserverRegistered) {
+                mContext.getContentResolver()
+                        .unregisterContentObserver(mProvisionCompleteContentObserver);
+                mProvisionCompleteContentObserverRegistered = false;
+            }
+
+            if (mUserUnlockReceiverRegistered) {
+                mContext.unregisterReceiver(mUserUnlockReceiver);
+                mUserUnlockReceiverRegistered = false;
+            }
+
             InstallCarrierAppUtils.hideAllNotifications(mContext);
             InstallCarrierAppUtils.unregisterPackageInstallReceiver(mContext);
 
             mCi.unregisterForOffOrNotAvailable(mHandler);
-            mContext.unregisterReceiver(mReceiver);
+            mContext.unregisterReceiver(mCarrierConfigChangedReceiver);
 
             if (mCatService != null) mCatService.dispose();
             for (UiccCardApplication app : mUiccApplications) {
@@ -317,7 +346,12 @@
             if (isGsm) {
                 mCurrentAppType = UiccController.APP_FAM_3GPP;
             } else {
-                mCurrentAppType = UiccController.APP_FAM_3GPP2;
+                UiccCardApplication newApp = getApplication(UiccController.APP_FAM_3GPP2);
+                if(newApp != null) {
+                    mCurrentAppType = UiccController.APP_FAM_3GPP2;
+                } else {
+                    mCurrentAppType = UiccController.APP_FAM_3GPP;
+                }
             }
         }
     }
@@ -367,7 +401,7 @@
                     nameSource = SubscriptionManager.NAME_SOURCE_SIM_PNN;
                 } else {
                     newCarrierName = phone.getCarrierName();    // Get the name from carrier id.
-                    nameSource = SubscriptionManager.NAME_SOURCE_DEFAULT_SOURCE;
+                    nameSource = SubscriptionManager.NAME_SOURCE_CARRIER_ID;
                 }
             }
         }
@@ -419,7 +453,7 @@
             int nameSource) {
         /* update display name with carrier override */
         SubscriptionInfo subInfo = subCon.getActiveSubscriptionInfo(
-                subId, mContext.getOpPackageName());
+                subId, mContext.getOpPackageName(), mContext.getAttributionTag());
 
         if (subInfo == null) {
             return;
@@ -519,8 +553,7 @@
                 cardLocked = true;
                 lockedState = IccCardConstants.State.PUK_REQUIRED;
             } else if (appState == IccCardApplicationStatus.AppState.APPSTATE_SUBSCRIPTION_PERSO) {
-                if (mUiccApplication.getPersoSubState()
-                        == IccCardApplicationStatus.PersoSubState.PERSOSUBSTATE_SIM_NETWORK) {
+                if (PersoSubState.isPersoLocked(mUiccApplication.getPersoSubState())) {
                     if (VDBG) log("updateExternalState: PERSOSUBSTATE_SIM_NETWORK");
                     cardLocked = true;
                     lockedState = IccCardConstants.State.NETWORK_LOCKED;
@@ -565,6 +598,12 @@
                 }
                 setExternalState(IccCardConstants.State.NOT_READY);
                 break;
+            case APPSTATE_DETECTED:
+                if (VDBG) {
+                    log("updateExternalState: app state is detected; setting state to NOT_READY");
+                }
+                setExternalState(IccCardConstants.State.NOT_READY);
+                break;
             case APPSTATE_READY:
                 checkAndUpdateIfAnyAppToBeIgnored();
                 if (areAllApplicationsReady()) {
@@ -732,7 +771,8 @@
             mNetworkLockedRegistrants.add(r);
 
             if (getState() == IccCardConstants.State.NETWORK_LOCKED) {
-                r.notifyRegistrant();
+                r.notifyRegistrant(
+                        new AsyncResult(null, mUiccApplication.getPersoSubState().ordinal(), null));
             }
         }
     }
@@ -815,6 +855,20 @@
     }
 
     @Override
+    public void supplySimDepersonalization(PersoSubState persoType, String pin, Message onComplete) {
+        synchronized (mLock) {
+            if (mUiccApplication != null) {
+                mUiccApplication.supplySimDepersonalization(persoType, pin, onComplete);
+            } else if (onComplete != null) {
+                Exception e = new RuntimeException("CommandsInterface is not set.");
+                AsyncResult.forMessage(onComplete).exception = e;
+                onComplete.sendToTarget();
+                return;
+            }
+        }
+    }
+
+    @Override
     public boolean getIccLockEnabled() {
         synchronized (mLock) {
             /* defaults to false, if ICC is absent/deactivated */
@@ -1174,10 +1228,13 @@
         }
     }
 
-    static boolean isPackageInstalled(Context context, String pkgName) {
+    static boolean isPackageBundled(Context context, String pkgName) {
         PackageManager pm = context.getPackageManager();
         try {
-            pm.getPackageInfo(pkgName, PackageManager.GET_ACTIVITIES);
+            // We also match hidden-until-installed apps. The assumption here is that some other
+            // mechanism (like CarrierAppUtils) would automatically enable such an app, so we
+            // shouldn't prompt the user about it.
+            pm.getApplicationInfo(pkgName, PackageManager.MATCH_HIDDEN_UNTIL_INSTALLED_COMPONENTS);
             if (DBG) log(pkgName + " is installed.");
             return true;
         } catch (PackageManager.NameNotFoundException e) {
@@ -1204,21 +1261,47 @@
 
         synchronized (mLock) {
             mCarrierPrivilegeRegistrants.notifyRegistrants();
-            boolean isProvisioned = Settings.Global.getInt(
-                    mContext.getContentResolver(),
-                    Settings.Global.DEVICE_PROVISIONED, 1) == 1;
-            // Only show dialog if the phone is through with Setup Wizard.  Otherwise, wait for
-            // completion and show a notification instead
-            if (isProvisioned) {
+            boolean isProvisioned = isProvisioned();
+            boolean isUnlocked = isUserUnlocked();
+            // Only show dialog if the phone is through with Setup Wizard and is unlocked.
+            // Otherwise, wait for completion and unlock and show a notification instead.
+            if (isProvisioned && isUnlocked) {
                 for (String pkgName : getUninstalledCarrierPackages()) {
                     promptInstallCarrierApp(pkgName);
                 }
             } else {
-                final Uri uri = Settings.Global.getUriFor(Settings.Global.DEVICE_PROVISIONED);
-                mContext.getContentResolver().registerContentObserver(
-                        uri,
-                        false,
-                        mProvisionCompleteContentObserver);
+                if (!isProvisioned) {
+                    final Uri uri = Settings.Global.getUriFor(Settings.Global.DEVICE_PROVISIONED);
+                    mContext.getContentResolver().registerContentObserver(
+                            uri,
+                            false,
+                            mProvisionCompleteContentObserver);
+                    mProvisionCompleteContentObserverRegistered = true;
+                }
+                if (!isUnlocked) {
+                    mContext.registerReceiver(
+                            mUserUnlockReceiver, new IntentFilter(Intent.ACTION_USER_UNLOCKED));
+                    mUserUnlockReceiverRegistered = true;
+                }
+            }
+        }
+    }
+
+    private boolean isProvisioned() {
+        return Settings.Global.getInt(
+                mContext.getContentResolver(),
+                Settings.Global.DEVICE_PROVISIONED, 1) == 1;
+    }
+
+    private boolean isUserUnlocked() {
+        return mContext.getSystemService(UserManager.class).isUserUnlocked();
+    }
+
+    private void showCarrierAppNotificationsIfPossible() {
+        if (isProvisioned() && isUserUnlocked()) {
+            for (String pkgName : getUninstalledCarrierPackages()) {
+                InstallCarrierAppUtils.showNotification(mContext, pkgName);
+                InstallCarrierAppUtils.registerPackageInstallReceiver(mContext);
             }
         }
     }
@@ -1242,7 +1325,7 @@
         for (UiccAccessRule accessRule : accessRules) {
             String certHexString = accessRule.getCertificateHexString().toUpperCase();
             String pkgName = certPackageMap.get(certHexString);
-            if (!TextUtils.isEmpty(pkgName) && !isPackageInstalled(mContext, pkgName)) {
+            if (!TextUtils.isEmpty(pkgName) && !isPackageBundled(mContext, pkgName)) {
                 uninstalledCarrierPackages.add(pkgName);
             }
         }
@@ -1409,8 +1492,8 @@
      * Exposes {@link CommandsInterface#iccOpenLogicalChannel}
      */
     public void iccOpenLogicalChannel(String aid, int p2, Message response) {
-        loglocal("iccOpenLogicalChannel: " + aid + " , " + p2 + " by pid:" + Binder.getCallingPid()
-                + " uid:" + Binder.getCallingUid());
+        logWithLocalLog("iccOpenLogicalChannel: " + aid + " , " + p2 + " by pid:"
+                + Binder.getCallingPid() + " uid:" + Binder.getCallingUid());
         mCi.iccOpenLogicalChannel(aid, p2,
                 mHandler.obtainMessage(EVENT_OPEN_LOGICAL_CHANNEL_DONE, response));
     }
@@ -1419,7 +1502,7 @@
      * Exposes {@link CommandsInterface#iccCloseLogicalChannel}
      */
     public void iccCloseLogicalChannel(int channel, Message response) {
-        loglocal("iccCloseLogicalChannel: " + channel);
+        logWithLocalLog("iccCloseLogicalChannel: " + channel);
         mCi.iccCloseLogicalChannel(channel,
                 mHandler.obtainMessage(EVENT_CLOSE_LOGICAL_CHANNEL_DONE, response));
     }
@@ -1583,6 +1666,21 @@
     }
 
     /**
+     * Make sure the iccid in SIM record matches the current active subId. If not, return false.
+     * When SIM switching in eSIM is happening, there are rare cases that setOperatorBrandOverride
+     * is called on old subId while new iccid is already loaded on SIM record. For those cases
+     * setOperatorBrandOverride would apply to the wrong (new) iccid. This check is to avoid it.
+     */
+    private boolean checkSubIdAndIccIdMatch(String iccid) {
+        if (TextUtils.isEmpty(iccid)) return false;
+        SubscriptionInfo subInfo = SubscriptionController.getInstance()
+                .getActiveSubscriptionInfoForSimSlotIndex(
+                        getPhoneId(), mContext.getOpPackageName(), null);
+        return subInfo != null && IccUtils.stripTrailingFs(subInfo.getIccId()).equals(
+                IccUtils.stripTrailingFs(iccid));
+    }
+
+    /**
      * Sets the overridden operator brand.
      */
     public boolean setOperatorBrandOverride(String brand) {
@@ -1593,6 +1691,10 @@
         if (TextUtils.isEmpty(iccId)) {
             return false;
         }
+        if (!checkSubIdAndIccIdMatch(iccId)) {
+            loge("iccId doesn't match current active subId.");
+            return false;
+        }
 
         SharedPreferences.Editor spEditor =
                 PreferenceManager.getDefaultSharedPreferences(mContext).edit();
@@ -1634,6 +1736,26 @@
         return null;
     }
 
+    private static String eventToString(int event) {
+        switch (event) {
+            case EVENT_RADIO_OFF_OR_UNAVAILABLE: return "RADIO_OFF_OR_UNAVAILABLE";
+            case EVENT_ICC_LOCKED: return "ICC_LOCKED";
+            case EVENT_APP_READY: return "APP_READY";
+            case EVENT_RECORDS_LOADED: return "RECORDS_LOADED";
+            case EVENT_NETWORK_LOCKED: return "NETWORK_LOCKED";
+            case EVENT_EID_READY: return "EID_READY";
+            case EVENT_ICC_RECORD_EVENTS: return "ICC_RECORD_EVENTS";
+            case EVENT_OPEN_LOGICAL_CHANNEL_DONE: return "OPEN_LOGICAL_CHANNEL_DONE";
+            case EVENT_CLOSE_LOGICAL_CHANNEL_DONE: return "CLOSE_LOGICAL_CHANNEL_DONE";
+            case EVENT_TRANSMIT_APDU_LOGICAL_CHANNEL_DONE: return "TRANSMIT_APDU_LOGICAL_CHANNEL_DONE";
+            case EVENT_TRANSMIT_APDU_BASIC_CHANNEL_DONE: return "TRANSMIT_APDU_BASIC_CHANNEL_DONE";
+            case EVENT_SIM_IO_DONE: return "SIM_IO_DONE";
+            case EVENT_CARRIER_PRIVILEGES_LOADED: return "CARRIER_PRIVILEGES_LOADED";
+            case EVENT_CARRIER_CONFIG_CHANGED: return "CARRIER_CONFIG_CHANGED";
+            default: return "UNKNOWN(" + event + ")";
+        }
+    }
+
     private static void log(String msg) {
         Rlog.d(LOG_TAG, msg);
     }
@@ -1642,8 +1764,9 @@
         Rlog.e(LOG_TAG, msg);
     }
 
-    private void loglocal(String msg) {
-        if (DBG) UiccController.sLocalLog.log("UiccProfile[" + mPhoneId + "]: " + msg);
+    private void logWithLocalLog(String msg) {
+        Rlog.d(LOG_TAG, msg);
+        if (DBG) UiccController.addLocalLog("UiccProfile[" + mPhoneId + "]: " + msg);
     }
 
     /**
diff --git a/src/java/com/android/internal/telephony/uicc/UiccSlot.java b/src/java/com/android/internal/telephony/uicc/UiccSlot.java
index f113cd6..24e5a31 100644
--- a/src/java/com/android/internal/telephony/uicc/UiccSlot.java
+++ b/src/java/com/android/internal/telephony/uicc/UiccSlot.java
@@ -26,7 +26,7 @@
 import android.os.Handler;
 import android.os.Message;
 import android.os.PowerManager;
-import android.telephony.Rlog;
+import android.os.UserHandle;
 import android.telephony.TelephonyManager;
 import android.text.TextUtils;
 import android.view.WindowManager;
@@ -34,8 +34,11 @@
 import com.android.internal.R;
 import com.android.internal.telephony.CommandsInterface;
 import com.android.internal.telephony.IccCardConstants;
+import com.android.internal.telephony.Phone;
+import com.android.internal.telephony.PhoneFactory;
 import com.android.internal.telephony.uicc.IccCardStatus.CardState;
 import com.android.internal.telephony.uicc.euicc.EuiccCard;
+import com.android.telephony.Rlog;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
@@ -103,9 +106,9 @@
             //   2. The latest mCardState is not ABSENT, but there is no UiccCard instance.
             } else if ((oldState == null || oldState == CardState.CARDSTATE_ABSENT
                     || mUiccCard == null) && mCardState != CardState.CARDSTATE_ABSENT) {
-                // No notifications while radio is off or we just powering up
-                if (radioState == TelephonyManager.RADIO_POWER_ON
-                        && mLastRadioState == TelephonyManager.RADIO_POWER_ON) {
+                // No notification while we are just powering up
+                if (radioState != TelephonyManager.RADIO_POWER_UNAVAILABLE
+                        && mLastRadioState != TelephonyManager.RADIO_POWER_UNAVAILABLE) {
                     if (DBG) log("update: notify card added");
                     sendMessage(obtainMessage(EVENT_CARD_ADDED, null));
                 }
@@ -151,12 +154,10 @@
             if (iss.slotState == IccSlotStatus.SlotState.SLOTSTATE_INACTIVE) {
                 // TODO: (b/79432584) evaluate whether should broadcast card state change
                 // even if it's inactive.
+                UiccController.updateInternalIccStateForInactiveSlot(mContext, mPhoneId, mIccId);
                 if (mActive) {
                     mActive = false;
                     mLastRadioState = TelephonyManager.RADIO_POWER_UNAVAILABLE;
-                    UiccController.updateInternalIccState(
-                            mContext, IccCardConstants.State.ABSENT, null, mPhoneId,
-                            true /* special notification for absent card in an inactive slot */);
                     mPhoneId = INVALID_PHONE_ID;
                     nullifyUiccCard(true /* sim state is unknown */);
                 }
@@ -181,9 +182,9 @@
     private void updateCardStateAbsent() {
         int radioState =
                 (mCi == null) ? TelephonyManager.RADIO_POWER_UNAVAILABLE : mCi.getRadioState();
-        // No notifications while radio is off or we just powering up
-        if (radioState == TelephonyManager.RADIO_POWER_ON
-                && mLastRadioState == TelephonyManager.RADIO_POWER_ON) {
+        // No notification while we are just powering up
+        if (radioState != TelephonyManager.RADIO_POWER_UNAVAILABLE
+                && mLastRadioState != TelephonyManager.RADIO_POWER_UNAVAILABLE) {
             if (DBG) log("update: notify card removed");
             sendMessage(obtainMessage(EVENT_CARD_REMOVED, null));
         }
@@ -295,6 +296,13 @@
             log("onIccSwap: isHotSwapSupported is true, don't prompt for rebooting");
             return;
         }
+
+        Phone phone = PhoneFactory.getPhone(mPhoneId);
+        if (phone != null && phone.isShuttingDown()) {
+            log("onIccSwap: already doing shutdown, no need to prompt");
+            return;
+        }
+
         log("onIccSwap: isHotSwapSupported is false, prompt for rebooting");
 
         promptForRestart(isAdded);
@@ -310,7 +318,7 @@
                         dialogComponent)).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
                         .putExtra(EXTRA_ICC_CARD_ADDED, isAdded);
                 try {
-                    mContext.startActivity(intent);
+                    mContext.startActivityAsUser(intent, UserHandle.CURRENT);
                     return;
                 } catch (ActivityNotFoundException e) {
                     loge("Unable to find ICC hotswap prompt for restart activity: " + e);
@@ -428,6 +436,7 @@
         pw.println(" mCi=" + mCi);
         pw.println(" mActive=" + mActive);
         pw.println(" mIsEuicc=" + mIsEuicc);
+        pw.println(" mIsRemovable=" + mIsRemovable);
         pw.println(" mLastRadioState=" + mLastRadioState);
         pw.println(" mIccId=" + mIccId);
         pw.println(" mEid=" + mEid);
diff --git a/src/java/com/android/internal/telephony/uicc/UsimFileHandler.java b/src/java/com/android/internal/telephony/uicc/UsimFileHandler.java
index b120eb6..ff8d63d 100755
--- a/src/java/com/android/internal/telephony/uicc/UsimFileHandler.java
+++ b/src/java/com/android/internal/telephony/uicc/UsimFileHandler.java
@@ -16,10 +16,8 @@
 
 package com.android.internal.telephony.uicc;
 
-import android.telephony.Rlog;
-
 import com.android.internal.telephony.CommandsInterface;
-import com.android.internal.telephony.uicc.UiccCardApplication;
+import com.android.telephony.Rlog;
 
 /**
  * {@hide}
diff --git a/src/java/com/android/internal/telephony/uicc/UsimServiceTable.java b/src/java/com/android/internal/telephony/uicc/UsimServiceTable.java
index 2c103ca..281b5f2 100644
--- a/src/java/com/android/internal/telephony/uicc/UsimServiceTable.java
+++ b/src/java/com/android/internal/telephony/uicc/UsimServiceTable.java
@@ -16,7 +16,7 @@
 
 package com.android.internal.telephony.uicc;
 
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
 
 
 /**
diff --git a/src/java/com/android/internal/telephony/uicc/VoiceMailConstants.java b/src/java/com/android/internal/telephony/uicc/VoiceMailConstants.java
index b3bdf14..b10b875 100644
--- a/src/java/com/android/internal/telephony/uicc/VoiceMailConstants.java
+++ b/src/java/com/android/internal/telephony/uicc/VoiceMailConstants.java
@@ -16,21 +16,21 @@
 
 package com.android.internal.telephony.uicc;
 
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
 import android.os.Environment;
 import android.util.Xml;
-import android.telephony.Rlog;
 
-import java.util.HashMap;
-import java.io.FileReader;
-import java.io.File;
-import java.io.FileNotFoundException;
-import java.io.IOException;
+import com.android.internal.telephony.util.XmlUtils;
+import com.android.telephony.Rlog;
 
 import org.xmlpull.v1.XmlPullParser;
 import org.xmlpull.v1.XmlPullParserException;
 
-import com.android.internal.util.XmlUtils;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileReader;
+import java.io.IOException;
+import java.util.HashMap;
 
 /**
  * {@hide}
diff --git a/src/java/com/android/internal/telephony/uicc/euicc/EuiccCard.java b/src/java/com/android/internal/telephony/uicc/euicc/EuiccCard.java
index 12f2ead..0b56273 100644
--- a/src/java/com/android/internal/telephony/uicc/euicc/EuiccCard.java
+++ b/src/java/com/android/internal/telephony/uicc/euicc/EuiccCard.java
@@ -25,7 +25,6 @@
 import android.os.RegistrantList;
 import android.service.carrier.CarrierIdentifier;
 import android.service.euicc.EuiccProfileInfo;
-import android.telephony.Rlog;
 import android.telephony.SubscriptionInfo;
 import android.telephony.UiccAccessRule;
 import android.telephony.euicc.EuiccCardManager;
@@ -53,6 +52,7 @@
 import com.android.internal.telephony.uicc.euicc.apdu.RequestProvider;
 import com.android.internal.telephony.uicc.euicc.async.AsyncResultCallback;
 import com.android.internal.telephony.uicc.euicc.async.AsyncResultHelper;
+import com.android.telephony.Rlog;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
@@ -99,6 +99,9 @@
     private static final String DEV_CAP_EUTRAN = "eutran";
     private static final String DEV_CAP_NFC = "nfc";
     private static final String DEV_CAP_CRL = "crl";
+    private static final String DEV_CAP_NREPC = "nrepc";
+    private static final String DEV_CAP_NR5GC = "nr5gc";
+    private static final String DEV_CAP_EUTRAN5GC = "eutran5gc";
 
     // These interfaces are used for simplifying the code by leveraging lambdas.
     private interface ApduRequestBuilder {
@@ -1059,6 +1062,15 @@
             case DEV_CAP_CRL:
                 devCapBuilder.addChildAsBytes(Tags.TAG_CTX_7, versionBytes);
                 break;
+            case DEV_CAP_NREPC:
+                devCapBuilder.addChildAsBytes(Tags.TAG_CTX_9, versionBytes);
+                break;
+            case DEV_CAP_NR5GC:
+                devCapBuilder.addChildAsBytes(Tags.TAG_CTX_10, versionBytes);
+                break;
+            case DEV_CAP_EUTRAN5GC:
+                devCapBuilder.addChildAsBytes(Tags.TAG_CTX_11, versionBytes);
+                break;
             default:
                 loge("Invalid device capability name: " + devCap);
                 break;
diff --git a/src/java/com/android/internal/telephony/uicc/euicc/EuiccSpecVersion.java b/src/java/com/android/internal/telephony/uicc/euicc/EuiccSpecVersion.java
index b038716..9450bfc 100644
--- a/src/java/com/android/internal/telephony/uicc/euicc/EuiccSpecVersion.java
+++ b/src/java/com/android/internal/telephony/uicc/euicc/EuiccSpecVersion.java
@@ -16,12 +16,11 @@
 
 package com.android.internal.telephony.uicc.euicc;
 
-import android.telephony.Rlog;
-
 import com.android.internal.telephony.uicc.asn1.Asn1Decoder;
 import com.android.internal.telephony.uicc.asn1.Asn1Node;
 import com.android.internal.telephony.uicc.asn1.InvalidAsn1DataException;
 import com.android.internal.telephony.uicc.asn1.TagNotFoundException;
+import com.android.telephony.Rlog;
 
 import java.util.Arrays;
 
diff --git a/src/java/com/android/internal/telephony/uicc/euicc/Tags.java b/src/java/com/android/internal/telephony/uicc/euicc/Tags.java
index c914132..26df42b 100644
--- a/src/java/com/android/internal/telephony/uicc/euicc/Tags.java
+++ b/src/java/com/android/internal/telephony/uicc/euicc/Tags.java
@@ -55,6 +55,10 @@
     static final int TAG_CTX_6 = 0x86;
     static final int TAG_CTX_7 = 0x87;
     static final int TAG_CTX_8 = 0x88;
+    static final int TAG_CTX_9 = 0x89;
+    static final int TAG_CTX_10 = 0x8A;
+    static final int TAG_CTX_11 = 0x8B;
+
     // Context tags for constructed (compound) types
     static final int TAG_CTX_COMP_0 = 0xA0;
     static final int TAG_CTX_COMP_1 = 0xA1;
diff --git a/src/java/com/android/internal/telephony/uicc/euicc/apdu/ApduSender.java b/src/java/com/android/internal/telephony/uicc/euicc/apdu/ApduSender.java
index 1f72ae8..8e7237e 100644
--- a/src/java/com/android/internal/telephony/uicc/euicc/apdu/ApduSender.java
+++ b/src/java/com/android/internal/telephony/uicc/euicc/apdu/ApduSender.java
@@ -18,13 +18,14 @@
 
 import android.annotation.Nullable;
 import android.os.Handler;
+import android.os.Looper;
 import android.telephony.IccOpenLogicalChannelResponse;
-import android.telephony.Rlog;
 
 import com.android.internal.telephony.CommandsInterface;
 import com.android.internal.telephony.uicc.IccIoResult;
 import com.android.internal.telephony.uicc.euicc.async.AsyncResultCallback;
 import com.android.internal.telephony.uicc.euicc.async.AsyncResultHelper;
+import com.android.telephony.Rlog;
 
 import java.io.ByteArrayOutputStream;
 import java.io.IOException;
@@ -50,10 +51,16 @@
     private static final int STATUS_NO_ERROR = 0x9000;
     private static final int SW1_NO_ERROR = 0x91;
 
+    private static final int WAIT_TIME_MS = 2000;
+
     private static void logv(String msg) {
         Rlog.v(LOG_TAG, msg);
     }
 
+    private static void logd(String msg) {
+        Rlog.d(LOG_TAG, msg);
+    }
+
     private final String mAid;
     private final boolean mSupportExtendedApdu;
     private final OpenLogicalChannelInvocation mOpenChannel;
@@ -94,10 +101,25 @@
             Handler handler) {
         synchronized (mChannelLock) {
             if (mChannelOpened) {
-                AsyncResultHelper.throwException(
-                        new ApduException("Logical channel has already been opened."),
-                        resultCallback, handler);
-                return;
+                if (!Looper.getMainLooper().equals(Looper.myLooper())) {
+                    logd("Logical channel has already been opened. Wait.");
+                    try {
+                        mChannelLock.wait(WAIT_TIME_MS);
+                    } catch (InterruptedException e) {
+                        // nothing to do
+                    }
+                    if (mChannelOpened) {
+                        AsyncResultHelper.throwException(
+                                new ApduException("The logical channel is still in use."),
+                                resultCallback, handler);
+                        return;
+                    }
+                } else {
+                    AsyncResultHelper.throwException(
+                            new ApduException("The logical channel is in use."),
+                            resultCallback, handler);
+                    return;
+                }
             }
             mChannelOpened = true;
         }
@@ -111,6 +133,7 @@
                         || status != IccOpenLogicalChannelResponse.STATUS_NO_ERROR) {
                     synchronized (mChannelLock) {
                         mChannelOpened = false;
+                        mChannelLock.notify();
                     }
                     resultCallback.onException(
                             new ApduException("Failed to open logical channel opened for AID: "
@@ -245,6 +268,7 @@
             public void onResult(Boolean aBoolean) {
                 synchronized (mChannelLock) {
                     mChannelOpened = false;
+                    mChannelLock.notify();
                 }
 
                 if (exception == null) {
diff --git a/src/java/com/android/internal/telephony/uicc/euicc/apdu/CloseLogicalChannelInvocation.java b/src/java/com/android/internal/telephony/uicc/euicc/apdu/CloseLogicalChannelInvocation.java
index edd89d6..82ddb80 100644
--- a/src/java/com/android/internal/telephony/uicc/euicc/apdu/CloseLogicalChannelInvocation.java
+++ b/src/java/com/android/internal/telephony/uicc/euicc/apdu/CloseLogicalChannelInvocation.java
@@ -18,11 +18,11 @@
 
 import android.os.AsyncResult;
 import android.os.Message;
-import android.telephony.Rlog;
 
 import com.android.internal.telephony.CommandException;
 import com.android.internal.telephony.CommandsInterface;
 import com.android.internal.telephony.uicc.euicc.async.AsyncMessageInvocation;
+import com.android.telephony.Rlog;
 
 /**
  * Invokes {@link CommandsInterface#iccCloseLogicalChannel(int, Message)}. This takes a channel id
diff --git a/src/java/com/android/internal/telephony/uicc/euicc/apdu/OpenLogicalChannelInvocation.java b/src/java/com/android/internal/telephony/uicc/euicc/apdu/OpenLogicalChannelInvocation.java
index 15b0c43..caed9cf 100644
--- a/src/java/com/android/internal/telephony/uicc/euicc/apdu/OpenLogicalChannelInvocation.java
+++ b/src/java/com/android/internal/telephony/uicc/euicc/apdu/OpenLogicalChannelInvocation.java
@@ -19,11 +19,11 @@
 import android.os.AsyncResult;
 import android.os.Message;
 import android.telephony.IccOpenLogicalChannelResponse;
-import android.telephony.Rlog;
 
 import com.android.internal.telephony.CommandException;
 import com.android.internal.telephony.CommandsInterface;
 import com.android.internal.telephony.uicc.euicc.async.AsyncMessageInvocation;
+import com.android.telephony.Rlog;
 
 /**
  * Invokes {@link CommandsInterface#iccOpenLogicalChannel(String, int, Message)}. This takes AID
diff --git a/src/java/com/android/internal/telephony/uicc/euicc/apdu/TransmitApduLogicalChannelInvocation.java b/src/java/com/android/internal/telephony/uicc/euicc/apdu/TransmitApduLogicalChannelInvocation.java
index 296fe47..09de54a 100644
--- a/src/java/com/android/internal/telephony/uicc/euicc/apdu/TransmitApduLogicalChannelInvocation.java
+++ b/src/java/com/android/internal/telephony/uicc/euicc/apdu/TransmitApduLogicalChannelInvocation.java
@@ -18,12 +18,12 @@
 
 import android.os.AsyncResult;
 import android.os.Message;
-import android.telephony.Rlog;
 
 import com.android.internal.telephony.CommandException;
 import com.android.internal.telephony.CommandsInterface;
 import com.android.internal.telephony.uicc.IccIoResult;
 import com.android.internal.telephony.uicc.euicc.async.AsyncMessageInvocation;
+import com.android.telephony.Rlog;
 
 /**
  * Invokes {@link CommandsInterface#iccTransmitApduLogicalChannel(int, int, int, int, int, int,
diff --git a/src/java/com/android/internal/telephony/uicc/euicc/async/AsyncResultCallback.java b/src/java/com/android/internal/telephony/uicc/euicc/async/AsyncResultCallback.java
index b28e7e7..58ab6bf 100644
--- a/src/java/com/android/internal/telephony/uicc/euicc/async/AsyncResultCallback.java
+++ b/src/java/com/android/internal/telephony/uicc/euicc/async/AsyncResultCallback.java
@@ -16,7 +16,7 @@
 
 package com.android.internal.telephony.uicc.euicc.async;
 
-import android.telephony.Rlog;
+import com.android.telephony.Rlog;
 
 /**
  * Class to deliver the returned value from an asynchronous call. Either {@link #onResult(Result)}
diff --git a/src/java/com/android/internal/telephony/util/LocaleUtils.java b/src/java/com/android/internal/telephony/util/LocaleUtils.java
new file mode 100644
index 0000000..064b10d
--- /dev/null
+++ b/src/java/com/android/internal/telephony/util/LocaleUtils.java
@@ -0,0 +1,216 @@
+/*
+ * Copyright (C) 2019 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.util;
+
+import android.content.Context;
+import android.icu.util.ULocale;
+import android.text.TextUtils;
+
+import com.android.internal.telephony.MccTable;
+import com.android.telephony.Rlog;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Locale;
+
+/**
+ * This class provides various util functions about Locale.
+ */
+public class LocaleUtils {
+
+    private static final String LOG_TAG = "LocaleUtils";
+
+    /**
+     * Get Locale based on the MCC of the SIM.
+     *
+     * @param context Context to act on.
+     * @param mcc Mobile Country Code of the SIM or SIM-like entity (build prop on CDMA)
+     * @param simLanguage (nullable) the language from the SIM records (if present).
+     *
+     * @return locale for the mcc or null if none
+     */
+    public static Locale getLocaleFromMcc(Context context, int mcc, String simLanguage) {
+        boolean hasSimLanguage = !TextUtils.isEmpty(simLanguage);
+        String language = hasSimLanguage ? simLanguage : defaultLanguageForMcc(mcc);
+        String country = MccTable.countryCodeForMcc(mcc);
+
+        Rlog.d(LOG_TAG, "getLocaleFromMcc(" + language + ", " + country + ", " + mcc);
+        final Locale locale = getLocaleForLanguageCountry(context, language, country);
+
+        // If we couldn't find a locale that matches the SIM language, give it a go again
+        // with the "likely" language for the given country.
+        if (locale == null && hasSimLanguage) {
+            language = defaultLanguageForMcc(mcc);
+            Rlog.d(LOG_TAG, "[retry ] getLocaleFromMcc(" + language + ", " + country + ", " + mcc);
+            return getLocaleForLanguageCountry(context, language, country);
+        }
+
+        return locale;
+    }
+
+    /**
+     * Return Locale for the language and country or null if no good match.
+     *
+     * @param context Context to act on.
+     * @param language Two character language code desired
+     * @param country Two character country code desired
+     *
+     * @return Locale or null if no appropriate value
+     */
+    private static Locale getLocaleForLanguageCountry(Context context, String language,
+                                                      String country) {
+        if (language == null) {
+            Rlog.d(LOG_TAG, "getLocaleForLanguageCountry: skipping no language");
+            return null; // no match possible
+        }
+        if (country == null) {
+            country = ""; // The Locale constructor throws if passed null.
+        }
+
+        final Locale target = new Locale(language, country);
+        try {
+            String[] localeArray = context.getAssets().getLocales();
+            List<String> locales = new ArrayList<>(Arrays.asList(localeArray));
+
+            // Even in developer mode, you don't want the pseudolocales.
+            locales.remove("ar-XB");
+            locales.remove("en-XA");
+
+            List<Locale> languageMatches = new ArrayList<>();
+            for (String locale : locales) {
+                final Locale l = Locale.forLanguageTag(locale.replace('_', '-'));
+
+                // Only consider locales with both language and country.
+                if (l == null || "und".equals(l.getLanguage())
+                        || l.getLanguage().isEmpty() || l.getCountry().isEmpty()) {
+                    continue;
+                }
+                if (l.getLanguage().equals(target.getLanguage())) {
+                    // If we got a perfect match, we're done.
+                    if (l.getCountry().equals(target.getCountry())) {
+                        Rlog.d(LOG_TAG, "getLocaleForLanguageCountry: got perfect match: "
+                                + l.toLanguageTag());
+                        return l;
+                    }
+
+                    // We've only matched the language, not the country.
+                    languageMatches.add(l);
+                }
+            }
+
+            if (languageMatches.isEmpty()) {
+                Rlog.d(LOG_TAG, "getLocaleForLanguageCountry: no locales for language " + language);
+                return null;
+            }
+
+            Locale bestMatch = lookupFallback(target, languageMatches);
+            if (bestMatch != null) {
+                Rlog.d(LOG_TAG, "getLocaleForLanguageCountry: got a fallback match: "
+                        + bestMatch.toLanguageTag());
+                return bestMatch;
+            } else {
+                // If a locale is "translated", it is selectable in setup wizard, and can therefore
+                // be considered a valid result for this method.
+                if (!TextUtils.isEmpty(target.getCountry())) {
+                    if (isTranslated(context, target)) {
+                        Rlog.d(LOG_TAG, "getLocaleForLanguageCountry: "
+                                + "target locale is translated: " + target);
+                        return target;
+                    }
+                }
+
+                // Somewhat arbitrarily take the first locale for the language,
+                // unless we get a perfect match later. Note that these come back in no
+                // particular order, so there's no reason to think the first match is
+                // a particularly good match.
+                Rlog.d(LOG_TAG, "getLocaleForLanguageCountry: got language-only match: "
+                        + language);
+                return languageMatches.get(0);
+            }
+        } catch (Exception e) {
+            Rlog.d(LOG_TAG, "getLocaleForLanguageCountry: exception", e);
+        }
+
+        return null;
+    }
+
+    /**
+     * Given a GSM Mobile Country Code, returns
+     * an ISO 2-3 character language code if available.
+     * Returns null if unavailable.
+     */
+    public static String defaultLanguageForMcc(int mcc) {
+        MccTable.MccEntry entry = MccTable.entryForMcc(mcc);
+        if (entry == null) {
+            Rlog.d(LOG_TAG, "defaultLanguageForMcc(" + mcc + "): no country for mcc");
+            return null;
+        }
+
+        final String country = entry.mIso;
+
+        // Choose English as the default language for India.
+        if ("in".equals(country)) {
+            return "en";
+        }
+
+        // Ask CLDR for the language this country uses...
+        ULocale likelyLocale = ULocale.addLikelySubtags(new ULocale("und", country));
+        String likelyLanguage = likelyLocale.getLanguage();
+        Rlog.d(LOG_TAG, "defaultLanguageForMcc(" + mcc + "): country " + country + " uses "
+                + likelyLanguage);
+        return likelyLanguage;
+    }
+
+    private static boolean isTranslated(Context context, Locale targetLocale) {
+        ULocale fullTargetLocale = ULocale.addLikelySubtags(ULocale.forLocale(targetLocale));
+        String language = fullTargetLocale.getLanguage();
+        String script = fullTargetLocale.getScript();
+
+        for (String localeId : context.getAssets().getLocales()) {
+            ULocale fullLocale = ULocale.addLikelySubtags(new ULocale(localeId));
+            if (language.equals(fullLocale.getLanguage())
+                    && script.equals(fullLocale.getScript())) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Finds a suitable locale among {@code candidates} to use as the fallback locale for
+     * {@code target}. This looks through the list of {@link MccTable#FALLBACKS},
+     * and follows the chain until a locale in {@code candidates} is found.
+     * This function assumes that {@code target} is not in {@code candidates}.
+     *
+     * TODO: This should really follow the CLDR chain of parent locales! That might be a bit
+     * of a problem because we don't really have an en-001 locale on android.
+     *
+     * @return The fallback locale or {@code null} if there is no suitable fallback defined in the
+     *         lookup.
+     */
+    private static Locale lookupFallback(Locale target, List<Locale> candidates) {
+        Locale fallback = target;
+        while ((fallback = MccTable.FALLBACKS.get(fallback)) != null) {
+            if (candidates.contains(fallback)) {
+                return fallback;
+            }
+        }
+
+        return null;
+    }
+}
diff --git a/src/java/com/android/internal/telephony/util/NotificationChannelController.java b/src/java/com/android/internal/telephony/util/NotificationChannelController.java
index 329f3c6..de1ddd3 100644
--- a/src/java/com/android/internal/telephony/util/NotificationChannelController.java
+++ b/src/java/com/android/internal/telephony/util/NotificationChannelController.java
@@ -70,14 +70,14 @@
         alertChannel.setSound(Settings.System.DEFAULT_NOTIFICATION_URI,
                 new AudioAttributes.Builder().setUsage(AudioAttributes.USAGE_NOTIFICATION).build());
         // allow users to block notifications from system
-        alertChannel.setBlockableSystem(true);
+        alertChannel.setBlockable(true);
 
         final NotificationChannel mobileDataStatusChannel = new NotificationChannel(
                 CHANNEL_ID_MOBILE_DATA_STATUS,
                 context.getText(R.string.notification_channel_mobile_data_status),
                 NotificationManager.IMPORTANCE_LOW);
         // allow users to block notifications from system
-        mobileDataStatusChannel.setBlockableSystem(true);
+        mobileDataStatusChannel.setBlockable(true);
 
         final NotificationChannel simChannel = new NotificationChannel(
                 CHANNEL_ID_SIM,
diff --git a/src/java/com/android/internal/telephony/vendor/VendorGsmCdmaPhone.java b/src/java/com/android/internal/telephony/vendor/VendorGsmCdmaPhone.java
new file mode 100644
index 0000000..0c92159
--- /dev/null
+++ b/src/java/com/android/internal/telephony/vendor/VendorGsmCdmaPhone.java
@@ -0,0 +1,175 @@
+/*
+ * Copyright (C) 2020 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.vendor;
+
+import android.content.Context;
+import android.os.AsyncResult;
+import android.os.Message;
+import android.telephony.PhoneNumberUtils;
+import android.telephony.Rlog;
+import android.telephony.ServiceState;
+import android.telephony.SubscriptionManager;
+
+import com.android.internal.telephony.CommandsInterface;
+import com.android.internal.telephony.GsmCdmaPhone;
+import com.android.internal.telephony.PhoneNotifier;
+import com.android.internal.telephony.TelephonyComponentFactory;
+
+public class VendorGsmCdmaPhone extends GsmCdmaPhone {
+    private static final String LOG_TAG = "VendorGsmCdmaPhone";
+    private static final int PROP_EVENT_START = EVENT_LAST;
+    private static final int DEFAULT_PHONE_INDEX = 0;
+
+    private boolean mIsPhoneReadySent = false;
+    private boolean mIsPhoneReadyPending = false;
+    private static int READY = 1;
+
+    public VendorGsmCdmaPhone(Context context,
+            CommandsInterface ci, PhoneNotifier notifier, int phoneId,
+            int precisePhoneType, TelephonyComponentFactory telephonyComponentFactory) {
+        this(context, ci, notifier, false, phoneId, precisePhoneType,
+                telephonyComponentFactory);
+    }
+
+    public VendorGsmCdmaPhone(Context context,
+            CommandsInterface ci, PhoneNotifier notifier, boolean unitTestMode, int phoneId,
+            int precisePhoneType, TelephonyComponentFactory telephonyComponentFactory) {
+        super(context, ci, notifier, unitTestMode, phoneId, precisePhoneType,
+                telephonyComponentFactory);
+        Rlog.d(LOG_TAG, "Constructor");
+    }
+
+    @Override
+    protected void phoneObjectUpdater(int newVoiceTech) {
+        super.phoneObjectUpdater(newVoiceTech);
+    }
+
+    @Override
+    public boolean getCallForwardingIndicator() {
+        if (!isCurrentSubValid()) {
+            return false;
+        }
+        return super.getCallForwardingIndicator();
+    }
+
+    private boolean isCurrentSubValid() {
+        boolean isUiccApplicationEnabled = true;
+        // FIXME get the SubscriptionManager.UICC_APPLICATIONS_ENABLED value and use it above
+
+        SubscriptionManager subscriptionManager = SubscriptionManager.from(mContext);
+
+        Rlog.d(LOG_TAG, "ProvisionStatus: " + isUiccApplicationEnabled + " phone id:" + mPhoneId);
+        return subscriptionManager.isActiveSubscriptionId(getSubId()) && isUiccApplicationEnabled;
+    }
+
+    public void fetchIMEI() {
+            Rlog.d(LOG_TAG, "fetching device id");
+            mCi.getDeviceIdentity(obtainMessage(EVENT_GET_DEVICE_IDENTITY_DONE));
+    }
+
+    @Override
+    public void handleMessage(Message msg) {
+        Rlog.d(LOG_TAG, "handleMessage: Event: " + msg.what);
+        AsyncResult ar;
+        switch(msg.what) {
+
+            case EVENT_SIM_RECORDS_LOADED:
+                if(isPhoneTypeGsm()) {
+                    Rlog.d(LOG_TAG, "notify call forward indication, phone id:" + mPhoneId);
+                    notifyCallForwardingIndicator();
+                }
+
+                super.handleMessage(msg);
+                break;
+
+            case EVENT_RADIO_AVAILABLE:
+                mIsPhoneReadySent = false;
+                super.handleMessage(msg);
+                break;
+
+            case EVENT_RIL_CONNECTED:
+                mIsPhoneReadySent = false;
+                super.handleMessage(msg);
+                break;
+
+            default: {
+                super.handleMessage(msg);
+            }
+
+        }
+    }
+
+    // In DSDA, char 'D' is used as DTMF char for playing supervisory tone for G/W.
+    // For CDMA, '#' is used. A, B, C & D are also supported as DTMF digits for G/W networks.
+    @Override
+    public void startDtmf(char c) {
+        if (!(PhoneNumberUtils.is12Key(c) || (c == 'D'))) {
+            Rlog.e(LOG_TAG, "startDtmf called with invalid character '" + c + "'");
+        } else {
+            if (isPhoneTypeCdma() && c == 'D') {
+                c = '#';
+            }
+            mCi.startDtmf(c, null);
+        }
+    }
+
+    // For CDMA sendBurstDtmf is used, if dtmf char is 'D' then it with '#'
+    // since 'D' is used for SCH tone and for CDMA it has to be '#'.
+    @Override
+    public void sendBurstDtmf(String dtmfString, int on, int off, Message onComplete) {
+        Character c = dtmfString.charAt(0);
+        if(dtmfString.length() == 1 && c == 'D') {
+            dtmfString = c.toString();
+        }
+        super.sendBurstDtmf(dtmfString, on, off, onComplete);
+    }
+
+    // When OOS occurs, IMS registration may be still available so that IMS service
+    // state is also in-service, then reports in-service to upper layer.
+    // Add a precondition to merge IMS service so that notifies proper service state
+    // after IMS changes RAT.
+    @Override
+    public ServiceState getServiceState() {
+        if (mSST == null || mSST.mSS.getState() != ServiceState.STATE_IN_SERVICE) {
+            // Ensure UE has IMS service capability, then merge IMS service state.
+            // Video enabled includes WIFI video
+            final boolean isImsEnabled = mImsPhone != null && (mImsPhone.isVolteEnabled()
+                    || mImsPhone.isVideoEnabled()
+                    || mImsPhone.isWifiCallingEnabled());
+            if (isImsEnabled) {
+                return ServiceState.mergeServiceStates(
+                        ((mSST == null) ? new ServiceState() : mSST.mSS),
+                        mImsPhone.getServiceState());
+            }
+        }
+
+        if (mSST != null) {
+            return mSST.mSS;
+        } else {
+            // avoid potential NPE in EmergencyCallHelper during Phone switch
+            return new ServiceState();
+        }
+    }
+
+    private void logd(String msg) {
+        Rlog.d(LOG_TAG, "[" + mPhoneId +" ] " + msg);
+    }
+
+    private void loge(String msg) {
+        Rlog.e(LOG_TAG, "[" + mPhoneId +" ] " + msg);
+    }
+}
diff --git a/src/java/com/android/internal/telephony/vendor/VendorMultiSimSettingController.java b/src/java/com/android/internal/telephony/vendor/VendorMultiSimSettingController.java
new file mode 100644
index 0000000..026b6f5
--- /dev/null
+++ b/src/java/com/android/internal/telephony/vendor/VendorMultiSimSettingController.java
@@ -0,0 +1,112 @@
+/*
+ * Copyright (C) 2020 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.vendor;
+
+import android.content.Context;
+import android.provider.Settings;
+import android.provider.Settings.SettingNotFoundException;
+import android.telephony.SubscriptionInfo;
+import android.util.Log;
+import com.android.internal.telephony.GlobalSettingsHelper;
+import com.android.internal.telephony.MultiSimSettingController;
+import com.android.internal.telephony.SubscriptionController;
+import com.android.internal.telephony.Phone;
+import com.android.internal.telephony.PhoneFactory;
+
+import java.util.List;
+import java.util.stream.Collectors;
+
+/*
+ * Extending VendorMultiSimSettingController to override default
+ * behavior for mobile data
+ */
+public class VendorMultiSimSettingController extends MultiSimSettingController {
+
+    private static final String LOG_TAG = "VendorMultiSimSettingController";
+
+    public static MultiSimSettingController init(Context context, SubscriptionController sc) {
+        synchronized (VendorMultiSimSettingController.class) {
+            if (sInstance == null) {
+                sInstance = new VendorMultiSimSettingController(context,
+                        SubscriptionController.getInstance());
+            } else {
+                Log.wtf(LOG_TAG, "init() called multiple times!  sInstance = " + sInstance);
+            }
+        }
+        return sInstance;
+    }
+
+    private VendorMultiSimSettingController(Context context, SubscriptionController sc) {
+        super(context, sc);
+    }
+
+    public static VendorMultiSimSettingController getInstance() {
+        return (VendorMultiSimSettingController)sInstance;
+    }
+
+    @Override
+    protected void disableDataForNonDefaultNonOpportunisticSubscriptions() {
+        log("disableDataForNonDefaultNonOpportunisticSubscriptions - do nothing");
+    }
+
+    protected synchronized void onUserDataEnabled(int subId, boolean enable) {
+        log("onUserDataEnabled");
+        // Make sure MOBILE_DATA of subscriptions in same group are synced.
+        setUserDataEnabledForGroup(subId, enable);
+    }
+
+    /**
+     * Make sure MOBILE_DATA of subscriptions in the same group with the subId
+     * are synced.
+     */
+    @Override
+    protected synchronized void setUserDataEnabledForGroup(int subId, boolean enable) {
+        log("setUserDataEnabledForGroup subId " + subId + " enable " + enable);
+        List<SubscriptionInfo> infoList = mSubController.getSubscriptionsInGroup(
+                mSubController.getGroupUuid(subId), mContext.getOpPackageName(),
+                null);
+
+        if (infoList == null) return;
+
+        for (SubscriptionInfo info : infoList) {
+            int currentSubId = info.getSubscriptionId();
+            if (currentSubId == subId) continue;
+            // TODO: simplify when setUserDataEnabled becomes singleton
+            if (mSubController.isActiveSubId(currentSubId)) {
+                // For active subscription, call setUserDataEnabled through DataEnabledSettings.
+                Phone phone = PhoneFactory.getPhone(mSubController.getPhoneId(currentSubId));
+                if (phone != null) {
+                    phone.getDataEnabledSettings().setUserDataEnabled(enable);
+                }
+            } else {
+                // For inactive subscription, directly write into global settings.
+                GlobalSettingsHelper.setBoolean(
+                        mContext, Settings.Global.MOBILE_DATA, currentSubId, enable);
+            }
+        }
+   }
+
+   @Override
+   protected void updateDefaults() {
+        log("updateDefaults");
+
+   }
+
+    protected void log(String msg) {
+        Log.d(LOG_TAG, msg);
+    }
+}
diff --git a/src/java/com/android/internal/telephony/vendor/VendorPhoneSwitcher.java b/src/java/com/android/internal/telephony/vendor/VendorPhoneSwitcher.java
new file mode 100644
index 0000000..d12ea33
--- /dev/null
+++ b/src/java/com/android/internal/telephony/vendor/VendorPhoneSwitcher.java
@@ -0,0 +1,656 @@
+/*
+ * Copyright (C) 2020 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.vendor;
+
+import static android.telephony.SubscriptionManager.DEFAULT_SUBSCRIPTION_ID;
+import static android.telephony.SubscriptionManager.INVALID_PHONE_INDEX;
+import static android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID;
+import static android.telephony.TelephonyManager.RADIO_POWER_UNAVAILABLE;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.net.NetworkCapabilities;
+import android.net.NetworkRequest;
+import android.os.AsyncResult;
+import android.os.Looper;
+import android.os.Message;
+import android.os.Registrant;
+import android.os.SystemProperties;
+import android.telephony.data.ApnSetting;
+import android.telephony.Rlog;
+import android.telephony.SubscriptionManager;
+
+import android.text.TextUtils;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.telephony.CallManager;
+import com.android.internal.telephony.Call;
+import com.android.internal.telephony.CommandsInterface;
+import com.android.internal.telephony.dataconnection.DcRequest;
+import com.android.internal.telephony.dataconnection.DataEnabledSettings;
+import com.android.internal.telephony.GsmCdmaCall;
+import com.android.internal.telephony.imsphone.ImsPhone;
+import com.android.internal.telephony.imsphone.ImsPhoneCall;
+import com.android.internal.telephony.ITelephonyRegistry;
+import com.android.internal.telephony.IccCardConstants;
+import com.android.internal.telephony.Phone;
+import com.android.internal.telephony.PhoneConstants;
+import com.android.internal.telephony.PhoneFactory;
+import com.android.internal.telephony.PhoneSwitcher;
+import com.android.internal.telephony.SubscriptionController;
+import com.android.internal.telephony.TelephonyIntents;
+
+import java.lang.Integer;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.util.ArrayList;
+import java.util.List;
+import android.net.StringNetworkSpecifier;
+import android.net.NetworkSpecifier;
+
+public class VendorPhoneSwitcher extends PhoneSwitcher {
+
+    private final int MAX_CONNECT_FAILURE_COUNT = 5;
+    private final int[] mRetryArray =  new int []{5,10,20,40,60};
+    private int[] mAllowDataFailure;
+    private boolean[] mDdsRequestSent;
+    private boolean mManualDdsSwitch = false;
+    private int mDefaultDataPhoneId = -1;
+    private String [] mSimStates;
+    private List<Integer> mNewActivePhones;
+    private boolean mWaitForDetachResponse = false;
+    private DdsSwitchState mDdsSwitchState = DdsSwitchState.NONE;
+    private final int USER_INITIATED_SWITCH = 0;
+    private final int NONUSER_INITIATED_SWITCH = 1;
+    private final String PROPERTY_TEMP_DDSSWITCH = "persist.vendor.radio.enable_temp_dds";
+    private final GsmCdmaCall[] mFgCsCalls;
+    private final GsmCdmaCall[] mBgCsCalls;
+    private final GsmCdmaCall[] mRiCsCalls;
+    private final ImsPhone[] mImsPhones;
+    private final ImsPhoneCall[] mFgImsCalls;
+    private final ImsPhoneCall[] mBgImsCalls;
+    private final ImsPhoneCall[] mRiImsCalls;
+
+    private final int EVENT_ALLOW_DATA_FALSE_RESPONSE  = 201;
+    private final int EVENT_ALLOW_DATA_TRUE_RESPONSE   = 202;
+    private final int EVENT_DDS_SWITCH_RESPONSE        = 203;
+    private final int EVENT_PREFERRED_SUB_VALID        = 204;
+
+    private enum DdsSwitchState {
+        NONE, REQUIRED, DONE
+    }
+
+    public VendorPhoneSwitcher(int maxActivePhones, Context context, Looper looper) {
+        super (maxActivePhones, context, looper);
+        mAllowDataFailure = new int[mActiveModemCount];
+        mDdsRequestSent = new boolean[mActiveModemCount];
+        mSimStates = new String[mActiveModemCount];
+        IntentFilter filter = new IntentFilter();
+        filter.addAction(TelephonyIntents.ACTION_SIM_STATE_CHANGED);
+        mContext.registerReceiver(mSimStateIntentReceiver, filter);
+
+        mImsPhones = new ImsPhone[mActiveModemCount];
+        mFgCsCalls = new GsmCdmaCall[mActiveModemCount];
+        mBgCsCalls = new GsmCdmaCall[mActiveModemCount];
+        mRiCsCalls = new GsmCdmaCall[mActiveModemCount];
+        mFgImsCalls = new ImsPhoneCall[mActiveModemCount];
+        mBgImsCalls = new ImsPhoneCall[mActiveModemCount];
+        mRiImsCalls = new ImsPhoneCall[mActiveModemCount];
+
+        for (int i=0; i < mActiveModemCount; i++) {
+            if (PhoneFactory.getPhone(i) != null) {
+                mFgCsCalls[i] = (GsmCdmaCall) PhoneFactory.getPhone(i).getForegroundCall();
+                mBgCsCalls[i] = (GsmCdmaCall) PhoneFactory.getPhone(i).getBackgroundCall();
+                mRiCsCalls[i] = (GsmCdmaCall) PhoneFactory.getPhone(i).getRingingCall();
+            }
+            mImsPhones[i] = (ImsPhone)PhoneFactory.getPhone(i).getImsPhone();
+            if (mImsPhones[i] != null) {
+                mFgImsCalls[i] = mImsPhones[i].getForegroundCall();
+                mBgImsCalls[i] = mImsPhones[i].getBackgroundCall();
+                mRiImsCalls[i] = mImsPhones[i].getRingingCall();
+            }
+
+            mDdsRequestSent[i] = false;
+        }
+    }
+
+    public static VendorPhoneSwitcher make(int maxActivePhones, Context context, Looper looper) {
+        if (sPhoneSwitcher == null) {
+            sPhoneSwitcher = new VendorPhoneSwitcher(maxActivePhones, context, looper);
+        }
+
+        return (VendorPhoneSwitcher)sPhoneSwitcher;
+    }
+
+    private BroadcastReceiver mSimStateIntentReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            String action = intent.getAction();
+            if (action.equals(TelephonyIntents.ACTION_SIM_STATE_CHANGED)) {
+                String value = intent.getStringExtra(IccCardConstants.INTENT_KEY_ICC_STATE);
+                int phoneId = intent.getIntExtra(PhoneConstants.PHONE_KEY,
+                        SubscriptionManager.INVALID_PHONE_INDEX);
+                log("mSimStateIntentReceiver: phoneId = " + phoneId + " value = " + value);
+                if (SubscriptionManager.isValidPhoneId(phoneId)) {
+                    mSimStates[phoneId] = value;
+                    // If SIM is absent, allow DDS request always, which avoids DDS switch
+                    // can't be completed in the no-SIM case because the sent status of the
+                    // old preferred phone has no chance to reset in hot-swap
+                    if (IccCardConstants.INTENT_VALUE_ICC_ABSENT.equals(value)) {
+                        mDdsRequestSent[phoneId] = false;
+                    }
+                }
+
+                if (isSimReady(phoneId) && (getConnectFailureCount(phoneId) > 0)) {
+                    sendRilCommands(phoneId);
+                }
+            }
+        }
+    };
+
+    @Override
+    public void handleMessage(Message msg) {
+        final int ddsSubId = mSubscriptionController.getDefaultDataSubId();
+        final int ddsPhoneId = mSubscriptionController.getPhoneId(ddsSubId);
+
+        log("handle event - " + msg.what);
+        AsyncResult ar = null;
+        switch (msg.what) {
+            case EVENT_SUBSCRIPTION_CHANGED: {
+                if (mHalCommandToUse == HAL_COMMAND_UNKNOWN) {
+                    log("EVENT_SUBSCRIPTION_CHANGED: update HAL command");
+                    mHalCommandToUse = mRadioConfig.isSetPreferredDataCommandSupported()
+                            ? HAL_COMMAND_PREFERRED_DATA : HAL_COMMAND_ALLOW_DATA;
+                }
+                onEvaluate(REQUESTS_UNCHANGED, "subChanged");
+                break;
+            }
+            case EVENT_PRECISE_CALL_STATE_CHANGED: {
+                log("EVENT_PRECISE_CALL_STATE_CHANGED");
+                if (!isAnyVoiceCallActiveOnDevice()) {
+                    for (int i = 0; i < mActiveModemCount; i++) {
+                        if ((getConnectFailureCount(i) > 0) &&
+                                isPhoneIdValidForRetry(i)) {
+                            sendRilCommands(i);
+                            break;
+                        }
+                    }
+                }
+                super.handleMessage(msg);
+                break;
+            }
+            case EVENT_ALLOW_DATA_TRUE_RESPONSE: {
+                log("EVENT_ALLOW_DATA_TRUE_RESPONSE");
+                onDdsSwitchResponse(msg.arg1, (AsyncResult)msg.obj);
+                break;
+            }
+            case EVENT_ALLOW_DATA_FALSE_RESPONSE: {
+                log("EVENT_ALLOW_DATA_FALSE_RESPONSE");
+                mWaitForDetachResponse = false;
+                for (int phoneId : mNewActivePhones) {
+                    activate(phoneId);
+                }
+                if (mNewActivePhones.contains(ddsPhoneId)) {
+                    mManualDdsSwitch = false;
+                }
+                break;
+            }
+            case EVENT_DDS_SWITCH_RESPONSE: {
+                log("EVENT_DDS_SWITCH_RESPONSE");
+                onDdsSwitchResponse(msg.arg1, (AsyncResult)msg.obj);
+                break;
+            }
+            case EVENT_PREFERRED_SUB_VALID: {
+                log("EVENT_PREFERRED_SUB_VALID");
+                notifyDdsSwitchDone();
+                break;
+            }
+            default:
+                super.handleMessage(msg);
+        }
+    }
+
+    private boolean isSimReady(int phoneId) {
+        if (phoneId == SubscriptionManager.INVALID_PHONE_INDEX) {
+            return false;
+        }
+
+        if (IccCardConstants.INTENT_VALUE_ICC_READY.equals(mSimStates[phoneId]) ||
+                IccCardConstants.INTENT_VALUE_ICC_LOADED.equals(mSimStates[phoneId]) ||
+                IccCardConstants.INTENT_VALUE_ICC_IMSI.equals(mSimStates[phoneId])) {
+            log("SIM READY for phoneId: " + phoneId);
+            return true;
+        } else {
+            return false;
+        }
+    }
+
+    @Override
+    protected boolean onEvaluate(boolean requestsChanged, String reason) {
+        StringBuilder sb = new StringBuilder(reason);
+
+        boolean diffDetected = requestsChanged;
+
+        // Check if user setting of default non-opportunistic data sub is changed.
+        final int primaryDataSubId = mSubscriptionController.getDefaultDataSubId();
+        final int ddsPhoneId = mSubscriptionController.getPhoneId(primaryDataSubId);
+        if (primaryDataSubId != mPrimaryDataSubId) {
+            sb.append(" mPrimaryDataSubId ").append(mPrimaryDataSubId).append("->")
+                .append(primaryDataSubId);
+            mManualDdsSwitch = true;
+            mPrimaryDataSubId = primaryDataSubId;
+        }
+
+        // Check to see if there is any active subscription on any phone
+        boolean hasAnyActiveSubscription = false;
+        boolean hasSubRefreshedOnThePreferredPhone = false;
+
+        // Check if phoneId to subId mapping is changed.
+        for (int i = 0; i < mActiveModemCount; i++) {
+            int sub = mSubscriptionController.getSubIdUsingPhoneId(i);
+
+            if (SubscriptionManager.isValidSubscriptionId(sub)) hasAnyActiveSubscription = true;
+
+            if (sub != mPhoneSubscriptions[i]) {
+                sb.append(" phone[").append(i).append("] ").append(mPhoneSubscriptions[i]);
+                sb.append("->").append(sub);
+                if (SubscriptionManager.isValidSubscriptionId(mPreferredDataSubId.get())
+                        && mPhoneSubscriptions[i] == mPreferredDataSubId.get()) {
+                    sb.append("sub refreshed");
+                    hasSubRefreshedOnThePreferredPhone = true;
+                }
+                mPhoneSubscriptions[i] = sub;
+                diffDetected = true;
+            }
+        }
+
+        if (!hasAnyActiveSubscription) {
+            transitionToEmergencyPhone();
+        } else {
+            if (VDBG) log("Found an active subscription");
+        }
+        final boolean isOldPeferredDataSubValid =
+                SubscriptionManager.isValidSubscriptionId(mPreferredDataSubId.get());
+        // Check if phoneId for preferred data is changed.
+        int oldPreferredDataPhoneId = mPreferredDataPhoneId;
+
+        // When there are no subscriptions, the preferred data phone ID is invalid, but we want
+        // to keep a valid phoneId for Emergency, so skip logic that updates for preferred data
+        // phone ID. Ideally there should be a single set of checks that evaluate the correct
+        // phoneId on a service-by-service basis (EIMS being one), but for now... just bypass
+        // this logic in the no-SIM case.
+        if (hasAnyActiveSubscription) updatePreferredDataPhoneId();
+
+        final boolean isPeferredDataSubValid =
+                SubscriptionManager.isValidSubscriptionId(mPreferredDataSubId.get());
+
+        if(!isOldPeferredDataSubValid && isPeferredDataSubValid) {
+            // To avoid race condition, I'd like to send a msg in OnEvalute
+            // This is used to ensure informing active phones again after the preferred
+            // SUB is valid
+            sendEmptyMessage(EVENT_PREFERRED_SUB_VALID);
+        }
+
+        if (oldPreferredDataPhoneId != mPreferredDataPhoneId) {
+            sb.append(" preferred phoneId ").append(oldPreferredDataPhoneId)
+                    .append("->").append(mPreferredDataPhoneId);
+            if (SubscriptionManager.isValidPhoneId(oldPreferredDataPhoneId)) {
+                mDdsRequestSent[oldPreferredDataPhoneId] = false;
+            }
+            mDdsSwitchState = DdsSwitchState.REQUIRED;
+            diffDetected = true;
+        } else if (hasSubRefreshedOnThePreferredPhone) {
+            // Tell connectivity the real active data phone
+            notifyPreferredDataSubIdChanged();
+        }
+
+        if (diffDetected) {
+            log("evaluating due to " + sb.toString());
+            if (mHalCommandToUse == HAL_COMMAND_PREFERRED_DATA) {
+                // With HAL_COMMAND_PREFERRED_DATA, all phones are assumed to allow PS attach.
+                // So marking all phone as active.
+                for (int phoneId = 0; phoneId < mActiveModemCount; phoneId++) {
+                    activate(phoneId);
+                }
+                sendRilCommands(mPreferredDataPhoneId);
+            } else {
+                List<Integer> newActivePhones = new ArrayList<Integer>();
+
+                for (DcRequest dcRequest : mPrioritizedDcRequests) {
+                    int phoneIdForRequest = phoneIdForRequest(dcRequest.networkRequest,
+                            dcRequest.apnType);
+                    if (phoneIdForRequest == INVALID_PHONE_INDEX) continue;
+                    if (newActivePhones.contains(phoneIdForRequest)) continue;
+                    newActivePhones.add(phoneIdForRequest);
+                    if (newActivePhones.size() >= mMaxDataAttachModemCount) break;
+                }
+
+                if (VDBG) {
+                    log("default subId = " + mPrimaryDataSubId);
+                    log("preferred subId = " + mPreferredDataSubId.get());
+                    for (int i = 0; i < mActiveModemCount; i++) {
+                        log(" phone[" + i + "] using sub[" + mPhoneSubscriptions[i] + "]");
+                    }
+                    log(" newActivePhones:");
+                    for (Integer i : newActivePhones) log("  " + i);
+                }
+
+                mNewActivePhones = newActivePhones;
+                for (int phoneId = 0; (phoneId < mActiveModemCount); phoneId++) {
+                    if (!newActivePhones.contains(phoneId)) {
+                        deactivate(phoneId);
+                    }
+                }
+                if (!mWaitForDetachResponse) {
+                    // only activate phones up to the limit
+                    final boolean activateDdsPhone = mNewActivePhones.contains(ddsPhoneId);
+                    if (activateDdsPhone && mManualDdsSwitch) {
+                        activate(ddsPhoneId);
+                    } else {
+                        for (int phoneId : newActivePhones) {
+                            activate(phoneId);
+                        }
+                    }
+                    if (activateDdsPhone) {
+                        mManualDdsSwitch = false;
+                    }
+                }
+            }
+        }
+
+        return diffDetected;
+    }
+
+    /* Determine the phone id on which PS attach needs to be done
+     */
+    protected int phoneIdForRequest(NetworkRequest netRequest, int apnType) {
+        int subId = getSubIdFromNetworkSpecifier(netRequest.networkCapabilities
+                .getNetworkSpecifier());
+
+        if (subId == DEFAULT_SUBSCRIPTION_ID) return mPreferredDataPhoneId;
+        if (subId == INVALID_SUBSCRIPTION_ID) return INVALID_PHONE_INDEX;
+
+        int preferredDataSubId = SubscriptionManager.isValidPhoneId(mPreferredDataPhoneId)
+                ? mPhoneSubscriptions[mPreferredDataPhoneId] : INVALID_SUBSCRIPTION_ID;
+        // Currently we assume multi-SIM devices will only support one Internet PDN connection. So
+        // if Internet PDN is established on the non-preferred phone, it will interrupt
+        // Internet connection on the preferred phone. So we only accept Internet request with
+        // preferred data subscription or no specified subscription.
+        if (netRequest.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
+                && netRequest.hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED)
+                && subId != preferredDataSubId && subId != mValidator.getSubIdInValidation()) {
+            // Returning INVALID_PHONE_INDEX will result in netRequest not being handled.
+            return INVALID_PHONE_INDEX;
+        }
+
+        // This is for Volte+PS case
+        if ((ApnSetting.TYPE_IMS == apnType) && mManualDdsSwitch
+                && mMaxDataAttachModemCount != mActiveModemCount) {
+            subId = mPrimaryDataSubId;
+        }
+
+        // Try to find matching phone ID. If it doesn't exist, we'll end up returning INVALID.
+        int phoneId = INVALID_PHONE_INDEX;
+        for (int i = 0; i < mActiveModemCount; i++) {
+            if (mPhoneSubscriptions[i] == subId) {
+                phoneId = i;
+                break;
+            }
+        }
+        return phoneId;
+    }
+
+    protected boolean isUiccProvisioned(int phoneId) {
+        boolean isUiccApplicationEnabled = true;
+        // FIXME get the SubscriptionManager.UICC_APPLICATIONS_ENABLED value and use it here
+        log("isUiccProvisioned: status= " + isUiccApplicationEnabled + " phoneid=" + phoneId);
+        return mSubscriptionController.isActiveSubId(mPhoneSubscriptions[phoneId]) && isUiccApplicationEnabled; 
+    }
+
+    @Override
+    protected void deactivate(int phoneId) {
+        PhoneState state = mPhoneStates[phoneId];
+        if (state.active == false) {
+            return;
+        }
+        state.active = false;
+        log("deactivate " + phoneId);
+        state.lastRequested = System.currentTimeMillis();
+        if (mHalCommandToUse == HAL_COMMAND_ALLOW_DATA || mHalCommandToUse == HAL_COMMAND_UNKNOWN) {
+            if (mSubscriptionController.isActiveSubId(mPhoneSubscriptions[phoneId])) {
+                PhoneFactory.getPhone(phoneId).mCi.setDataAllowed(false,
+                        obtainMessage(EVENT_ALLOW_DATA_FALSE_RESPONSE));
+                mWaitForDetachResponse = true;
+            }
+        }
+    }
+
+    @Override
+    protected void activate(int phoneId) {
+        PhoneState state = mPhoneStates[phoneId];
+        if ((state.active == true) && !mManualDdsSwitch &&
+                (getConnectFailureCount(phoneId) == 0)) return;
+        state.active = true;
+        log("activate " + phoneId);
+        state.lastRequested = System.currentTimeMillis();
+        if (mHalCommandToUse == HAL_COMMAND_ALLOW_DATA || mHalCommandToUse == HAL_COMMAND_UNKNOWN) {
+            PhoneFactory.getPhone(phoneId).mCi.setDataAllowed(true,
+                    obtainMessage(EVENT_ALLOW_DATA_TRUE_RESPONSE, phoneId, 0));
+        }
+    }
+
+    @Override
+    protected void sendRilCommands(int phoneId) {
+        if (!SubscriptionManager.isValidPhoneId(phoneId) || phoneId >= mActiveModemCount) {
+            log("sendRilCommands: skip dds switch due to invalid phoneid=" + phoneId);
+            return;
+        }
+
+        if (mHalCommandToUse == HAL_COMMAND_ALLOW_DATA || mHalCommandToUse == HAL_COMMAND_UNKNOWN) {
+            PhoneFactory.getPhone(phoneId).mCi.setDataAllowed(isPhoneActive(phoneId),
+                    obtainMessage(isPhoneActive(phoneId) ? EVENT_ALLOW_DATA_TRUE_RESPONSE
+                    : EVENT_ALLOW_DATA_FALSE_RESPONSE, phoneId, 0));
+        } else if (phoneId == mPreferredDataPhoneId) {
+            if (!mDdsRequestSent[phoneId]) {
+                // Only setPreferredDataModem if the phoneId equals to current mPreferredDataPhoneId
+                log("sendRilCommands: setPreferredDataModem - phoneId: " + phoneId);
+                mRadioConfig.setPreferredDataModem(phoneId,
+                        obtainMessage(EVENT_DDS_SWITCH_RESPONSE, phoneId, 0));
+                mDdsRequestSent[phoneId] = true;
+            } else {
+                log("sendRilCommands: setPreferredDataModem request already sent on phoneId: " +
+                        phoneId);
+            }
+        }
+    }
+
+    /*
+     * Method to check if any of the calls are started
+     */
+    @Override
+    protected boolean isPhoneInVoiceCall(Phone phone) {
+        if (phone == null) {
+            return false;
+        }
+        boolean dataDuringCallsEnabled = false;
+        DataEnabledSettings dataEnabledSettings = phone.getDataEnabledSettings();
+        if (dataEnabledSettings != null) {
+            dataDuringCallsEnabled = dataEnabledSettings.isDataAllowedInVoiceCall();
+        }
+        if (!dataDuringCallsEnabled) {
+            log("isPhoneInVoiceCall: dataDuringCallsEnabled=" + dataDuringCallsEnabled);
+            return false;
+        }
+        int phoneId = phone.getPhoneId();
+        return (mFgCsCalls[phoneId].getState().isAlive() ||
+                mBgCsCalls[phoneId].getState().isAlive() ||
+                mRiCsCalls[phoneId].getState().isAlive() ||
+                mFgImsCalls[phoneId].getState().isAlive() ||
+                mBgImsCalls[phoneId].getState().isAlive() ||
+                mRiImsCalls[phoneId].getState().isAlive());
+    }
+
+    private void resetConnectFailureCount(int phoneId) {
+        mAllowDataFailure[phoneId] = 0;
+    }
+
+    private void incConnectFailureCount(int phoneId) {
+        mAllowDataFailure[phoneId]++;
+    }
+
+    @VisibleForTesting
+    public int getConnectFailureCount(int phoneId) {
+        return mAllowDataFailure[phoneId];
+    }
+
+    private void handleConnectMaxFailure(int phoneId) {
+        resetConnectFailureCount(phoneId);
+        int ddsSubId = mSubscriptionController.getDefaultDataSubId();
+        int ddsPhoneId = mSubscriptionController.getPhoneId(ddsSubId);
+        if (SubscriptionManager.isValidPhoneId(ddsPhoneId) && phoneId != ddsPhoneId) {
+            log("ALLOW_DATA retries exhausted on phoneId = " + phoneId);
+            enforceDds(ddsPhoneId);
+        }
+    }
+
+    private void enforceDds(int phoneId) {
+        int[] subId = mSubscriptionController.getSubId(phoneId);
+        log("enforceDds: subId = " + subId[0]);
+        mSubscriptionController.setDefaultDataSubId(subId[0]);
+    }
+
+    private boolean isAnyVoiceCallActiveOnDevice() {
+        boolean ret = (CallManager.getInstance().getState() != PhoneConstants.State.IDLE);
+        log("isAnyVoiceCallActiveOnDevice: " + ret);
+        return ret;
+    }
+
+    private void onDdsSwitchResponse(int phoneId, AsyncResult ar) {
+        if (ar.exception != null) {
+            mDdsRequestSent[phoneId] = false;
+            incConnectFailureCount(phoneId);
+            log("Dds switch failed on phoneId = " + phoneId + ", failureCount = "
+                    + getConnectFailureCount(phoneId));
+
+            if (isAnyVoiceCallActiveOnDevice()) {
+                boolean isTempSwitchPropEnabled = SystemProperties.getBoolean(
+                        PROPERTY_TEMP_DDSSWITCH, false);
+                int ddsPhoneId = mSubscriptionController.getPhoneId(
+                        mSubscriptionController.getDefaultDataSubId());
+                log("onDdsSwitchResponse: isTempSwitchPropEnabled=" + isTempSwitchPropEnabled +
+                        ", ddsPhoneId=" + ddsPhoneId + ", mPreferredDataPhoneId=" +
+                        mPreferredDataPhoneId);
+                if (isTempSwitchPropEnabled && (phoneId != ddsPhoneId) &&
+                        getConnectFailureCount(phoneId) < MAX_CONNECT_FAILURE_COUNT) {
+                    log("Retry Temporary DDS switch on phoneId:" + phoneId);
+                    sendRilCommands(phoneId);
+                } else {
+                    /* Any DDS retry while voice call is active is in vain
+                       Wait for call to get disconnected */
+                    log("Wait for call end indication");
+                }
+                return;
+            }
+
+            if (!isSimReady(phoneId)) {
+                /* If there is a attach failure due to sim not ready then
+                hold the retry until sim gets ready */
+                log("Wait for SIM to get READY");
+                return;
+            }
+
+            int ddsSwitchFailureCount = getConnectFailureCount(phoneId);
+            if (ddsSwitchFailureCount > MAX_CONNECT_FAILURE_COUNT) {
+                handleConnectMaxFailure(phoneId);
+            } else {
+                int retryDelay = mRetryArray[ddsSwitchFailureCount - 1] * 1000;
+                log("Scheduling DDS switch retry after: " + retryDelay);
+                postDelayed(new Runnable() {
+                    @Override
+                    public void run() {
+                        log("Running DDS switch retry");
+                        if (isPhoneIdValidForRetry(phoneId)) {
+                            sendRilCommands(phoneId);
+                        } else {
+                            log("Abandon DDS switch retry");
+                            resetConnectFailureCount(phoneId);
+                        }
+                    }}, retryDelay);
+                }
+        } else {
+            log("DDS switch success on phoneId = " + phoneId);
+            resetConnectFailureCount(phoneId);
+            if (mDdsSwitchState == DdsSwitchState.REQUIRED) {
+                mDdsSwitchState = DdsSwitchState.DONE;
+            }
+            notifyDdsSwitchDone();
+        }
+    }
+
+    private void notifyDdsSwitchDone() {
+        log("notifyDdsSwitchDone on the preferred data SUB = " + mPreferredDataSubId.get()
+                + " and the preferred phone ID = " + mPreferredDataPhoneId);
+        // Notify all registrants.
+        mActivePhoneRegistrants.notifyRegistrants();
+        notifyPreferredDataSubIdChanged();
+
+        if (mDdsSwitchState == DdsSwitchState.DONE
+                && SubscriptionManager.isValidSubscriptionId(mPreferredDataSubId.get())) {
+            mDdsSwitchState = mDdsSwitchState.NONE;
+            Intent intent = new Intent(
+                    "org.codeaurora.intent.action.ACTION_DDS_SWITCH_DONE");
+            intent.putExtra(PhoneConstants.SUBSCRIPTION_KEY, mPreferredDataSubId.get());
+            intent.addFlags(Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
+            log("Broadcast dds switch done intent on " + mPreferredDataSubId.get());
+            mContext.sendBroadcast(intent);
+        }
+    }
+
+    private boolean isPhoneIdValidForRetry(int phoneId) {
+        boolean isValid = false;
+        int phoneIdForRequest = INVALID_PHONE_INDEX;
+        int ddsPhoneId = mSubscriptionController.getPhoneId(
+                mSubscriptionController.getDefaultDataSubId());
+        if (ddsPhoneId != INVALID_PHONE_INDEX && ddsPhoneId == phoneId) {
+            isValid = true;
+        } else {
+            if (mPrioritizedDcRequests.size() > 0) {
+                for (int i = 0; i < mMaxDataAttachModemCount; i++) {
+                    DcRequest dcRequest = mPrioritizedDcRequests.get(i);
+                    if (dcRequest != null) {
+                        phoneIdForRequest = phoneIdForRequest(dcRequest.networkRequest,
+                                dcRequest.apnType);
+                        if (phoneIdForRequest == phoneId) {
+                            isValid = true;
+                            break;
+                        }
+                    }
+                }
+            }
+        }
+        return isValid;
+    }
+
+    /*
+     * Returns true if mPhoneIdInVoiceCall is set for active calls
+     */
+    private boolean isCallInProgress() {
+        return SubscriptionManager.isValidPhoneId(mPhoneIdInVoiceCall);
+    }
+}
diff --git a/src/java/com/android/internal/telephony/vendor/VendorServiceStateTracker.java b/src/java/com/android/internal/telephony/vendor/VendorServiceStateTracker.java
new file mode 100644
index 0000000..de9d42c
--- /dev/null
+++ b/src/java/com/android/internal/telephony/vendor/VendorServiceStateTracker.java
@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) 2020 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.vendor;
+
+import android.content.ActivityNotFoundException;
+import android.content.Intent;
+import android.os.AsyncResult;
+import android.os.Message;
+import android.telephony.NetworkRegistrationInfo;
+import android.telephony.TelephonyManager;
+
+import com.android.internal.telephony.CommandsInterface;
+import com.android.internal.telephony.GsmCdmaPhone;
+import com.android.internal.telephony.PhoneConstants;
+import com.android.internal.telephony.ServiceStateTracker;
+
+public class VendorServiceStateTracker extends ServiceStateTracker {
+    private static final String LOG_TAG = "VendorServiceStateTracker";
+    private static final boolean DBG = true;
+    private static final boolean VDBG = false;  // STOPSHIP if true
+    private static final String ACTION_MANAGED_ROAMING_IND =
+            "android.intent.action.ACTION_MANAGED_ROAMING_IND";
+
+    public VendorServiceStateTracker(GsmCdmaPhone phone, CommandsInterface ci) {
+        super(phone,ci);
+    }
+
+    @Override
+    protected void handlePollStateResultMessage(int what, AsyncResult ar) {
+        switch (what) {
+            case EVENT_POLL_STATE_CS_CELLULAR_REGISTRATION: {
+                super.handlePollStateResultMessage(what, ar);
+                if (mPhone.isPhoneTypeGsm()) {
+                    NetworkRegistrationInfo regStates = (NetworkRegistrationInfo) ar.result;
+                    int regState = regStates.getRegistrationState();
+
+                    if (regState == NetworkRegistrationInfo.REGISTRATION_STATE_DENIED) {
+                        int rejCode = regStates.getRejectCause();
+                        // Check if rejCode is "Persistent location update reject",
+                        if (rejCode == 10) {
+                            log(" Posting Managed roaming intent sub = "
+                                    + mPhone.getSubId());
+                            try {
+                                Intent intent =
+                                        new Intent(ACTION_MANAGED_ROAMING_IND);
+                                // component would display Dialog to perform Manual scan
+                                // if current Network selection Mode is Manual.
+                                intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+                                intent.putExtra(PhoneConstants.SUBSCRIPTION_KEY,
+                                        mPhone.getSubId());
+                                mPhone.getContext().startActivity(intent);
+                            } catch (ActivityNotFoundException e) {
+                                loge("unable to start activity: " + e);
+                            }
+                        }
+                    }
+                }
+                break;
+            }
+
+            default:
+                super.handlePollStateResultMessage(what, ar);
+        }
+    }
+
+    @Override
+    public void handleMessage(Message msg) {
+        if (msg.what == EVENT_RADIO_STATE_CHANGED) {
+            if (mPhone.mCi.getRadioState() == TelephonyManager.RADIO_POWER_OFF) {
+                setPowerStateToDesired();
+                log("Trigger as manual polling");
+                pollState();
+            } else {
+                super.handleMessage(msg);
+            }
+        } else {
+            super.handleMessage(msg);
+        }
+    }
+}
diff --git a/src/java/com/android/internal/telephony/vendor/VendorSubscriptionController.java b/src/java/com/android/internal/telephony/vendor/VendorSubscriptionController.java
new file mode 100644
index 0000000..0f8b987
--- /dev/null
+++ b/src/java/com/android/internal/telephony/vendor/VendorSubscriptionController.java
@@ -0,0 +1,439 @@
+/*
+ * Copyright (C) 2020 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.vendor;
+
+import android.Manifest;
+import android.content.ContentValues;
+import android.content.Context;
+import android.content.Intent;
+import android.os.AsyncResult;
+import android.os.Handler;
+import android.os.Message;
+import android.os.Registrant;
+import android.os.RegistrantList;
+import android.os.SystemProperties;
+import android.provider.Settings;
+import android.provider.Settings.SettingNotFoundException;
+import android.telecom.PhoneAccount;
+import android.telecom.PhoneAccountHandle;
+import android.telecom.TelecomManager;
+import android.telephony.Rlog;
+import android.telephony.SubscriptionInfo;
+import android.telephony.SubscriptionManager;
+import android.telephony.TelephonyManager;
+import android.util.Log;
+
+import com.android.internal.telephony.PhoneFactory;
+import com.android.internal.telephony.SubscriptionController;
+import com.android.internal.telephony.Phone;
+
+import java.util.Iterator;
+import java.util.List;
+
+/*
+ * Extending SubscriptionController here:
+ * To implement fall back of sms/data user preferred subId value to next
+ * available subId when current preferred SIM deactivated or removed.
+ */
+public class VendorSubscriptionController extends SubscriptionController {
+    static final String LOG_TAG = "VendorSubscriptionController";
+    private static final boolean DBG = true;
+    private static final boolean VDBG = Rlog.isLoggable(LOG_TAG, Log.VERBOSE);
+
+    private static int sNumPhones;
+
+    private static final int EVENT_UICC_APPS_ENABLEMENT_DONE = 101;
+
+    private static final int PROVISIONED = 1;
+    private static final int NOT_PROVISIONED = 0;
+
+    private TelecomManager mTelecomManager;
+    private TelephonyManager mTelephonyManager;
+
+    private RegistrantList mAddSubscriptionRecordRegistrants = new RegistrantList();
+
+    private static final String SETTING_USER_PREF_DATA_SUB = "user_preferred_data_sub";
+    /**
+     * This intent would be broadcasted when a subId/slotId pair added to the
+     * sSlotIdxToSubId hashmap.
+     */
+    private static final String ACTION_SUBSCRIPTION_RECORD_ADDED =
+            "android.intent.action.SUBSCRIPTION_INFO_RECORD_ADDED";
+
+    public static VendorSubscriptionController init(Context c) {
+        synchronized (VendorSubscriptionController.class) {
+            if (sInstance == null) {
+                sInstance = new VendorSubscriptionController(c);
+            } else {
+                Log.wtf(LOG_TAG, "init() called multiple times!  sInstance = " + sInstance);
+            }
+            return (VendorSubscriptionController)sInstance;
+        }
+    }
+
+    public static VendorSubscriptionController getInstance() {
+        if (sInstance == null) {
+           Log.wtf(LOG_TAG, "getInstance null");
+        }
+
+        return (VendorSubscriptionController)sInstance;
+    }
+
+    protected VendorSubscriptionController(Context c) {
+        super(c);
+        if (DBG) logd(" init by Context");
+
+        mTelecomManager = (TelecomManager) mContext.getSystemService(Context.TELECOM_SERVICE);
+        mTelephonyManager = (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE);
+        sNumPhones = TelephonyManager.getDefault().getPhoneCount();
+    }
+
+    public void registerForAddSubscriptionRecord(Handler handler, int what, Object obj) {
+        Registrant r = new Registrant(handler, what, obj);
+        synchronized (mAddSubscriptionRecordRegistrants) {
+            mAddSubscriptionRecordRegistrants.add(r);
+            List<SubscriptionInfo> subInfoList =
+                    getActiveSubscriptionInfoList(mContext.getOpPackageName());
+            if (subInfoList != null) {
+                r.notifyRegistrant();
+            }
+        }
+    }
+
+    public void unregisterForAddSubscriptionRecord(Handler handler) {
+        synchronized (mAddSubscriptionRecordRegistrants) {
+            mAddSubscriptionRecordRegistrants.remove(handler);
+        }
+    }
+
+    @Override
+    public int addSubInfoRecord(String iccId, int slotIndex) {
+        logd("addSubInfoRecord: broadcast intent subId[" + slotIndex + "]");
+        return addSubInfo(iccId, null, slotIndex, SubscriptionManager.SUBSCRIPTION_TYPE_LOCAL_SIM);
+    }
+
+    @Override
+    public int addSubInfo(String uniqueId, String displayName, int slotIndex,
+            int subscriptionType) {
+
+        int retVal = super.addSubInfo(uniqueId, displayName, slotIndex, subscriptionType);
+
+        int[] subId = getSubId(slotIndex);
+        if (subId != null && (subId.length > 0)) {
+            // When a new entry added in sSlotIdxToSubId for slotId, broadcast intent
+            logd("addSubInfoRecord: broadcast intent subId[" + slotIndex + "] = " + subId[0]);
+            mAddSubscriptionRecordRegistrants.notifyRegistrants(
+                    new AsyncResult(null, slotIndex, null));
+            Intent intent = new Intent(ACTION_SUBSCRIPTION_RECORD_ADDED);
+            SubscriptionManager.putPhoneIdAndSubIdExtra(intent, slotIndex, subId[0]);
+            mContext.sendBroadcast(intent, Manifest.permission.READ_PRIVILEGED_PHONE_STATE);
+        }
+        return retVal;
+    }
+
+    @Override
+    public int setUiccApplicationsEnabled(boolean enabled, int subId) {
+        if (DBG) logd("[setUiccApplicationsEnabled]+ enabled:" + enabled + " subId:" + subId);
+
+        ContentValues value = new ContentValues(1);
+        value.put(SubscriptionManager.UICC_APPLICATIONS_ENABLED, enabled);
+
+        int result = mContext.getContentResolver().update(
+                SubscriptionManager.getUriForSubscriptionId(subId), value, null, null);
+
+        // Refresh the Cache of Active Subscription Info List
+        refreshCachedActiveSubscriptionInfoList();
+
+        notifySubscriptionInfoChanged();
+
+        if (isActiveSubId(subId)) {
+            Phone phone = PhoneFactory.getPhone(getPhoneId(subId));
+            phone.enableUiccApplications(enabled, Message.obtain(
+                    mSubscriptionHandler, EVENT_UICC_APPS_ENABLEMENT_DONE, enabled));
+        }
+
+        return result;
+    }
+
+    /*
+     * Handler Class
+     */
+    private Handler mSubscriptionHandler = new Handler() {
+        @Override
+        public void handleMessage(Message msg) {
+            switch (msg.what) {
+                case EVENT_UICC_APPS_ENABLEMENT_DONE: {
+                    logd("EVENT_UICC_APPS_ENABLEMENT_DONE");
+                    AsyncResult ar = (AsyncResult) msg.obj;
+                    if (ar.exception != null) {
+                        logd("Received exception: " + ar.exception);
+                        return;
+                    }
+                    updateUserPreferences();
+                    break;
+                }
+            }
+        }
+    };
+
+    protected boolean isRadioAvailableOnAllSubs() {
+        for (int i = 0; i < sNumPhones; i++) {
+            if (PhoneFactory.getPhone(i).mCi != null &&
+                    PhoneFactory.getPhone(i).mCi.getRadioState() ==
+                    TelephonyManager.RADIO_POWER_UNAVAILABLE) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    protected boolean isShuttingDown() {
+        for (int i = 0; i < sNumPhones; i++) {
+            if (PhoneFactory.getPhone(i) != null &&
+                    PhoneFactory.getPhone(i).isShuttingDown()) return true;
+        }
+        return false;
+    }
+
+    public boolean isRadioInValidState() {
+
+        // Radio Unavailable, do not updateUserPrefs. As this may happened due to SSR or RIL Crash.
+        if (!isRadioAvailableOnAllSubs()) {
+            logd(" isRadioInValidState, radio not available");
+            return false;
+        }
+
+        //Do not updateUserPrefs when Shutdown is in progress
+        if (isShuttingDown()) {
+            logd(" isRadioInValidState: device shutdown in progress ");
+            return false;
+        }
+        return true;
+    }
+
+    // If any of the voice/data/sms preference related SIM
+    // deactivated/re-activated this will update the preference
+    // with next available/activated SIM.
+    public void updateUserPreferences() {
+        SubscriptionInfo mNextActivatedSub = null;
+        int activeCount = 0;
+        if (!isRadioInValidState()) {
+            logd("Radio is in Invalid state, Ignore Updating User Preference!!!");
+            return;
+        }
+        List<SubscriptionInfo> sil = getActiveSubscriptionInfoList(mContext.getOpPackageName());
+        // If list of active subscriptions empty OR non of the SIM provisioned
+        // clear defaults preference of voice/sms/data.
+        if (sil == null || sil.size() < 1) {
+            logi("updateUserPreferences: Subscription list is empty");
+            return;
+        }
+
+        // Do not fallback to next available sub if AOSP feature
+        // "User choice of selecting data/sms fallback preference" enabled.
+        if (SystemProperties.getBoolean("persist.vendor.radio.aosp_usr_pref_sel", false)) {
+            logi("updateUserPreferences: AOSP user preference option enabled ");
+            return;
+        }
+
+        final int defaultVoiceSubId = getDefaultVoiceSubId();
+        final int defaultDataSubId = getDefaultDataSubId();
+        final int defaultSmsSubId = getDefaultSmsSubId();
+
+        //Get num of activated Subs and next available activated sub info.
+        for (SubscriptionInfo subInfo : sil) {
+            if (isUiccProvisioned(subInfo.getSimSlotIndex())) {
+                activeCount++;
+                if (mNextActivatedSub == null) mNextActivatedSub = subInfo;
+            }
+        }
+        logd("updateUserPreferences:: active sub count = " + activeCount + " dds = "
+                 + defaultDataSubId + " voice = " + defaultVoiceSubId +
+                 " sms = " + defaultSmsSubId);
+
+        // If active SUB count is 1, Always Ask Prompt to be disabled and
+        // preference fallback to the next available SUB.
+        if (activeCount == 1) {
+            setSmsPromptEnabled(false);
+        }
+
+        // TODO Set all prompt options to false ?
+
+        // in Single SIM case or if there are no activated subs available, no need to update. EXIT.
+        if ((mNextActivatedSub == null) || (getActiveSubInfoCountMax() == 1)) return;
+
+        handleDataPreference(mNextActivatedSub.getSubscriptionId());
+
+        if ((defaultSmsSubId != SubscriptionManager.INVALID_SUBSCRIPTION_ID
+                || activeCount == 1) && !isSubProvisioned(defaultSmsSubId)) {
+            setDefaultSmsSubId(mNextActivatedSub.getSubscriptionId());
+        }
+
+        if ((defaultVoiceSubId != SubscriptionManager.INVALID_SUBSCRIPTION_ID
+                || activeCount == 1) && !isSubProvisioned(defaultVoiceSubId)) {
+            setDefaultVoiceSubId(mNextActivatedSub.getSubscriptionId());
+        }
+
+        // voice preference is handled in such a way that
+        // 1. Whenever current Sub is deactivated or removed It fall backs to
+        //    next available Sub.
+        // 2. When device is flashed for the first time, initial voice preference
+        //    would be set to always ask.
+        if (!isNonSimAccountFound() && activeCount == 1) {
+            final int subId = mNextActivatedSub.getSubscriptionId();
+            PhoneAccountHandle phoneAccountHandle = subscriptionIdToPhoneAccountHandle(subId);
+            logi("set default phoneaccount to  " + subId);
+            mTelecomManager.setUserSelectedOutgoingPhoneAccount(phoneAccountHandle);
+        }
+        if (!isSubProvisioned(sDefaultFallbackSubId.get())) {
+            setDefaultFallbackSubId(mNextActivatedSub.getSubscriptionId(),
+                SubscriptionManager.SUBSCRIPTION_TYPE_LOCAL_SIM);
+        }
+
+        notifySubscriptionInfoChanged();
+        logd("updateUserPreferences: after currentDds = " + getDefaultDataSubId() + " voice = " +
+                 getDefaultVoiceSubId() + " sms = " + getDefaultSmsSubId());
+    }
+
+    protected void handleDataPreference(int nextActiveSubId) {
+        int userPrefDataSubId = getUserPrefDataSubIdFromDB();
+        int currentDataSubId = getDefaultDataSubId();
+
+        List<SubscriptionInfo> subInfoList =
+                getActiveSubscriptionInfoList(mContext.getOpPackageName());
+        if (subInfoList == null) {
+            return;
+        }
+        boolean userPrefSubValid = false;
+        for (SubscriptionInfo subInfo : subInfoList) {
+            if (subInfo.getSubscriptionId() == userPrefDataSubId) {
+                userPrefSubValid = true;
+            }
+        }
+        logd("havePrefSub = " + userPrefSubValid + " user pref subId = "
+                 + userPrefDataSubId + " current dds " + currentDataSubId
+                 + " next active subId " + nextActiveSubId);
+
+        // If earlier user selected DDS is now available, set that as DDS subId.
+        if (userPrefSubValid && isSubProvisioned(userPrefDataSubId) &&
+                 (currentDataSubId != userPrefDataSubId)) {
+            setDefaultDataSubId(userPrefDataSubId);
+        } else if (!isSubProvisioned(currentDataSubId)) {
+            setDefaultDataSubId(nextActiveSubId);
+        }
+
+    }
+
+    protected boolean isUiccProvisioned(int slotId) {
+//        return isSubscriptionEnabled();
+        return true;
+    }
+
+    // This method returns true if subId and corresponding slotId is in valid
+    // range and the Uicc card corresponds to this slot is provisioned.
+    protected boolean isSubProvisioned(int subId) {
+        boolean isSubIdUsable = SubscriptionManager.isUsableSubIdValue(subId);
+
+        if (isSubIdUsable) {
+            int slotId = getSlotIndex(subId);
+            if (!SubscriptionManager.isValidSlotIndex(slotId)) {
+                loge(" Invalid slotId " + slotId + " or subId = " + subId);
+                isSubIdUsable = false;
+            } else {
+                if (!isUiccProvisioned(slotId)) {
+                    isSubIdUsable = false;
+                }
+                loge("isSubProvisioned, state = " + isSubIdUsable + " subId = " + subId);
+            }
+        }
+        return isSubIdUsable;
+    }
+
+    /* Returns User SMS Prompt property,  enabled or not */
+    public boolean isSmsPromptEnabled() {
+        boolean prompt = false;
+        int value = 0;
+        try {
+            value = Settings.Global.getInt(mContext.getContentResolver(),
+                    Settings.Global.MULTI_SIM_SMS_PROMPT);
+        } catch (SettingNotFoundException snfe) {
+            loge("Settings Exception Reading Dual Sim SMS Prompt Values");
+        }
+        prompt = (value == 0) ? false : true ;
+        if (VDBG) logd("SMS Prompt option:" + prompt);
+
+       return prompt;
+    }
+
+    /*Sets User SMS Prompt property,  enable or not */
+    public void setSmsPromptEnabled(boolean enabled) {
+        enforceModifyPhoneState("setSMSPromptEnabled");
+        int value = (enabled == false) ? 0 : 1;
+        Settings.Global.putInt(mContext.getContentResolver(),
+                Settings.Global.MULTI_SIM_SMS_PROMPT, value);
+        logi("setSMSPromptOption to " + enabled);
+    }
+
+    protected boolean isNonSimAccountFound() {
+        final Iterator<PhoneAccountHandle> phoneAccounts =
+                mTelecomManager.getCallCapablePhoneAccounts().listIterator();
+
+        while (phoneAccounts.hasNext()) {
+            final PhoneAccountHandle phoneAccountHandle = phoneAccounts.next();
+            final PhoneAccount phoneAccount = mTelecomManager.getPhoneAccount(phoneAccountHandle);
+            if (mTelephonyManager.getSubIdForPhoneAccount(phoneAccount) ==
+                            SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
+                logi("Other than SIM account found. ");
+                return true;
+            }
+        }
+        logi("Other than SIM account not found ");
+        return false;
+    }
+
+    protected PhoneAccountHandle subscriptionIdToPhoneAccountHandle(final int subId) {
+        final Iterator<PhoneAccountHandle> phoneAccounts =
+                mTelecomManager.getCallCapablePhoneAccounts().listIterator();
+
+        while (phoneAccounts.hasNext()) {
+            final PhoneAccountHandle phoneAccountHandle = phoneAccounts.next();
+            final PhoneAccount phoneAccount = mTelecomManager.getPhoneAccount(phoneAccountHandle);
+            if (subId == mTelephonyManager.getSubIdForPhoneAccount(phoneAccount)) {
+                return phoneAccountHandle;
+            }
+        }
+
+        return null;
+    }
+
+    protected int getUserPrefDataSubIdFromDB() {
+        return android.provider.Settings.Global.getInt(mContext.getContentResolver(),
+                SETTING_USER_PREF_DATA_SUB, SubscriptionManager.INVALID_SUBSCRIPTION_ID);
+    }
+
+    private void logd(String string) {
+        if (DBG) Rlog.d(LOG_TAG, string);
+    }
+
+    private void logi(String string) {
+        Rlog.i(LOG_TAG, string);
+    }
+
+    private void loge(String string) {
+        Rlog.e(LOG_TAG, string);
+    }
+}
diff --git a/src/java/com/android/internal/telephony/vendor/VendorSubscriptionInfoUpdater.java b/src/java/com/android/internal/telephony/vendor/VendorSubscriptionInfoUpdater.java
new file mode 100644
index 0000000..2060553
--- /dev/null
+++ b/src/java/com/android/internal/telephony/vendor/VendorSubscriptionInfoUpdater.java
@@ -0,0 +1,138 @@
+/*
+ * Copyright (C) 2020 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.vendor;
+
+import android.content.Context;
+import android.os.Looper;
+import android.telephony.Rlog;
+import android.telephony.TelephonyManager;
+import android.util.Log;
+import java.util.ArrayList;
+import java.util.List;
+
+import com.android.internal.telephony.CommandsInterface;
+import com.android.internal.telephony.IccCardConstants;
+import com.android.internal.telephony.SubscriptionController;
+import com.android.internal.telephony.SubscriptionInfoUpdater;
+import com.android.internal.telephony.uicc.UiccController;
+import com.android.internal.telephony.uicc.UiccSlot;
+import com.android.internal.telephony.uicc.IccUtils;
+
+
+/**
+ * To reduce delay in SubInfo records availability to clients, add subInfo record
+ * to table without waiting for SIM state moves to LOADED.
+ */
+public class VendorSubscriptionInfoUpdater extends SubscriptionInfoUpdater {
+    private static final String LOG_TAG = "VendorSubscriptionInfoUpdater";
+
+    private static final String ICCID_STRING_FOR_NO_SIM = "";
+    private static Context sContext = null;
+
+    protected boolean[] mIsRecordUpdateRequired;
+    protected static VendorSubscriptionInfoUpdater sInstance = null;
+    private static final int SUPPORTED_MODEM_COUNT = TelephonyManager.getDefault()
+            .getSupportedModemCount();
+
+    static VendorSubscriptionInfoUpdater init(Looper looper, Context context,
+            CommandsInterface[] ci) {
+        synchronized (VendorSubscriptionInfoUpdater.class) {
+            if (sInstance == null) {
+                sInstance = new VendorSubscriptionInfoUpdater(looper, context, ci);
+            } else {
+                Log.wtf(LOG_TAG, "init() called multiple times!  sInstance = " + sInstance);
+            }
+            return sInstance;
+        }
+    }
+
+    public static VendorSubscriptionInfoUpdater getInstance() {
+        if (sInstance == null) {
+            Log.wtf(LOG_TAG, "getInstance null");
+        }
+        return sInstance;
+    }
+
+    protected VendorSubscriptionInfoUpdater(Looper looper, Context context,
+            CommandsInterface[] ci) {
+        super(looper, context, ci);
+        sContext = context;
+        mIsRecordUpdateRequired = new boolean[SUPPORTED_MODEM_COUNT];
+
+        for (int index = 0; index < SUPPORTED_MODEM_COUNT; index++) {
+            mIsRecordUpdateRequired[index] = true;
+        }
+    }
+
+    @Override
+    protected void handleSimReady(int phoneId) {
+        List<Integer> cardIds = new ArrayList<>();
+        Rlog.d(LOG_TAG, "handleSimReady: phoneId: " + phoneId);
+
+        if (sIccId[phoneId] != null && sIccId[phoneId].equals(ICCID_STRING_FOR_NO_SIM)) {
+            Rlog.d(LOG_TAG, " SIM" + (phoneId + 1) + " hot plug in");
+            sIccId[phoneId] = null;
+        }
+        UiccSlot uiccSlot = UiccController.getInstance().getUiccSlotForPhone(phoneId);
+        if (uiccSlot == null) {
+            Rlog.d(LOG_TAG, "handleSimReady: uiccSlot null");
+            return;
+        }
+
+        String iccId = uiccSlot.getIccId();
+        if (IccUtils.stripTrailingFs(iccId) == null) {
+            Rlog.d(LOG_TAG, "handleSimReady: IccID null");
+            return;
+        }
+        sIccId[phoneId] = IccUtils.stripTrailingFs(iccId);
+
+        updateSubscriptionInfoByIccId(phoneId, true /* updateEmbeddedSubs */);
+
+        cardIds.add(getCardIdFromPhoneId(phoneId));
+        updateEmbeddedSubscriptions(cardIds, (hasChanges) -> {
+            if (hasChanges) {
+                SubscriptionController.getInstance().notifySubscriptionInfoChanged();
+            }
+        });
+        broadcastSimStateChanged(phoneId, IccCardConstants.INTENT_VALUE_ICC_READY, null);
+        broadcastSimCardStateChanged(phoneId, TelephonyManager.SIM_STATE_PRESENT);
+        broadcastSimApplicationStateChanged(phoneId, TelephonyManager.SIM_STATE_NOT_READY);
+    }
+
+    @Override
+    protected void handleSimLoaded(int phoneId) {
+        // mIsRecordUpdateRequired set to false if sIccId has a valid Iccid to skip
+        // adding subId once again from here.
+        if ((sIccId[phoneId] != null) && (sIccId[phoneId] != ICCID_STRING_FOR_NO_SIM)) {
+            mIsRecordUpdateRequired[phoneId] = false;
+        }
+        Rlog.d(LOG_TAG, "handleSimLoaded: phoneId: " + phoneId);
+        super.handleSimLoaded(phoneId);
+    }
+
+    @Override
+    synchronized protected void updateSubscriptionInfoByIccId(int phoneId,
+        boolean updateEmbeddedSubs) {
+
+        if (mIsRecordUpdateRequired[phoneId] == true) {
+            super.updateSubscriptionInfoByIccId(phoneId, updateEmbeddedSubs);
+        } else {
+            Rlog.d(LOG_TAG, "Ignoring subscription update event " + phoneId);
+            mIsRecordUpdateRequired[phoneId] = true;
+        }
+    }
+}
diff --git a/src/java/com/android/internal/telephony/vendor/dataconnection/VendorDataResetEventTracker.java b/src/java/com/android/internal/telephony/vendor/dataconnection/VendorDataResetEventTracker.java
new file mode 100644
index 0000000..492ffd1
--- /dev/null
+++ b/src/java/com/android/internal/telephony/vendor/dataconnection/VendorDataResetEventTracker.java
@@ -0,0 +1,209 @@
+/*
+ * Copyright (C) 2020 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.vendor.dataconnection;
+
+import com.android.internal.telephony.DctConstants;
+import com.android.internal.telephony.IccCardConstants;
+import com.android.internal.telephony.Phone;
+import com.android.internal.telephony.TelephonyIntents;
+
+import android.app.AlarmManager;
+import android.app.PendingIntent;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.AsyncResult;
+import android.os.Handler;
+import android.os.Message;
+import android.os.SystemClock;
+import android.os.SystemProperties;
+
+import android.telephony.CellLocation;
+import android.telephony.PhoneStateListener;
+import android.telephony.Rlog;
+import android.telephony.ServiceState;
+import android.telephony.SubscriptionManager;
+import android.telephony.TelephonyManager;
+import android.telephony.gsm.GsmCellLocation;
+import android.text.TextUtils;
+import android.util.Log;
+import android.util.Pair;
+
+import java.util.ArrayList;
+
+public class VendorDataResetEventTracker {
+
+    public static interface ResetEventListener {
+        public void onResetEvent(boolean retry);
+    }
+
+    private static final boolean DBG = true;
+
+    private TelephonyManager mTelephonyManager = null;
+    private GsmCellLocation mPreviousLocation = null;
+    private PhoneStateListener mPhoneStateListener = null;
+    private Context mContext = null;
+    private Phone mPhone = null;
+    private ResetEventListener mListener = null;
+    private int mPreviousRAT = 0;
+    private int mTransportType;
+
+    private Handler mHandler = new Handler() {
+        @Override
+        public void handleMessage(Message msg) {
+            switch (msg.what) {
+                case DctConstants.EVENT_DATA_RAT_CHANGED: {
+                    AsyncResult ar = (AsyncResult) msg.obj;
+                    Pair<Integer, Integer> result = (Pair<Integer, Integer>) ar.result;
+                    if (result != null) {
+                        if (mPreviousRAT > 0 && result.second > 0
+                                && mPreviousRAT != result.second) {
+                            if (DBG) log("RAT CHANGED, " + mPreviousRAT
+                                     + "->" + result.second);
+                            notifyResetEvent("DATA_RAT_CHANGED", false);
+                        }
+                        mPreviousRAT = result.second;
+                    }
+                    break;
+                }
+                case DctConstants.EVENT_RADIO_OFF_OR_NOT_AVAILABLE: {
+                    if (DBG) log("EVENT_RADIO_OFF_OR_NOT_AVAILABLE");
+                    notifyResetEvent("RADIO_OFF_OR_NOT_AVAILABLE", false);
+                    break;
+                }
+            }
+        }
+    };
+
+    private BroadcastReceiver mSimStateReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            final String action = intent.getAction();
+            if (TelephonyIntents.ACTION_SIM_STATE_CHANGED.equals(action)) {
+                final String stateExtra = intent
+                        .getStringExtra(IccCardConstants.INTENT_KEY_ICC_STATE);
+                log("ACTION_SIM_STATE_CHANGED, action " + stateExtra);
+                if (IccCardConstants.INTENT_VALUE_ICC_ABSENT.equals(stateExtra)) {
+                    notifyResetEvent("SIM_STATE_ABSENT", false);
+                }
+            }
+        }
+    };
+
+    public VendorDataResetEventTracker(int transportType, Phone phone,
+            ResetEventListener listener) {
+        if (DBG) log("VendorDataResetEventTracker constructor: " + this);
+        mPhone = phone;
+        mContext = mPhone.getContext();
+        this.mListener = listener;
+        mTransportType = transportType;
+        mTelephonyManager = (TelephonyManager) mContext
+                .getSystemService(Context.TELEPHONY_SERVICE);
+    }
+
+    /**
+     * Register listener for RAU update and RAT change.
+     */
+    public void startResetEventTracker() {
+        if (DBG) log("startResetEventTracker");
+        mPhone.getServiceStateTracker().registerForDataRegStateOrRatChanged(
+                mTransportType, mHandler, DctConstants.EVENT_DATA_RAT_CHANGED, null);
+        mPhone.mCi.registerForOffOrNotAvailable(mHandler,
+                DctConstants.EVENT_RADIO_OFF_OR_NOT_AVAILABLE, null);
+        mContext.registerReceiver(mSimStateReceiver, new IntentFilter(
+                TelephonyIntents.ACTION_SIM_STATE_CHANGED));
+
+        CellLocation currentCellLocation = mPhone.getCellIdentity().asCellLocation();
+        if (currentCellLocation instanceof GsmCellLocation) {
+            mPreviousLocation = (GsmCellLocation) mPhone.getCellIdentity().asCellLocation();
+            if (DBG) log("DataConnection mPreviousLocation : " + mPreviousLocation);
+        }
+        int ddsSubId = SubscriptionManager.getDefaultDataSubscriptionId();
+
+        if (mPhoneStateListener == null) {
+            mPhoneStateListener = new PhoneStateListener() {
+                public void onCellLocationChanged(CellLocation location) {
+                    if (DBG) log("DataConnection onCellLocationChanged : "
+                                + location);
+
+                    if (location instanceof GsmCellLocation) {
+                        GsmCellLocation currentLocation = (GsmCellLocation) location;
+
+                        if (mPreviousLocation != null
+                                && currentLocation != null) {
+                            if (mPreviousLocation.getCid() != currentLocation
+                                    .getCid()
+                                    || mPreviousLocation.getLac() != currentLocation
+                                            .getLac()) {
+                                if (DBG) log("DataConnection location updated");
+                                notifyResetEvent("LOCATION_UPDATED", true);
+                            }
+                        }
+                        mPreviousLocation = currentLocation;
+                    }
+                }
+            };
+        }
+
+        mTelephonyManager.
+                createForSubscriptionId(ddsSubId).
+                listen(mPhoneStateListener, PhoneStateListener.LISTEN_CELL_LOCATION);
+    }
+
+    /**
+     * Unregister for RAU update and RAT change.
+     */
+    public void stopResetEventTracker() {
+        if (DBG) log("stopResetTimer");
+        try {
+            mPreviousRAT = 0;
+            mPreviousLocation = null;
+            if (mPhoneStateListener != null) {
+                mTelephonyManager.listen(mPhoneStateListener,
+                        PhoneStateListener.LISTEN_NONE);
+            }
+            mPhone.getServiceStateTracker()
+                    .unregisterForDataRegStateOrRatChanged(mTransportType, mHandler);
+            mPhone.mCi.unregisterForOffOrNotAvailable(mHandler);
+            mContext.unregisterReceiver(mSimStateReceiver);
+        } catch (Exception e) {
+            if (DBG) log("error:" + e.getMessage());
+            e.printStackTrace();
+        }
+    }
+
+    public void dispose() {
+        if (DBG) log("dispose");
+        stopResetEventTracker();
+        mTelephonyManager = null;
+    }
+
+    /**
+     * notify the listener for reset event
+     */
+    private void notifyResetEvent(String reason, boolean retry) {
+        if (DBG) log("notifyResetEvent: reason=" + reason + ", retry=" + retry);
+        stopResetEventTracker();
+        if (mListener != null) {
+            mListener.onResetEvent(retry);
+        }
+    }
+
+    private void log(String log) {
+        Rlog.d("VendorDataResetEventTracker", log);
+    }
+}
diff --git a/src/java/com/android/internal/telephony/vendor/dataconnection/VendorDcTracker.java b/src/java/com/android/internal/telephony/vendor/dataconnection/VendorDcTracker.java
new file mode 100644
index 0000000..2e256ff
--- /dev/null
+++ b/src/java/com/android/internal/telephony/vendor/dataconnection/VendorDcTracker.java
@@ -0,0 +1,366 @@
+/*
+ * Copyright (C) 2020 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.vendor.dataconnection;
+
+import android.app.AlertDialog;
+import android.view.WindowManager;
+
+import android.telephony.AccessNetworkConstants;
+import android.telephony.Annotation.DataFailureCause;
+import android.telephony.CarrierConfigManager;
+import android.telephony.DataFailCause;
+import android.telephony.data.ApnSetting;
+import android.telephony.Rlog;
+import android.telephony.ServiceState;
+import android.telephony.SubscriptionManager;
+import android.telephony.TelephonyManager;
+
+import com.android.internal.telephony.dataconnection.ApnContext;
+import com.android.internal.telephony.dataconnection.DcTracker;
+import com.android.internal.telephony.DctConstants;
+import com.android.internal.telephony.GsmCdmaPhone;
+import com.android.internal.telephony.Phone;
+import com.android.internal.telephony.PhoneConstants;
+import com.android.internal.telephony.uicc.IccRecords;
+import com.android.internal.util.ArrayUtils;
+import com.android.internal.util.AsyncChannel;
+
+import android.database.Cursor;
+import android.content.Context;
+import android.os.PersistableBundle;
+import android.provider.Telephony;
+
+import java.util.HashSet;
+import java.util.Iterator;
+
+public class VendorDcTracker extends DcTracker {
+    private String LOG_TAG = "VendorDCT";
+    private HashSet<String> mIccidSet = new HashSet<String>();
+    private int mTransportType = AccessNetworkConstants.TRANSPORT_TYPE_WWAN;
+
+    // Maximum data reject count
+    public static final int MAX_PDP_REJECT_COUNT = 3;
+
+    // Data reset event tracker to know reset events.
+    private VendorDataResetEventTracker mVendorDataResetEventTracker = null;
+
+    // data reject dialog, made static because only one dialog object can be
+    // used between multiple dataconnection objects.
+    protected static AlertDialog mDataRejectDialog = null;
+
+    //Store data reject cause for comparison
+    private String mDataRejectReason = "NONE";
+
+    //Store data reject count
+    private int mDataRejectCount = 0;
+
+    //Store data reject cause code
+    private int mPdpRejectCauseCode = 0;
+
+    // Constructor
+    public VendorDcTracker(Phone phone, int transportType) {
+        super(phone, transportType);
+        mTransportType = transportType;
+        LOG_TAG = LOG_TAG + "-" +
+                ((transportType == AccessNetworkConstants.TRANSPORT_TYPE_WWAN) ? "C" : "I");
+        if (DBG) log(LOG_TAG + ".constructor");
+        fillIccIdSet();
+    }
+
+    @Override
+    protected boolean allowInitialAttachForOperator() {
+        String iccId = mPhone.getIccSerialNumber();
+        if (iccId != null) {
+            Iterator<String> itr = mIccidSet.iterator();
+            while (itr.hasNext()) {
+                if (iccId.contains(itr.next())) {
+                    return false;
+                }
+            }
+        }
+        return true;
+    }
+
+    private void fillIccIdSet() {
+        mIccidSet.add("8991840");
+        mIccidSet.add("8991854");
+        mIccidSet.add("8991855");
+        mIccidSet.add("8991856");
+        mIccidSet.add("8991857");
+        mIccidSet.add("8991858");
+        mIccidSet.add("8991859");
+        mIccidSet.add("899186");
+        mIccidSet.add("8991870");
+        mIccidSet.add("8991871");
+        mIccidSet.add("8991872");
+        mIccidSet.add("8991873");
+        mIccidSet.add("8991874");
+    }
+
+    @Override
+    protected void onVoiceCallEnded() {
+        if (DBG) log("onVoiceCallEnded");
+        mInVoiceCall = false;
+        if (isConnected()) {
+            if (!mPhone.getServiceStateTracker().isConcurrentVoiceAndDataAllowed()) {
+                startNetStatPoll();
+                startDataStallAlarm(DATA_STALL_NOT_SUSPECTED);
+                mPhone.notifyAllActiveDataConnections();
+            } else {
+                // clean slate after call end.
+                resetPollStats();
+            }
+        }
+        //Allow data call retry only on DDS sub
+        if (mPhone.getSubId() == SubscriptionManager.getDefaultDataSubscriptionId()) {
+            // reset reconnect timer
+            setupDataOnAllConnectableApns(Phone.REASON_VOICE_CALL_ENDED, RetryFailures.ALWAYS);
+
+        }
+    }
+
+    @Override
+    protected void setupDataOnConnectableApn(ApnContext apnContext, String reason,
+            RetryFailures retryFailures) {
+        if (mPhone.getContext().getResources().getBoolean(
+                com.android.internal.R.bool.config_pdp_reject_enable_retry) &&
+                mDataRejectCount > 0) {
+            log("setupDataOnConnectableApn: data retry in progress, skip processing");
+        } else {
+            super.setupDataOnConnectableApn(apnContext, reason, retryFailures);
+        }
+    }
+
+    @Override
+    protected void onDataSetupComplete(ApnContext apnContext, boolean success, int cause,
+            @RequestNetworkType int requestType) {
+        boolean isPdpRejectConfigEnabled = mPhone.getContext().getResources().getBoolean(
+                com.android.internal.R.bool.config_pdp_reject_enable_retry);
+        if (success) {
+            if (isPdpRejectConfigEnabled) {
+                handlePdpRejectCauseSuccess();
+            }
+        } else {
+            mPdpRejectCauseCode = cause;
+        }
+
+        super.onDataSetupComplete(apnContext, success, cause, requestType);
+    }
+
+    @Override
+    protected void onDataSetupCompleteError(ApnContext apnContext,
+            @RequestNetworkType int requestType) {
+        long delay = apnContext.getDelayForNextApn(mFailFast);
+        if (mPhone.getContext().getResources().getBoolean(
+                com.android.internal.R.bool.config_pdp_reject_enable_retry)) {
+            String reason = DataFailCause.toString(mPdpRejectCauseCode);
+
+            if (isMatchingPdpRejectCause(reason)) {
+                if (mVendorDataResetEventTracker == null) {
+                    mVendorDataResetEventTracker = new VendorDataResetEventTracker(mTransportType,
+                            mPhone, mResetEventListener);
+                }
+                if (mDataRejectCount == 0) {
+                    mVendorDataResetEventTracker.startResetEventTracker();
+                }
+                boolean isHandled = handlePdpRejectCauseFailure(reason);
+
+                /* If MAX Reject count reached, display pop-up to user */
+                if (MAX_PDP_REJECT_COUNT <= mDataRejectCount) {
+                    if (DBG) log("onDataSetupCompleteError: reached max retry count");
+                    displayPopup(mDataRejectReason);
+                    delay = -1;
+                } else if (isHandled) {
+                    delay = mPhone.getContext().getResources().getInteger(
+                            com.android.internal.R.integer.config_pdp_reject_retry_delay_ms);
+                    if (DBG) log("onDataSetupCompleteError: delay from config: " + delay);
+                }
+            } else {
+                if (DBG) log("onDataSetupCompleteError: reset reject count");
+                resetDataRejectCounter();
+            }
+        }
+
+        // Check if we need to retry or not.
+        // TODO: We should support handover retry in the future.
+        if (delay >= 0) {
+            if (DBG) log("onDataSetupCompleteError: Try next APN. delay = " + delay);
+            apnContext.setState(DctConstants.State.RETRYING);
+            // Wait a bit before trying the next APN, so that
+            // we're not tying up the RIL command channel
+
+            startReconnect(delay, apnContext);
+        } else {
+            // If we are not going to retry any APN, set this APN context to failed state.
+            // This would be the final state of a data connection.
+            apnContext.setState(DctConstants.State.FAILED);
+            mPhone.notifyDataConnection(apnContext.getApnType());
+            apnContext.setDataConnection(null);
+            if (DBG) log("onDataSetupCompleteError: Stop retrying APNs. delay=" + delay
+                    + ", requestType=" + requestTypeToString(requestType));
+        }
+    }
+
+    /*
+     * Reset data reject params on data call success
+     */
+    private void handlePdpRejectCauseSuccess() {
+        if (mDataRejectCount > 0) {
+            if (DBG) log("handlePdpRejectCauseSuccess: reset reject count");
+            resetDataRejectCounter();
+        }
+    }
+
+        /*
+     * Process data failure if RAT is WCDMA
+     * And if the failure cause matches one of the following cause codes:
+     * 1. USER_AUTHENTICATION
+     * 2. SERVICE_OPTION_NOT_SUBSCRIBED
+     * 3. MULTI_CONN_TO_SAME_PDN_NOT_ALLOWED
+     */
+    private boolean handlePdpRejectCauseFailure(String reason) {
+        boolean handleFailure = false;
+
+        // Check if data rat is WCDMA
+        if (isWCDMA(getDataRat())) {
+            if (DBG) log("handlePdpRejectCauseFailure: reason=" + reason +
+                    ", mDataRejectReason=" + mDataRejectReason);
+            /*
+             * If previously rejected code is not same as current data reject reason,
+             * then reset the count and reset the reject reason
+             */
+            if (!reason.equalsIgnoreCase(mDataRejectReason)) {
+                resetDataRejectCounter();
+            }
+
+            /*
+             * If failure reason is USER_AUTHENTICATION or
+             * SERVICE_OPTION_NOT_SUBSCRIBED or MULTI_CONN_TO_SAME_PDN_NOT_ALLOWED,
+             * increment counter and store reject cause
+             */
+            if (isMatchingPdpRejectCause(reason)) {
+                mDataRejectCount++;
+                mDataRejectReason = reason;
+                if (DBG) log ("handlePdpRejectCauseFailure: DataRejectCount = " +
+                        mDataRejectCount);
+                handleFailure = true;
+            }
+        } else {
+            if (DBG) log("isPdpRejectCauseFailureHandled: DataConnection not on wcdma");
+            resetDataRejectCounter();
+        }
+
+        return handleFailure;
+    }
+
+    /*
+     * Data reset event listener. Dc will get get onResetEvent
+     * whenever any data reset event occurs
+     */
+    private VendorDataResetEventTracker.ResetEventListener mResetEventListener =
+           new VendorDataResetEventTracker.ResetEventListener() {
+        @Override
+        public void onResetEvent(boolean retry) {
+            if (DBG) log("onResetEvent: retry=" + retry);
+
+            //Dismiss dialog
+            if (mDataRejectDialog != null && mDataRejectDialog.isShowing()) {
+                if (DBG) log("onResetEvent: Dismiss dialog");
+                mDataRejectDialog.dismiss();
+            }
+            mVendorDataResetEventTracker.stopResetEventTracker();
+
+            for (ApnContext apnContext : mApnContexts.values()) {
+                if (mDataRejectCount > 0) {
+                    if (DBG) log("onResetEvent: reset reject count=" + mDataRejectCount);
+                    resetDataRejectCounter();
+                    cancelReconnect(apnContext);
+                    if (retry) {
+                        if (DBG) log("onResetEvent: retry data call on apnContext=" + apnContext);
+                        sendMessage(obtainMessage(DctConstants.EVENT_TRY_SETUP_DATA, apnContext));
+                    }
+                }
+            }
+        }
+    };
+
+    /**
+     * This function will display the pdp reject message
+     */
+    private void displayPopup(String pdpRejectCause) {
+        if (DBG) log("displayPopup : " + pdpRejectCause);
+        String title = mPhone.getContext().getResources().
+                getString(com.android.internal.R.string.config_pdp_reject_dialog_title);
+        String message = null;
+        if (pdpRejectCause.equalsIgnoreCase("USER_AUTHENTICATION")) {
+            message = mPhone.getContext().getResources().
+                    getString(com.android.internal.R.string.config_pdp_reject_user_authentication_failed);
+        } else if (pdpRejectCause.equalsIgnoreCase("SERVICE_OPTION_NOT_SUBSCRIBED")) {
+            message = mPhone.getContext().getResources().getString(
+                    com.android.internal.R.string.config_pdp_reject_service_not_subscribed);
+        } else if (pdpRejectCause.equalsIgnoreCase("MULTI_CONN_TO_SAME_PDN_NOT_ALLOWED")) {
+            message = mPhone.getContext().getResources().getString(
+                    com.android.internal.R.string.config_pdp_reject_multi_conn_to_same_pdn_not_allowed);
+        }
+        if (mDataRejectDialog == null || !mDataRejectDialog.isShowing()) {
+            AlertDialog.Builder builder = new AlertDialog.Builder(
+                    mPhone.getContext());
+            builder.setPositiveButton(android.R.string.ok, null);
+            mDataRejectDialog = builder.create();
+        }
+        mDataRejectDialog.setMessage(message);
+        mDataRejectDialog.setCanceledOnTouchOutside(false);
+        mDataRejectDialog.setTitle(title);
+        mDataRejectDialog.getWindow().setType(
+                WindowManager.LayoutParams.TYPE_SYSTEM_ALERT);
+        mDataRejectDialog.show();
+    }
+
+    /*
+     * returns true if data reject cause matches errors listed
+     */
+    private boolean isMatchingPdpRejectCause(String reason) {
+        return reason.equalsIgnoreCase("USER_AUTHENTICATION") ||
+               reason.equalsIgnoreCase("SERVICE_OPTION_NOT_SUBSCRIBED") ||
+               reason.equalsIgnoreCase("MULTI_CONN_TO_SAME_PDN_NOT_ALLOWED");
+    }
+
+    /**
+     * returns true if radioTechnology is WCDMA rat, else false
+     */
+    private boolean isWCDMA(int radioTechnology) {
+        return radioTechnology == ServiceState.RIL_RADIO_TECHNOLOGY_UMTS
+            || radioTechnology == ServiceState.RIL_RADIO_TECHNOLOGY_HSDPA
+            || radioTechnology == ServiceState.RIL_RADIO_TECHNOLOGY_HSUPA
+            || radioTechnology == ServiceState.RIL_RADIO_TECHNOLOGY_HSPA
+            || radioTechnology == ServiceState.RIL_RADIO_TECHNOLOGY_HSPAP;
+
+    }
+
+    /*
+     * Reset data reject count and reason
+     */
+    private void resetDataRejectCounter() {
+        mDataRejectCount = 0;
+        mDataRejectReason = "NONE";
+    }
+
+    @Override
+    protected void log(String s) {
+        Rlog.d(LOG_TAG, "[" + mPhone.getPhoneId() + "]" + s);
+    }
+}
diff --git a/src/java/com/google/android/mms/ContentType.java b/src/java/com/google/android/mms/ContentType.java
deleted file mode 100644
index 51e52be..0000000
--- a/src/java/com/google/android/mms/ContentType.java
+++ /dev/null
@@ -1,230 +0,0 @@
-/*
- * Copyright (C) 2007-2008 Esmertec AG.
- * Copyright (C) 2007-2008 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.google.android.mms;
-
-import java.util.ArrayList;
-
-public class ContentType {
-    public static final String MMS_MESSAGE       = "application/vnd.wap.mms-message";
-    // The phony content type for generic PDUs (e.g. ReadOrig.ind,
-    // Notification.ind, Delivery.ind).
-    public static final String MMS_GENERIC       = "application/vnd.wap.mms-generic";
-    public static final String MULTIPART_MIXED   = "application/vnd.wap.multipart.mixed";
-    public static final String MULTIPART_RELATED = "application/vnd.wap.multipart.related";
-    public static final String MULTIPART_ALTERNATIVE = "application/vnd.wap.multipart.alternative";
-
-    public static final String TEXT_PLAIN        = "text/plain";
-    public static final String TEXT_HTML         = "text/html";
-    public static final String TEXT_VCALENDAR    = "text/x-vCalendar";
-    public static final String TEXT_VCARD        = "text/x-vCard";
-
-    public static final String IMAGE_UNSPECIFIED = "image/*";
-    public static final String IMAGE_JPEG        = "image/jpeg";
-    public static final String IMAGE_JPG         = "image/jpg";
-    public static final String IMAGE_GIF         = "image/gif";
-    public static final String IMAGE_WBMP        = "image/vnd.wap.wbmp";
-    public static final String IMAGE_PNG         = "image/png";
-    public static final String IMAGE_X_MS_BMP    = "image/x-ms-bmp";
-
-    public static final String AUDIO_UNSPECIFIED = "audio/*";
-    public static final String AUDIO_AAC         = "audio/aac";
-    public static final String AUDIO_AMR         = "audio/amr";
-    public static final String AUDIO_IMELODY     = "audio/imelody";
-    public static final String AUDIO_MID         = "audio/mid";
-    public static final String AUDIO_MIDI        = "audio/midi";
-    public static final String AUDIO_MP3         = "audio/mp3";
-    public static final String AUDIO_MPEG3       = "audio/mpeg3";
-    public static final String AUDIO_MPEG        = "audio/mpeg";
-    public static final String AUDIO_MPG         = "audio/mpg";
-    public static final String AUDIO_MP4         = "audio/mp4";
-    public static final String AUDIO_X_MID       = "audio/x-mid";
-    public static final String AUDIO_X_MIDI      = "audio/x-midi";
-    public static final String AUDIO_X_MP3       = "audio/x-mp3";
-    public static final String AUDIO_X_MPEG3     = "audio/x-mpeg3";
-    public static final String AUDIO_X_MPEG      = "audio/x-mpeg";
-    public static final String AUDIO_X_MPG       = "audio/x-mpg";
-    public static final String AUDIO_3GPP        = "audio/3gpp";
-    public static final String AUDIO_X_WAV       = "audio/x-wav";
-    public static final String AUDIO_OGG         = "application/ogg";
-
-    public static final String VIDEO_UNSPECIFIED = "video/*";
-    public static final String VIDEO_3GPP        = "video/3gpp";
-    public static final String VIDEO_3G2         = "video/3gpp2";
-    public static final String VIDEO_H263        = "video/h263";
-    public static final String VIDEO_MP4         = "video/mp4";
-
-    public static final String APP_SMIL          = "application/smil";
-    public static final String APP_WAP_XHTML     = "application/vnd.wap.xhtml+xml";
-    public static final String APP_XHTML         = "application/xhtml+xml";
-
-    public static final String APP_DRM_CONTENT   = "application/vnd.oma.drm.content";
-    public static final String APP_DRM_MESSAGE   = "application/vnd.oma.drm.message";
-
-    private static final ArrayList<String> sSupportedContentTypes = new ArrayList<String>();
-    private static final ArrayList<String> sSupportedImageTypes = new ArrayList<String>();
-    private static final ArrayList<String> sSupportedAudioTypes = new ArrayList<String>();
-    private static final ArrayList<String> sSupportedVideoTypes = new ArrayList<String>();
-
-    static {
-        sSupportedContentTypes.add(TEXT_PLAIN);
-        sSupportedContentTypes.add(TEXT_HTML);
-        sSupportedContentTypes.add(TEXT_VCALENDAR);
-        sSupportedContentTypes.add(TEXT_VCARD);
-
-        sSupportedContentTypes.add(IMAGE_JPEG);
-        sSupportedContentTypes.add(IMAGE_GIF);
-        sSupportedContentTypes.add(IMAGE_WBMP);
-        sSupportedContentTypes.add(IMAGE_PNG);
-        sSupportedContentTypes.add(IMAGE_JPG);
-        sSupportedContentTypes.add(IMAGE_X_MS_BMP);
-        //supportedContentTypes.add(IMAGE_SVG); not yet supported.
-
-        sSupportedContentTypes.add(AUDIO_AAC);
-        sSupportedContentTypes.add(AUDIO_AMR);
-        sSupportedContentTypes.add(AUDIO_IMELODY);
-        sSupportedContentTypes.add(AUDIO_MID);
-        sSupportedContentTypes.add(AUDIO_MIDI);
-        sSupportedContentTypes.add(AUDIO_MP3);
-        sSupportedContentTypes.add(AUDIO_MP4);
-        sSupportedContentTypes.add(AUDIO_MPEG3);
-        sSupportedContentTypes.add(AUDIO_MPEG);
-        sSupportedContentTypes.add(AUDIO_MPG);
-        sSupportedContentTypes.add(AUDIO_X_MID);
-        sSupportedContentTypes.add(AUDIO_X_MIDI);
-        sSupportedContentTypes.add(AUDIO_X_MP3);
-        sSupportedContentTypes.add(AUDIO_X_MPEG3);
-        sSupportedContentTypes.add(AUDIO_X_MPEG);
-        sSupportedContentTypes.add(AUDIO_X_MPG);
-        sSupportedContentTypes.add(AUDIO_X_WAV);
-        sSupportedContentTypes.add(AUDIO_3GPP);
-        sSupportedContentTypes.add(AUDIO_OGG);
-
-        sSupportedContentTypes.add(VIDEO_3GPP);
-        sSupportedContentTypes.add(VIDEO_3G2);
-        sSupportedContentTypes.add(VIDEO_H263);
-        sSupportedContentTypes.add(VIDEO_MP4);
-
-        sSupportedContentTypes.add(APP_SMIL);
-        sSupportedContentTypes.add(APP_WAP_XHTML);
-        sSupportedContentTypes.add(APP_XHTML);
-
-        sSupportedContentTypes.add(APP_DRM_CONTENT);
-        sSupportedContentTypes.add(APP_DRM_MESSAGE);
-
-        // add supported image types
-        sSupportedImageTypes.add(IMAGE_JPEG);
-        sSupportedImageTypes.add(IMAGE_GIF);
-        sSupportedImageTypes.add(IMAGE_WBMP);
-        sSupportedImageTypes.add(IMAGE_PNG);
-        sSupportedImageTypes.add(IMAGE_JPG);
-        sSupportedImageTypes.add(IMAGE_X_MS_BMP);
-
-        // add supported audio types
-        sSupportedAudioTypes.add(AUDIO_AAC);
-        sSupportedAudioTypes.add(AUDIO_AMR);
-        sSupportedAudioTypes.add(AUDIO_IMELODY);
-        sSupportedAudioTypes.add(AUDIO_MID);
-        sSupportedAudioTypes.add(AUDIO_MIDI);
-        sSupportedAudioTypes.add(AUDIO_MP3);
-        sSupportedAudioTypes.add(AUDIO_MPEG3);
-        sSupportedAudioTypes.add(AUDIO_MPEG);
-        sSupportedAudioTypes.add(AUDIO_MPG);
-        sSupportedAudioTypes.add(AUDIO_MP4);
-        sSupportedAudioTypes.add(AUDIO_X_MID);
-        sSupportedAudioTypes.add(AUDIO_X_MIDI);
-        sSupportedAudioTypes.add(AUDIO_X_MP3);
-        sSupportedAudioTypes.add(AUDIO_X_MPEG3);
-        sSupportedAudioTypes.add(AUDIO_X_MPEG);
-        sSupportedAudioTypes.add(AUDIO_X_MPG);
-        sSupportedAudioTypes.add(AUDIO_X_WAV);
-        sSupportedAudioTypes.add(AUDIO_3GPP);
-        sSupportedAudioTypes.add(AUDIO_OGG);
-
-        // add supported video types
-        sSupportedVideoTypes.add(VIDEO_3GPP);
-        sSupportedVideoTypes.add(VIDEO_3G2);
-        sSupportedVideoTypes.add(VIDEO_H263);
-        sSupportedVideoTypes.add(VIDEO_MP4);
-    }
-
-    // This class should never be instantiated.
-    private ContentType() {
-    }
-
-    public static boolean isSupportedType(String contentType) {
-        return (null != contentType) && sSupportedContentTypes.contains(contentType);
-    }
-
-    public static boolean isSupportedImageType(String contentType) {
-        return isImageType(contentType) && isSupportedType(contentType);
-    }
-
-    public static boolean isSupportedAudioType(String contentType) {
-        return isAudioType(contentType) && isSupportedType(contentType);
-    }
-
-    public static boolean isSupportedVideoType(String contentType) {
-        return isVideoType(contentType) && isSupportedType(contentType);
-    }
-
-    public static boolean isTextType(String contentType) {
-        return (null != contentType) && contentType.startsWith("text/");
-    }
-
-    public static boolean isImageType(String contentType) {
-        return (null != contentType) && contentType.startsWith("image/");
-    }
-
-    public static boolean isAudioType(String contentType) {
-        return (null != contentType) && contentType.startsWith("audio/");
-    }
-
-    public static boolean isVideoType(String contentType) {
-        return (null != contentType) && contentType.startsWith("video/");
-    }
-
-    public static boolean isDrmType(String contentType) {
-        return (null != contentType)
-                && (contentType.equals(APP_DRM_CONTENT)
-                        || contentType.equals(APP_DRM_MESSAGE));
-    }
-
-    public static boolean isUnspecified(String contentType) {
-        return (null != contentType) && contentType.endsWith("*");
-    }
-
-    @SuppressWarnings("unchecked")
-    public static ArrayList<String> getImageTypes() {
-        return (ArrayList<String>) sSupportedImageTypes.clone();
-    }
-
-    @SuppressWarnings("unchecked")
-    public static ArrayList<String> getAudioTypes() {
-        return (ArrayList<String>) sSupportedAudioTypes.clone();
-    }
-
-    @SuppressWarnings("unchecked")
-    public static ArrayList<String> getVideoTypes() {
-        return (ArrayList<String>) sSupportedVideoTypes.clone();
-    }
-
-    @SuppressWarnings("unchecked")
-    public static ArrayList<String> getSupportedTypes() {
-        return (ArrayList<String>) sSupportedContentTypes.clone();
-    }
-}
diff --git a/src/java/com/google/android/mms/InvalidHeaderValueException.java b/src/java/com/google/android/mms/InvalidHeaderValueException.java
deleted file mode 100644
index 73d7832..0000000
--- a/src/java/com/google/android/mms/InvalidHeaderValueException.java
+++ /dev/null
@@ -1,41 +0,0 @@
-/*
- * Copyright (C) 2007 Esmertec AG.
- * Copyright (C) 2007 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.google.android.mms;
-
-/**
- * Thrown when an invalid header value was set.
- */
-public class InvalidHeaderValueException extends MmsException {
-    private static final long serialVersionUID = -2053384496042052262L;
-
-    /**
-     * Constructs an InvalidHeaderValueException with no detailed message.
-     */
-    public InvalidHeaderValueException() {
-        super();
-    }
-
-    /**
-     * Constructs an InvalidHeaderValueException with the specified detailed message.
-     *
-     * @param message the detailed message.
-     */
-    public InvalidHeaderValueException(String message) {
-        super(message);
-    }
-}
diff --git a/src/java/com/google/android/mms/MmsException.java b/src/java/com/google/android/mms/MmsException.java
deleted file mode 100644
index 6ca0c7e..0000000
--- a/src/java/com/google/android/mms/MmsException.java
+++ /dev/null
@@ -1,60 +0,0 @@
-/*
- * Copyright (C) 2007 Esmertec AG.
- * Copyright (C) 2007 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.google.android.mms;
-
-/**
- * A generic exception that is thrown by the Mms client.
- */
-public class MmsException extends Exception {
-    private static final long serialVersionUID = -7323249827281485390L;
-
-    /**
-     * Creates a new MmsException.
-     */
-    public MmsException() {
-        super();
-    }
-
-    /**
-     * Creates a new MmsException with the specified detail message.
-     *
-     * @param message the detail message.
-     */
-    public MmsException(String message) {
-        super(message);
-    }
-
-    /**
-     * Creates a new MmsException with the specified cause.
-     *
-     * @param cause the cause.
-     */
-    public MmsException(Throwable cause) {
-        super(cause);
-    }
-
-    /**
-     * Creates a new MmsException with the specified detail message and cause.
-     *
-     * @param message the detail message.
-     * @param cause the cause.
-     */
-    public MmsException(String message, Throwable cause) {
-        super(message, cause);
-    }
-}
diff --git a/src/java/com/google/android/mms/package.html b/src/java/com/google/android/mms/package.html
deleted file mode 100755
index c9f96a6..0000000
--- a/src/java/com/google/android/mms/package.html
+++ /dev/null
@@ -1,5 +0,0 @@
-<body>
-
-{@hide}
-
-</body>
diff --git a/src/java/com/google/android/mms/pdu/AcknowledgeInd.java b/src/java/com/google/android/mms/pdu/AcknowledgeInd.java
deleted file mode 100644
index 0e96c60..0000000
--- a/src/java/com/google/android/mms/pdu/AcknowledgeInd.java
+++ /dev/null
@@ -1,89 +0,0 @@
-/*
- * Copyright (C) 2007 Esmertec AG.
- * Copyright (C) 2007 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.google.android.mms.pdu;
-
-import com.google.android.mms.InvalidHeaderValueException;
-
-/**
- * M-Acknowledge.ind PDU.
- */
-public class AcknowledgeInd extends GenericPdu {
-    /**
-     * Constructor, used when composing a M-Acknowledge.ind pdu.
-     *
-     * @param mmsVersion current viersion of mms
-     * @param transactionId the transaction-id value
-     * @throws InvalidHeaderValueException if parameters are invalid.
-     *         NullPointerException if transactionId is null.
-     */
-    public AcknowledgeInd(int mmsVersion, byte[] transactionId)
-            throws InvalidHeaderValueException {
-        super();
-
-        setMessageType(PduHeaders.MESSAGE_TYPE_ACKNOWLEDGE_IND);
-        setMmsVersion(mmsVersion);
-        setTransactionId(transactionId);
-    }
-
-    /**
-     * Constructor with given headers.
-     *
-     * @param headers Headers for this PDU.
-     */
-    AcknowledgeInd(PduHeaders headers) {
-        super(headers);
-    }
-
-    /**
-     * Get X-Mms-Report-Allowed field value.
-     *
-     * @return the X-Mms-Report-Allowed value
-     */
-    public int getReportAllowed() {
-        return mPduHeaders.getOctet(PduHeaders.REPORT_ALLOWED);
-    }
-
-    /**
-     * Set X-Mms-Report-Allowed field value.
-     *
-     * @param value the value
-     * @throws InvalidHeaderValueException if the value is invalid.
-     */
-    public void setReportAllowed(int value) throws InvalidHeaderValueException {
-        mPduHeaders.setOctet(value, PduHeaders.REPORT_ALLOWED);
-    }
-
-    /**
-     * Get X-Mms-Transaction-Id field value.
-     *
-     * @return the X-Mms-Report-Allowed value
-     */
-    public byte[] getTransactionId() {
-        return mPduHeaders.getTextString(PduHeaders.TRANSACTION_ID);
-    }
-
-    /**
-     * Set X-Mms-Transaction-Id field value.
-     *
-     * @param value the value
-     * @throws NullPointerException if the value is null.
-     */
-    public void setTransactionId(byte[] value) {
-        mPduHeaders.setTextString(value, PduHeaders.TRANSACTION_ID);
-    }
-}
diff --git a/src/java/com/google/android/mms/pdu/Base64.java b/src/java/com/google/android/mms/pdu/Base64.java
deleted file mode 100644
index 604bee0..0000000
--- a/src/java/com/google/android/mms/pdu/Base64.java
+++ /dev/null
@@ -1,167 +0,0 @@
-/*
- * Copyright (C) 2007 Esmertec AG.
- * Copyright (C) 2007 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.google.android.mms.pdu;
-
-public class Base64 {
-    /**
-     * Used to get the number of Quadruples.
-     */
-    static final int FOURBYTE = 4;
-
-    /**
-     * Byte used to pad output.
-     */
-    static final byte PAD = (byte) '=';
-
-    /**
-     * The base length.
-     */
-    static final int BASELENGTH = 255;
-
-    // Create arrays to hold the base64 characters
-    private static byte[] base64Alphabet = new byte[BASELENGTH];
-
-    // Populating the character arrays
-    static {
-        for (int i = 0; i < BASELENGTH; i++) {
-            base64Alphabet[i] = (byte) -1;
-        }
-        for (int i = 'Z'; i >= 'A'; i--) {
-            base64Alphabet[i] = (byte) (i - 'A');
-        }
-        for (int i = 'z'; i >= 'a'; i--) {
-            base64Alphabet[i] = (byte) (i - 'a' + 26);
-        }
-        for (int i = '9'; i >= '0'; i--) {
-            base64Alphabet[i] = (byte) (i - '0' + 52);
-        }
-
-        base64Alphabet['+'] = 62;
-        base64Alphabet['/'] = 63;
-    }
-
-    /**
-     * Decodes Base64 data into octects
-     *
-     * @param base64Data Byte array containing Base64 data
-     * @return Array containing decoded data.
-     */
-    public static byte[] decodeBase64(byte[] base64Data) {
-        // RFC 2045 requires that we discard ALL non-Base64 characters
-        base64Data = discardNonBase64(base64Data);
-
-        // handle the edge case, so we don't have to worry about it later
-        if (base64Data.length == 0) {
-            return new byte[0];
-        }
-
-        int numberQuadruple = base64Data.length / FOURBYTE;
-        byte decodedData[] = null;
-        byte b1 = 0, b2 = 0, b3 = 0, b4 = 0, marker0 = 0, marker1 = 0;
-
-        // Throw away anything not in base64Data
-
-        int encodedIndex = 0;
-        int dataIndex = 0;
-        {
-            // this sizes the output array properly - rlw
-            int lastData = base64Data.length;
-            // ignore the '=' padding
-            while (base64Data[lastData - 1] == PAD) {
-                if (--lastData == 0) {
-                    return new byte[0];
-                }
-            }
-            decodedData = new byte[lastData - numberQuadruple];
-        }
-
-        for (int i = 0; i < numberQuadruple; i++) {
-            dataIndex = i * 4;
-            marker0 = base64Data[dataIndex + 2];
-            marker1 = base64Data[dataIndex + 3];
-
-            b1 = base64Alphabet[base64Data[dataIndex]];
-            b2 = base64Alphabet[base64Data[dataIndex + 1]];
-
-            if (marker0 != PAD && marker1 != PAD) {
-                //No PAD e.g 3cQl
-                b3 = base64Alphabet[marker0];
-                b4 = base64Alphabet[marker1];
-
-                decodedData[encodedIndex] = (byte) (b1 << 2 | b2 >> 4);
-                decodedData[encodedIndex + 1] =
-                    (byte) (((b2 & 0xf) << 4) | ((b3 >> 2) & 0xf));
-                decodedData[encodedIndex + 2] = (byte) (b3 << 6 | b4);
-            } else if (marker0 == PAD) {
-                //Two PAD e.g. 3c[Pad][Pad]
-                decodedData[encodedIndex] = (byte) (b1 << 2 | b2 >> 4);
-            } else if (marker1 == PAD) {
-                //One PAD e.g. 3cQ[Pad]
-                b3 = base64Alphabet[marker0];
-
-                decodedData[encodedIndex] = (byte) (b1 << 2 | b2 >> 4);
-                decodedData[encodedIndex + 1] =
-                    (byte) (((b2 & 0xf) << 4) | ((b3 >> 2) & 0xf));
-            }
-            encodedIndex += 3;
-        }
-        return decodedData;
-    }
-
-    /**
-     * Check octect wheter it is a base64 encoding.
-     *
-     * @param octect to be checked byte
-     * @return ture if it is base64 encoding, false otherwise.
-     */
-    private static boolean isBase64(byte octect) {
-        if (octect == PAD) {
-            return true;
-        } else if (base64Alphabet[octect] == -1) {
-            return false;
-        } else {
-            return true;
-        }
-    }
-
-    /**
-     * Discards any characters outside of the base64 alphabet, per
-     * the requirements on page 25 of RFC 2045 - "Any characters
-     * outside of the base64 alphabet are to be ignored in base64
-     * encoded data."
-     *
-     * @param data The base-64 encoded data to groom
-     * @return The data, less non-base64 characters (see RFC 2045).
-     */
-    static byte[] discardNonBase64(byte[] data) {
-        byte groomedData[] = new byte[data.length];
-        int bytesCopied = 0;
-
-        for (int i = 0; i < data.length; i++) {
-            if (isBase64(data[i])) {
-                groomedData[bytesCopied++] = data[i];
-            }
-        }
-
-        byte packedData[] = new byte[bytesCopied];
-
-        System.arraycopy(groomedData, 0, packedData, 0, bytesCopied);
-
-        return packedData;
-    }
-}
diff --git a/src/java/com/google/android/mms/pdu/CharacterSets.java b/src/java/com/google/android/mms/pdu/CharacterSets.java
deleted file mode 100644
index 4e22ca5..0000000
--- a/src/java/com/google/android/mms/pdu/CharacterSets.java
+++ /dev/null
@@ -1,172 +0,0 @@
-/*
- * Copyright (C) 2007 Esmertec AG.
- * Copyright (C) 2007 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.google.android.mms.pdu;
-
-import java.io.UnsupportedEncodingException;
-import java.util.HashMap;
-
-public class CharacterSets {
-    /**
-     * IANA assigned MIB enum numbers.
-     *
-     * From wap-230-wsp-20010705-a.pdf
-     * Any-charset = <Octet 128>
-     * Equivalent to the special RFC2616 charset value "*"
-     */
-    public static final int ANY_CHARSET = 0x00;
-    public static final int US_ASCII    = 0x03;
-    public static final int ISO_8859_1  = 0x04;
-    public static final int ISO_8859_2  = 0x05;
-    public static final int ISO_8859_3  = 0x06;
-    public static final int ISO_8859_4  = 0x07;
-    public static final int ISO_8859_5  = 0x08;
-    public static final int ISO_8859_6  = 0x09;
-    public static final int ISO_8859_7  = 0x0A;
-    public static final int ISO_8859_8  = 0x0B;
-    public static final int ISO_8859_9  = 0x0C;
-    public static final int SHIFT_JIS   = 0x11;
-    public static final int UTF_8       = 0x6A;
-    public static final int BIG5        = 0x07EA;
-    public static final int UCS2        = 0x03E8;
-    public static final int UTF_16      = 0x03F7;
-
-    /**
-     * If the encoding of given data is unsupported, use UTF_8 to decode it.
-     */
-    public static final int DEFAULT_CHARSET = UTF_8;
-
-    /**
-     * Array of MIB enum numbers.
-     */
-    private static final int[] MIBENUM_NUMBERS = {
-        ANY_CHARSET,
-        US_ASCII,
-        ISO_8859_1,
-        ISO_8859_2,
-        ISO_8859_3,
-        ISO_8859_4,
-        ISO_8859_5,
-        ISO_8859_6,
-        ISO_8859_7,
-        ISO_8859_8,
-        ISO_8859_9,
-        SHIFT_JIS,
-        UTF_8,
-        BIG5,
-        UCS2,
-        UTF_16,
-    };
-
-    /**
-     * The Well-known-charset Mime name.
-     */
-    public static final String MIMENAME_ANY_CHARSET = "*";
-    public static final String MIMENAME_US_ASCII    = "us-ascii";
-    public static final String MIMENAME_ISO_8859_1  = "iso-8859-1";
-    public static final String MIMENAME_ISO_8859_2  = "iso-8859-2";
-    public static final String MIMENAME_ISO_8859_3  = "iso-8859-3";
-    public static final String MIMENAME_ISO_8859_4  = "iso-8859-4";
-    public static final String MIMENAME_ISO_8859_5  = "iso-8859-5";
-    public static final String MIMENAME_ISO_8859_6  = "iso-8859-6";
-    public static final String MIMENAME_ISO_8859_7  = "iso-8859-7";
-    public static final String MIMENAME_ISO_8859_8  = "iso-8859-8";
-    public static final String MIMENAME_ISO_8859_9  = "iso-8859-9";
-    public static final String MIMENAME_SHIFT_JIS   = "shift_JIS";
-    public static final String MIMENAME_UTF_8       = "utf-8";
-    public static final String MIMENAME_BIG5        = "big5";
-    public static final String MIMENAME_UCS2        = "iso-10646-ucs-2";
-    public static final String MIMENAME_UTF_16      = "utf-16";
-
-    public static final String DEFAULT_CHARSET_NAME = MIMENAME_UTF_8;
-
-    /**
-     * Array of the names of character sets.
-     */
-    private static final String[] MIME_NAMES = {
-        MIMENAME_ANY_CHARSET,
-        MIMENAME_US_ASCII,
-        MIMENAME_ISO_8859_1,
-        MIMENAME_ISO_8859_2,
-        MIMENAME_ISO_8859_3,
-        MIMENAME_ISO_8859_4,
-        MIMENAME_ISO_8859_5,
-        MIMENAME_ISO_8859_6,
-        MIMENAME_ISO_8859_7,
-        MIMENAME_ISO_8859_8,
-        MIMENAME_ISO_8859_9,
-        MIMENAME_SHIFT_JIS,
-        MIMENAME_UTF_8,
-        MIMENAME_BIG5,
-        MIMENAME_UCS2,
-        MIMENAME_UTF_16,
-    };
-
-    private static final HashMap<Integer, String> MIBENUM_TO_NAME_MAP;
-    private static final HashMap<String, Integer> NAME_TO_MIBENUM_MAP;
-
-    static {
-        // Create the HashMaps.
-        MIBENUM_TO_NAME_MAP = new HashMap<Integer, String>();
-        NAME_TO_MIBENUM_MAP = new HashMap<String, Integer>();
-        assert(MIBENUM_NUMBERS.length == MIME_NAMES.length);
-        int count = MIBENUM_NUMBERS.length - 1;
-        for(int i = 0; i <= count; i++) {
-            MIBENUM_TO_NAME_MAP.put(MIBENUM_NUMBERS[i], MIME_NAMES[i]);
-            NAME_TO_MIBENUM_MAP.put(MIME_NAMES[i], MIBENUM_NUMBERS[i]);
-        }
-    }
-
-    private CharacterSets() {} // Non-instantiatable
-
-    /**
-     * Map an MIBEnum number to the name of the charset which this number
-     * is assigned to by IANA.
-     *
-     * @param mibEnumValue An IANA assigned MIBEnum number.
-     * @return The name string of the charset.
-     * @throws UnsupportedEncodingException
-     */
-    public static String getMimeName(int mibEnumValue)
-            throws UnsupportedEncodingException {
-        String name = MIBENUM_TO_NAME_MAP.get(mibEnumValue);
-        if (name == null) {
-            throw new UnsupportedEncodingException();
-        }
-        return name;
-    }
-
-    /**
-     * Map a well-known charset name to its assigned MIBEnum number.
-     *
-     * @param mimeName The charset name.
-     * @return The MIBEnum number assigned by IANA for this charset.
-     * @throws UnsupportedEncodingException
-     */
-    public static int getMibEnumValue(String mimeName)
-            throws UnsupportedEncodingException {
-        if(null == mimeName) {
-            return -1;
-        }
-
-        Integer mibEnumValue = NAME_TO_MIBENUM_MAP.get(mimeName);
-        if (mibEnumValue == null) {
-            throw new UnsupportedEncodingException();
-        }
-        return mibEnumValue;
-    }
-}
diff --git a/src/java/com/google/android/mms/pdu/DeliveryInd.java b/src/java/com/google/android/mms/pdu/DeliveryInd.java
deleted file mode 100644
index dafa8d1..0000000
--- a/src/java/com/google/android/mms/pdu/DeliveryInd.java
+++ /dev/null
@@ -1,138 +0,0 @@
-/*
- * Copyright (C) 2007 Esmertec AG.
- * Copyright (C) 2007 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.google.android.mms.pdu;
-
-import com.google.android.mms.InvalidHeaderValueException;
-
-/**
- * M-Delivery.Ind Pdu.
- */
-public class DeliveryInd extends GenericPdu {
-    /**
-     * Empty constructor.
-     * Since the Pdu corresponding to this class is constructed
-     * by the Proxy-Relay server, this class is only instantiated
-     * by the Pdu Parser.
-     *
-     * @throws InvalidHeaderValueException if error occurs.
-     */
-    public DeliveryInd() throws InvalidHeaderValueException {
-        super();
-        setMessageType(PduHeaders.MESSAGE_TYPE_DELIVERY_IND);
-    }
-
-    /**
-     * Constructor with given headers.
-     *
-     * @param headers Headers for this PDU.
-     */
-    DeliveryInd(PduHeaders headers) {
-        super(headers);
-    }
-
-    /**
-     * Get Date value.
-     *
-     * @return the value
-     */
-    public long getDate() {
-        return mPduHeaders.getLongInteger(PduHeaders.DATE);
-    }
-
-    /**
-     * Set Date value.
-     *
-     * @param value the value
-     */
-    public void setDate(long value) {
-        mPduHeaders.setLongInteger(value, PduHeaders.DATE);
-    }
-
-    /**
-     * Get Message-ID value.
-     *
-     * @return the value
-     */
-    public byte[] getMessageId() {
-        return mPduHeaders.getTextString(PduHeaders.MESSAGE_ID);
-    }
-
-    /**
-     * Set Message-ID value.
-     *
-     * @param value the value, should not be null
-     * @throws NullPointerException if the value is null.
-     */
-    public void setMessageId(byte[] value) {
-        mPduHeaders.setTextString(value, PduHeaders.MESSAGE_ID);
-    }
-
-    /**
-     * Get Status value.
-     *
-     * @return the value
-     */
-    public int getStatus() {
-        return mPduHeaders.getOctet(PduHeaders.STATUS);
-    }
-
-    /**
-     * Set Status value.
-     *
-     * @param value the value
-     * @throws InvalidHeaderValueException if the value is invalid.
-     */
-    public void setStatus(int value) throws InvalidHeaderValueException {
-        mPduHeaders.setOctet(value, PduHeaders.STATUS);
-    }
-
-    /**
-     * Get To value.
-     *
-     * @return the value
-     */
-    public EncodedStringValue[] getTo() {
-        return mPduHeaders.getEncodedStringValues(PduHeaders.TO);
-    }
-
-    /**
-     * set To value.
-     *
-     * @param value the value
-     * @throws NullPointerException if the value is null.
-     */
-    public void setTo(EncodedStringValue[] value) {
-        mPduHeaders.setEncodedStringValues(value, PduHeaders.TO);
-    }
-
-    /*
-     * Optional, not supported header fields:
-     *
-     *     public byte[] getApplicId() {return null;}
-     *     public void setApplicId(byte[] value) {}
-     *
-     *     public byte[] getAuxApplicId() {return null;}
-     *     public void getAuxApplicId(byte[] value) {}
-     *
-     *     public byte[] getReplyApplicId() {return 0x00;}
-     *     public void setReplyApplicId(byte[] value) {}
-     *
-     *     public EncodedStringValue getStatusText() {return null;}
-     *     public void setStatusText(EncodedStringValue value) {}
-     */
-}
diff --git a/src/java/com/google/android/mms/pdu/EncodedStringValue.java b/src/java/com/google/android/mms/pdu/EncodedStringValue.java
deleted file mode 100644
index 14ef772..0000000
--- a/src/java/com/google/android/mms/pdu/EncodedStringValue.java
+++ /dev/null
@@ -1,283 +0,0 @@
-/*
- * Copyright (C) 2007-2008 Esmertec AG.
- * Copyright (C) 2007-2008 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.google.android.mms.pdu;
-
-import android.util.Log;
-
-import java.io.ByteArrayOutputStream;
-import java.io.IOException;
-import java.io.UnsupportedEncodingException;
-import java.util.ArrayList;
-
-/**
- * Encoded-string-value = Text-string | Value-length Char-set Text-string
- */
-public class EncodedStringValue implements Cloneable {
-    private static final String TAG = "EncodedStringValue";
-    private static final boolean DEBUG = false;
-    private static final boolean LOCAL_LOGV = false;
-
-    /**
-     * The Char-set value.
-     */
-    private int mCharacterSet;
-
-    /**
-     * The Text-string value.
-     */
-    private byte[] mData;
-
-    /**
-     * Constructor.
-     *
-     * @param charset the Char-set value
-     * @param data the Text-string value
-     * @throws NullPointerException if Text-string value is null.
-     */
-    public EncodedStringValue(int charset, byte[] data) {
-        // TODO: CharSet needs to be validated against MIBEnum.
-        if(null == data) {
-            throw new NullPointerException("EncodedStringValue: Text-string is null.");
-        }
-
-        mCharacterSet = charset;
-        mData = new byte[data.length];
-        System.arraycopy(data, 0, mData, 0, data.length);
-    }
-
-    /**
-     * Constructor.
-     *
-     * @param data the Text-string value
-     * @throws NullPointerException if Text-string value is null.
-     */
-    public EncodedStringValue(byte[] data) {
-        this(CharacterSets.DEFAULT_CHARSET, data);
-    }
-
-    public EncodedStringValue(String data) {
-        try {
-            mData = data.getBytes(CharacterSets.DEFAULT_CHARSET_NAME);
-            mCharacterSet = CharacterSets.DEFAULT_CHARSET;
-        } catch (UnsupportedEncodingException e) {
-            Log.e(TAG, "Default encoding must be supported.", e);
-        }
-    }
-
-    /**
-     * Get Char-set value.
-     *
-     * @return the value
-     */
-    public int getCharacterSet() {
-        return mCharacterSet;
-    }
-
-    /**
-     * Set Char-set value.
-     *
-     * @param charset the Char-set value
-     */
-    public void setCharacterSet(int charset) {
-        // TODO: CharSet needs to be validated against MIBEnum.
-        mCharacterSet = charset;
-    }
-
-    /**
-     * Get Text-string value.
-     *
-     * @return the value
-     */
-    public byte[] getTextString() {
-        byte[] byteArray = new byte[mData.length];
-
-        System.arraycopy(mData, 0, byteArray, 0, mData.length);
-        return byteArray;
-    }
-
-    /**
-     * Set Text-string value.
-     *
-     * @param textString the Text-string value
-     * @throws NullPointerException if Text-string value is null.
-     */
-    public void setTextString(byte[] textString) {
-        if(null == textString) {
-            throw new NullPointerException("EncodedStringValue: Text-string is null.");
-        }
-
-        mData = new byte[textString.length];
-        System.arraycopy(textString, 0, mData, 0, textString.length);
-    }
-
-    /**
-     * Convert this object to a {@link java.lang.String}. If the encoding of
-     * the EncodedStringValue is null or unsupported, it will be
-     * treated as iso-8859-1 encoding.
-     *
-     * @return The decoded String.
-     */
-    public String getString()  {
-        if (CharacterSets.ANY_CHARSET == mCharacterSet) {
-            return new String(mData); // system default encoding.
-        } else {
-            try {
-                String name = CharacterSets.getMimeName(mCharacterSet);
-                return new String(mData, name);
-            } catch (UnsupportedEncodingException e) {
-            	if (LOCAL_LOGV) {
-            		Log.v(TAG, e.getMessage(), e);
-            	}
-            	try {
-                    return new String(mData, CharacterSets.MIMENAME_ISO_8859_1);
-                } catch (UnsupportedEncodingException e2) {
-                    return new String(mData); // system default encoding.
-                }
-            }
-        }
-    }
-
-    /**
-     * Append to Text-string.
-     *
-     * @param textString the textString to append
-     * @throws NullPointerException if the text String is null
-     *                      or an IOException occured.
-     */
-    public void appendTextString(byte[] textString) {
-        if(null == textString) {
-            throw new NullPointerException("Text-string is null.");
-        }
-
-        if(null == mData) {
-            mData = new byte[textString.length];
-            System.arraycopy(textString, 0, mData, 0, textString.length);
-        } else {
-            ByteArrayOutputStream newTextString = new ByteArrayOutputStream();
-            try {
-                newTextString.write(mData);
-                newTextString.write(textString);
-            } catch (IOException e) {
-                e.printStackTrace();
-                throw new NullPointerException(
-                        "appendTextString: failed when write a new Text-string");
-            }
-
-            mData = newTextString.toByteArray();
-        }
-    }
-
-    /*
-     * (non-Javadoc)
-     * @see java.lang.Object#clone()
-     */
-    @Override
-    public Object clone() throws CloneNotSupportedException {
-        super.clone();
-        int len = mData.length;
-        byte[] dstBytes = new byte[len];
-        System.arraycopy(mData, 0, dstBytes, 0, len);
-
-        try {
-            return new EncodedStringValue(mCharacterSet, dstBytes);
-        } catch (Exception e) {
-            Log.e(TAG, "failed to clone an EncodedStringValue: " + this);
-            e.printStackTrace();
-            throw new CloneNotSupportedException(e.getMessage());
-        }
-    }
-
-    /**
-     * Split this encoded string around matches of the given pattern.
-     *
-     * @param pattern the delimiting pattern
-     * @return the array of encoded strings computed by splitting this encoded
-     *         string around matches of the given pattern
-     */
-    public EncodedStringValue[] split(String pattern) {
-        String[] temp = getString().split(pattern);
-        EncodedStringValue[] ret = new EncodedStringValue[temp.length];
-        for (int i = 0; i < ret.length; ++i) {
-            try {
-                ret[i] = new EncodedStringValue(mCharacterSet,
-                        temp[i].getBytes());
-            } catch (NullPointerException e) {
-                // Can't arrive here
-                return null;
-            }
-        }
-        return ret;
-    }
-
-    /**
-     * Extract an EncodedStringValue[] from a given String.
-     */
-    public static EncodedStringValue[] extract(String src) {
-        String[] values = src.split(";");
-
-        ArrayList<EncodedStringValue> list = new ArrayList<EncodedStringValue>();
-        for (int i = 0; i < values.length; i++) {
-            if (values[i].length() > 0) {
-                list.add(new EncodedStringValue(values[i]));
-            }
-        }
-
-        int len = list.size();
-        if (len > 0) {
-            return list.toArray(new EncodedStringValue[len]);
-        } else {
-            return null;
-        }
-    }
-
-    /**
-     * Concatenate an EncodedStringValue[] into a single String.
-     */
-    public static String concat(EncodedStringValue[] addr) {
-        StringBuilder sb = new StringBuilder();
-        int maxIndex = addr.length - 1;
-        for (int i = 0; i <= maxIndex; i++) {
-            sb.append(addr[i].getString());
-            if (i < maxIndex) {
-                sb.append(";");
-            }
-        }
-
-        return sb.toString();
-    }
-
-    public static EncodedStringValue copy(EncodedStringValue value) {
-        if (value == null) {
-            return null;
-        }
-
-        return new EncodedStringValue(value.mCharacterSet, value.mData);
-    }
-    
-    public static EncodedStringValue[] encodeStrings(String[] array) {
-        int count = array.length;
-        if (count > 0) {
-            EncodedStringValue[] encodedArray = new EncodedStringValue[count];
-            for (int i = 0; i < count; i++) {
-                encodedArray[i] = new EncodedStringValue(array[i]);
-            }
-            return encodedArray;
-        }
-        return null;
-    }
-}
diff --git a/src/java/com/google/android/mms/pdu/GenericPdu.java b/src/java/com/google/android/mms/pdu/GenericPdu.java
deleted file mode 100644
index 705de6a..0000000
--- a/src/java/com/google/android/mms/pdu/GenericPdu.java
+++ /dev/null
@@ -1,113 +0,0 @@
-/*
- * Copyright (C) 2007 Esmertec AG.
- * Copyright (C) 2007 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.google.android.mms.pdu;
-
-import com.google.android.mms.InvalidHeaderValueException;
-
-public class GenericPdu {
-    /**
-     * The headers of pdu.
-     */
-    PduHeaders mPduHeaders = null;
-
-    /**
-     * Constructor.
-     */
-    public GenericPdu() {
-        mPduHeaders = new PduHeaders();
-    }
-
-    /**
-     * Constructor.
-     *
-     * @param headers Headers for this PDU.
-     */
-    GenericPdu(PduHeaders headers) {
-        mPduHeaders = headers;
-    }
-
-    /**
-     * Get the headers of this PDU.
-     *
-     * @return A PduHeaders of this PDU.
-     */
-    PduHeaders getPduHeaders() {
-        return mPduHeaders;
-    }
-
-    /**
-     * Get X-Mms-Message-Type field value.
-     *
-     * @return the X-Mms-Report-Allowed value
-     */
-    public int getMessageType() {
-        return mPduHeaders.getOctet(PduHeaders.MESSAGE_TYPE);
-    }
-
-    /**
-     * Set X-Mms-Message-Type field value.
-     *
-     * @param value the value
-     * @throws InvalidHeaderValueException if the value is invalid.
-     *         RuntimeException if field's value is not Octet.
-     */
-    public void setMessageType(int value) throws InvalidHeaderValueException {
-        mPduHeaders.setOctet(value, PduHeaders.MESSAGE_TYPE);
-    }
-
-    /**
-     * Get X-Mms-MMS-Version field value.
-     *
-     * @return the X-Mms-MMS-Version value
-     */
-    public int getMmsVersion() {
-        return mPduHeaders.getOctet(PduHeaders.MMS_VERSION);
-    }
-
-    /**
-     * Set X-Mms-MMS-Version field value.
-     *
-     * @param value the value
-     * @throws InvalidHeaderValueException if the value is invalid.
-     *         RuntimeException if field's value is not Octet.
-     */
-    public void setMmsVersion(int value) throws InvalidHeaderValueException {
-        mPduHeaders.setOctet(value, PduHeaders.MMS_VERSION);
-    }
-
-    /**
-     * Get From value.
-     * From-value = Value-length
-     *      (Address-present-token Encoded-string-value | Insert-address-token)
-     *
-     * @return the value
-     */
-    public EncodedStringValue getFrom() {
-       return mPduHeaders.getEncodedStringValue(PduHeaders.FROM);
-    }
-
-    /**
-     * Set From value.
-     *
-     * @param value the value
-     * @throws NullPointerException if the value is null.
-     */
-    public void setFrom(EncodedStringValue value) {
-        mPduHeaders.setEncodedStringValue(value, PduHeaders.FROM);
-    }
-}
diff --git a/src/java/com/google/android/mms/pdu/MultimediaMessagePdu.java b/src/java/com/google/android/mms/pdu/MultimediaMessagePdu.java
deleted file mode 100644
index 5a85e0e..0000000
--- a/src/java/com/google/android/mms/pdu/MultimediaMessagePdu.java
+++ /dev/null
@@ -1,150 +0,0 @@
-/*
- * Copyright (C) 2007 Esmertec AG.
- * Copyright (C) 2007 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.google.android.mms.pdu;
-
-import com.google.android.mms.InvalidHeaderValueException;
-
-/**
- * Multimedia message PDU.
- */
-public class MultimediaMessagePdu extends GenericPdu{
-    /**
-     * The body.
-     */
-    private PduBody mMessageBody;
-
-    /**
-     * Constructor.
-     */
-    public MultimediaMessagePdu() {
-        super();
-    }
-
-    /**
-     * Constructor.
-     *
-     * @param header the header of this PDU
-     * @param body the body of this PDU
-     */
-    public MultimediaMessagePdu(PduHeaders header, PduBody body) {
-        super(header);
-        mMessageBody = body;
-    }
-
-    /**
-     * Constructor with given headers.
-     *
-     * @param headers Headers for this PDU.
-     */
-    MultimediaMessagePdu(PduHeaders headers) {
-        super(headers);
-    }
-
-    /**
-     * Get body of the PDU.
-     *
-     * @return the body
-     */
-    public PduBody getBody() {
-        return mMessageBody;
-    }
-
-    /**
-     * Set body of the PDU.
-     *
-     * @param body the body
-     */
-    public void setBody(PduBody body) {
-        mMessageBody = body;
-    }
-
-    /**
-     * Get subject.
-     *
-     * @return the value
-     */
-    public EncodedStringValue getSubject() {
-        return mPduHeaders.getEncodedStringValue(PduHeaders.SUBJECT);
-    }
-
-    /**
-     * Set subject.
-     *
-     * @param value the value
-     * @throws NullPointerException if the value is null.
-     */
-    public void setSubject(EncodedStringValue value) {
-        mPduHeaders.setEncodedStringValue(value, PduHeaders.SUBJECT);
-    }
-
-    /**
-     * Get To value.
-     *
-     * @return the value
-     */
-    public EncodedStringValue[] getTo() {
-        return mPduHeaders.getEncodedStringValues(PduHeaders.TO);
-    }
-
-    /**
-     * Add a "To" value.
-     *
-     * @param value the value
-     * @throws NullPointerException if the value is null.
-     */
-    public void addTo(EncodedStringValue value) {
-        mPduHeaders.appendEncodedStringValue(value, PduHeaders.TO);
-    }
-
-    /**
-     * Get X-Mms-Priority value.
-     *
-     * @return the value
-     */
-    public int getPriority() {
-        return mPduHeaders.getOctet(PduHeaders.PRIORITY);
-    }
-
-    /**
-     * Set X-Mms-Priority value.
-     *
-     * @param value the value
-     * @throws InvalidHeaderValueException if the value is invalid.
-     */
-    public void setPriority(int value) throws InvalidHeaderValueException {
-        mPduHeaders.setOctet(value, PduHeaders.PRIORITY);
-    }
-
-    /**
-     * Get Date value.
-     *
-     * @return the value
-     */
-    public long getDate() {
-        return mPduHeaders.getLongInteger(PduHeaders.DATE);
-    }
-
-    /**
-     * Set Date value in seconds.
-     *
-     * @param value the value
-     */
-    public void setDate(long value) {
-        mPduHeaders.setLongInteger(value, PduHeaders.DATE);
-    }
-}
diff --git a/src/java/com/google/android/mms/pdu/NotificationInd.java b/src/java/com/google/android/mms/pdu/NotificationInd.java
deleted file mode 100644
index c56cba6..0000000
--- a/src/java/com/google/android/mms/pdu/NotificationInd.java
+++ /dev/null
@@ -1,285 +0,0 @@
-/*
- * Copyright (C) 2007 Esmertec AG.
- * Copyright (C) 2007 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.google.android.mms.pdu;
-
-import com.google.android.mms.InvalidHeaderValueException;
-
-/**
- * M-Notification.ind PDU.
- */
-public class NotificationInd extends GenericPdu {
-    /**
-     * Empty constructor.
-     * Since the Pdu corresponding to this class is constructed
-     * by the Proxy-Relay server, this class is only instantiated
-     * by the Pdu Parser.
-     *
-     * @throws InvalidHeaderValueException if error occurs.
-     *         RuntimeException if an undeclared error occurs.
-     */
-    public NotificationInd() throws InvalidHeaderValueException {
-        super();
-        setMessageType(PduHeaders.MESSAGE_TYPE_NOTIFICATION_IND);
-    }
-
-    /**
-     * Constructor with given headers.
-     *
-     * @param headers Headers for this PDU.
-     */
-    NotificationInd(PduHeaders headers) {
-        super(headers);
-    }
-
-    /**
-     * Get X-Mms-Content-Class Value.
-     *
-     * @return the value
-     */
-    public int getContentClass() {
-        return mPduHeaders.getOctet(PduHeaders.CONTENT_CLASS);
-    }
-
-    /**
-     * Set X-Mms-Content-Class Value.
-     *
-     * @param value the value
-     * @throws InvalidHeaderValueException if the value is invalid.
-     *         RuntimeException if an undeclared error occurs.
-     */
-    public void setContentClass(int value) throws InvalidHeaderValueException {
-        mPduHeaders.setOctet(value, PduHeaders.CONTENT_CLASS);
-    }
-
-    /**
-     * Get X-Mms-Content-Location value.
-     * When used in a PDU other than M-Mbox-Delete.conf and M-Delete.conf:
-     * Content-location-value = Uri-value
-     *
-     * @return the value
-     */
-    public byte[] getContentLocation() {
-        return mPduHeaders.getTextString(PduHeaders.CONTENT_LOCATION);
-    }
-
-    /**
-     * Set X-Mms-Content-Location value.
-     *
-     * @param value the value
-     * @throws NullPointerException if the value is null.
-     *         RuntimeException if an undeclared error occurs.
-     */
-    public void setContentLocation(byte[] value) {
-        mPduHeaders.setTextString(value, PduHeaders.CONTENT_LOCATION);
-    }
-
-    /**
-     * Get X-Mms-Expiry value.
-     *
-     * Expiry-value = Value-length
-     *      (Absolute-token Date-value | Relative-token Delta-seconds-value)
-     *
-     * @return the value
-     */
-    public long getExpiry() {
-        return mPduHeaders.getLongInteger(PduHeaders.EXPIRY);
-    }
-
-    /**
-     * Set X-Mms-Expiry value.
-     *
-     * @param value the value
-     * @throws RuntimeException if an undeclared error occurs.
-     */
-    public void setExpiry(long value) {
-        mPduHeaders.setLongInteger(value, PduHeaders.EXPIRY);
-    }
-
-    /**
-     * Get From value.
-     * From-value = Value-length
-     *      (Address-present-token Encoded-string-value | Insert-address-token)
-     *
-     * @return the value
-     */
-    public EncodedStringValue getFrom() {
-       return mPduHeaders.getEncodedStringValue(PduHeaders.FROM);
-    }
-
-    /**
-     * Set From value.
-     *
-     * @param value the value
-     * @throws NullPointerException if the value is null.
-     *         RuntimeException if an undeclared error occurs.
-     */
-    public void setFrom(EncodedStringValue value) {
-        mPduHeaders.setEncodedStringValue(value, PduHeaders.FROM);
-    }
-
-    /**
-     * Get X-Mms-Message-Class value.
-     * Message-class-value = Class-identifier | Token-text
-     * Class-identifier = Personal | Advertisement | Informational | Auto
-     *
-     * @return the value
-     */
-    public byte[] getMessageClass() {
-        return mPduHeaders.getTextString(PduHeaders.MESSAGE_CLASS);
-    }
-
-    /**
-     * Set X-Mms-Message-Class value.
-     *
-     * @param value the value
-     * @throws NullPointerException if the value is null.
-     *         RuntimeException if an undeclared error occurs.
-     */
-    public void setMessageClass(byte[] value) {
-        mPduHeaders.setTextString(value, PduHeaders.MESSAGE_CLASS);
-    }
-
-    /**
-     * Get X-Mms-Message-Size value.
-     * Message-size-value = Long-integer
-     *
-     * @return the value
-     */
-    public long getMessageSize() {
-        return mPduHeaders.getLongInteger(PduHeaders.MESSAGE_SIZE);
-    }
-
-    /**
-     * Set X-Mms-Message-Size value.
-     *
-     * @param value the value
-     * @throws RuntimeException if an undeclared error occurs.
-     */
-    public void setMessageSize(long value) {
-        mPduHeaders.setLongInteger(value, PduHeaders.MESSAGE_SIZE);
-    }
-
-    /**
-     * Get subject.
-     *
-     * @return the value
-     */
-    public EncodedStringValue getSubject() {
-        return mPduHeaders.getEncodedStringValue(PduHeaders.SUBJECT);
-    }
-
-    /**
-     * Set subject.
-     *
-     * @param value the value
-     * @throws NullPointerException if the value is null.
-     *         RuntimeException if an undeclared error occurs.
-     */
-    public void setSubject(EncodedStringValue value) {
-        mPduHeaders.setEncodedStringValue(value, PduHeaders.SUBJECT);
-    }
-
-    /**
-     * Get X-Mms-Transaction-Id.
-     *
-     * @return the value
-     */
-    public byte[] getTransactionId() {
-        return mPduHeaders.getTextString(PduHeaders.TRANSACTION_ID);
-    }
-
-    /**
-     * Set X-Mms-Transaction-Id.
-     *
-     * @param value the value
-     * @throws NullPointerException if the value is null.
-     *         RuntimeException if an undeclared error occurs.
-     */
-    public void setTransactionId(byte[] value) {
-        mPduHeaders.setTextString(value, PduHeaders.TRANSACTION_ID);
-    }
-
-    /**
-     * Get X-Mms-Delivery-Report Value.
-     *
-     * @return the value
-     */
-    public int getDeliveryReport() {
-        return mPduHeaders.getOctet(PduHeaders.DELIVERY_REPORT);
-    }
-
-    /**
-     * Set X-Mms-Delivery-Report Value.
-     *
-     * @param value the value
-     * @throws InvalidHeaderValueException if the value is invalid.
-     *         RuntimeException if an undeclared error occurs.
-     */
-    public void setDeliveryReport(int value) throws InvalidHeaderValueException {
-        mPduHeaders.setOctet(value, PduHeaders.DELIVERY_REPORT);
-    }
-
-    /*
-     * Optional, not supported header fields:
-     *
-     *     public byte[] getApplicId() {return null;}
-     *     public void setApplicId(byte[] value) {}
-     *
-     *     public byte[] getAuxApplicId() {return null;}
-     *     public void getAuxApplicId(byte[] value) {}
-     *
-     *     public byte getDrmContent() {return 0x00;}
-     *     public void setDrmContent(byte value) {}
-     *
-     *     public byte getDistributionIndicator() {return 0x00;}
-     *     public void setDistributionIndicator(byte value) {}
-     *
-     *     public ElementDescriptorValue getElementDescriptor() {return null;}
-     *     public void getElementDescriptor(ElementDescriptorValue value) {}
-     *
-     *     public byte getPriority() {return 0x00;}
-     *     public void setPriority(byte value) {}
-     *
-     *     public byte getRecommendedRetrievalMode() {return 0x00;}
-     *     public void setRecommendedRetrievalMode(byte value) {}
-     *
-     *     public byte getRecommendedRetrievalModeText() {return 0x00;}
-     *     public void setRecommendedRetrievalModeText(byte value) {}
-     *
-     *     public byte[] getReplaceId() {return 0x00;}
-     *     public void setReplaceId(byte[] value) {}
-     *
-     *     public byte[] getReplyApplicId() {return 0x00;}
-     *     public void setReplyApplicId(byte[] value) {}
-     *
-     *     public byte getReplyCharging() {return 0x00;}
-     *     public void setReplyCharging(byte value) {}
-     *
-     *     public byte getReplyChargingDeadline() {return 0x00;}
-     *     public void setReplyChargingDeadline(byte value) {}
-     *
-     *     public byte[] getReplyChargingId() {return 0x00;}
-     *     public void setReplyChargingId(byte[] value) {}
-     *
-     *     public long getReplyChargingSize() {return 0;}
-     *     public void setReplyChargingSize(long value) {}
-     *
-     *     public byte getStored() {return 0x00;}
-     *     public void setStored(byte value) {}
-     */
-}
diff --git a/src/java/com/google/android/mms/pdu/NotifyRespInd.java b/src/java/com/google/android/mms/pdu/NotifyRespInd.java
deleted file mode 100644
index 2cc2fce..0000000
--- a/src/java/com/google/android/mms/pdu/NotifyRespInd.java
+++ /dev/null
@@ -1,114 +0,0 @@
-/*
- * Copyright (C) 2007 Esmertec AG.
- * Copyright (C) 2007 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.google.android.mms.pdu;
-
-import com.google.android.mms.InvalidHeaderValueException;
-
-/**
- * M-NofifyResp.ind PDU.
- */
-public class NotifyRespInd extends GenericPdu {
-    /**
-     * Constructor, used when composing a M-NotifyResp.ind pdu.
-     *
-     * @param mmsVersion current version of mms
-     * @param transactionId the transaction-id value
-     * @param status the status value
-     * @throws InvalidHeaderValueException if parameters are invalid.
-     *         NullPointerException if transactionId is null.
-     *         RuntimeException if an undeclared error occurs.
-     */
-    public NotifyRespInd(int mmsVersion,
-                         byte[] transactionId,
-                         int status) throws InvalidHeaderValueException {
-        super();
-        setMessageType(PduHeaders.MESSAGE_TYPE_NOTIFYRESP_IND);
-        setMmsVersion(mmsVersion);
-        setTransactionId(transactionId);
-        setStatus(status);
-    }
-
-    /**
-     * Constructor with given headers.
-     *
-     * @param headers Headers for this PDU.
-     */
-    NotifyRespInd(PduHeaders headers) {
-        super(headers);
-    }
-
-    /**
-     * Get X-Mms-Report-Allowed field value.
-     *
-     * @return the X-Mms-Report-Allowed value
-     */
-    public int getReportAllowed() {
-        return mPduHeaders.getOctet(PduHeaders.REPORT_ALLOWED);
-    }
-
-    /**
-     * Set X-Mms-Report-Allowed field value.
-     *
-     * @param value the value
-     * @throws InvalidHeaderValueException if the value is invalid.
-     *         RuntimeException if an undeclared error occurs.
-     */
-    public void setReportAllowed(int value) throws InvalidHeaderValueException {
-        mPduHeaders.setOctet(value, PduHeaders.REPORT_ALLOWED);
-    }
-
-    /**
-     * Set X-Mms-Status field value.
-     *
-     * @param value the value
-     * @throws InvalidHeaderValueException if the value is invalid.
-     *         RuntimeException if an undeclared error occurs.
-     */
-    public void setStatus(int value) throws InvalidHeaderValueException {
-        mPduHeaders.setOctet(value, PduHeaders.STATUS);
-    }
-
-    /**
-     * GetX-Mms-Status field value.
-     *
-     * @return the X-Mms-Status value
-     */
-    public int getStatus() {
-        return mPduHeaders.getOctet(PduHeaders.STATUS);
-    }
-
-    /**
-     * Get X-Mms-Transaction-Id field value.
-     *
-     * @return the X-Mms-Report-Allowed value
-     */
-    public byte[] getTransactionId() {
-        return mPduHeaders.getTextString(PduHeaders.TRANSACTION_ID);
-    }
-
-    /**
-     * Set X-Mms-Transaction-Id field value.
-     *
-     * @param value the value
-     * @throws NullPointerException if the value is null.
-     *         RuntimeException if an undeclared error occurs.
-     */
-    public void setTransactionId(byte[] value) {
-            mPduHeaders.setTextString(value, PduHeaders.TRANSACTION_ID);
-    }
-}
diff --git a/src/java/com/google/android/mms/pdu/PduBody.java b/src/java/com/google/android/mms/pdu/PduBody.java
deleted file mode 100644
index fa0416c..0000000
--- a/src/java/com/google/android/mms/pdu/PduBody.java
+++ /dev/null
@@ -1,191 +0,0 @@
-/*
- * Copyright (C) 2007 Esmertec AG.
- * Copyright (C) 2007 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.google.android.mms.pdu;
-
-import java.util.HashMap;
-import java.util.Map;
-import java.util.Vector;
-
-public class PduBody {
-    private Vector<PduPart> mParts = null;
-
-    private Map<String, PduPart> mPartMapByContentId = null;
-    private Map<String, PduPart> mPartMapByContentLocation = null;
-    private Map<String, PduPart> mPartMapByName = null;
-    private Map<String, PduPart> mPartMapByFileName = null;
-
-    /**
-     * Constructor.
-     */
-    public PduBody() {
-        mParts = new Vector<PduPart>();
-
-        mPartMapByContentId = new HashMap<String, PduPart>();
-        mPartMapByContentLocation  = new HashMap<String, PduPart>();
-        mPartMapByName = new HashMap<String, PduPart>();
-        mPartMapByFileName = new HashMap<String, PduPart>();
-    }
-
-    private void putPartToMaps(PduPart part) {
-        // Put part to mPartMapByContentId.
-        byte[] contentId = part.getContentId();
-        if(null != contentId) {
-            mPartMapByContentId.put(new String(contentId), part);
-        }
-
-        // Put part to mPartMapByContentLocation.
-        byte[] contentLocation = part.getContentLocation();
-        if(null != contentLocation) {
-            String clc = new String(contentLocation);
-            mPartMapByContentLocation.put(clc, part);
-        }
-
-        // Put part to mPartMapByName.
-        byte[] name = part.getName();
-        if(null != name) {
-            String clc = new String(name);
-            mPartMapByName.put(clc, part);
-        }
-
-        // Put part to mPartMapByFileName.
-        byte[] fileName = part.getFilename();
-        if(null != fileName) {
-            String clc = new String(fileName);
-            mPartMapByFileName.put(clc, part);
-        }
-    }
-
-    /**
-     * Appends the specified part to the end of this body.
-     *
-     * @param part part to be appended
-     * @return true when success, false when fail
-     * @throws NullPointerException when part is null
-     */
-    public boolean addPart(PduPart part) {
-        if(null == part) {
-            throw new NullPointerException();
-        }
-
-        putPartToMaps(part);
-        return mParts.add(part);
-    }
-
-    /**
-     * Inserts the specified part at the specified position.
-     *
-     * @param index index at which the specified part is to be inserted
-     * @param part part to be inserted
-     * @throws NullPointerException when part is null
-     */
-    public void addPart(int index, PduPart part) {
-        if(null == part) {
-            throw new NullPointerException();
-        }
-
-        putPartToMaps(part);
-        mParts.add(index, part);
-    }
-
-    /**
-     * Removes the part at the specified position.
-     *
-     * @param index index of the part to return
-     * @return part at the specified index
-     */
-    public PduPart removePart(int index) {
-        return mParts.remove(index);
-    }
-
-    /**
-     * Remove all of the parts.
-     */
-    public void removeAll() {
-        mParts.clear();
-    }
-
-    /**
-     * Get the part at the specified position.
-     *
-     * @param index index of the part to return
-     * @return part at the specified index
-     */
-    public PduPart getPart(int index) {
-        return mParts.get(index);
-    }
-
-    /**
-     * Get the index of the specified part.
-     *
-     * @param part the part object
-     * @return index the index of the first occurrence of the part in this body
-     */
-    public int getPartIndex(PduPart part) {
-        return mParts.indexOf(part);
-    }
-
-    /**
-     * Get the number of parts.
-     *
-     * @return the number of parts
-     */
-    public int getPartsNum() {
-        return mParts.size();
-    }
-
-    /**
-     * Get pdu part by content id.
-     *
-     * @param cid the value of content id.
-     * @return the pdu part.
-     */
-    public PduPart getPartByContentId(String cid) {
-        return mPartMapByContentId.get(cid);
-    }
-
-    /**
-     * Get pdu part by Content-Location. Content-Location of part is
-     * the same as filename and name(param of content-type).
-     *
-     * @param fileName the value of filename.
-     * @return the pdu part.
-     */
-    public PduPart getPartByContentLocation(String contentLocation) {
-        return mPartMapByContentLocation.get(contentLocation);
-    }
-
-    /**
-     * Get pdu part by name.
-     *
-     * @param fileName the value of filename.
-     * @return the pdu part.
-     */
-    public PduPart getPartByName(String name) {
-        return mPartMapByName.get(name);
-    }
-
-    /**
-     * Get pdu part by filename.
-     *
-     * @param fileName the value of filename.
-     * @return the pdu part.
-     */
-    public PduPart getPartByFileName(String filename) {
-        return mPartMapByFileName.get(filename);
-    }
-}
diff --git a/src/java/com/google/android/mms/pdu/PduComposer.java b/src/java/com/google/android/mms/pdu/PduComposer.java
deleted file mode 100644
index 582caec..0000000
--- a/src/java/com/google/android/mms/pdu/PduComposer.java
+++ /dev/null
@@ -1,1202 +0,0 @@
-/*
- * Copyright (C) 2007-2008 Esmertec AG.
- * Copyright (C) 2007-2008 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.google.android.mms.pdu;
-
-import android.content.ContentResolver;
-import android.content.Context;
-import android.util.Log;
-import android.text.TextUtils;
-
-import java.io.ByteArrayOutputStream;
-import java.io.FileNotFoundException;
-import java.io.IOException;
-import java.io.InputStream;
-import java.util.Arrays;
-import java.util.HashMap;
-
-public class PduComposer {
-    /**
-     * Address type.
-     */
-    static private final int PDU_PHONE_NUMBER_ADDRESS_TYPE = 1;
-    static private final int PDU_EMAIL_ADDRESS_TYPE = 2;
-    static private final int PDU_IPV4_ADDRESS_TYPE = 3;
-    static private final int PDU_IPV6_ADDRESS_TYPE = 4;
-    static private final int PDU_UNKNOWN_ADDRESS_TYPE = 5;
-
-    /**
-     * Address regular expression string.
-     */
-    static final String REGEXP_PHONE_NUMBER_ADDRESS_TYPE = "\\+?[0-9|\\.|\\-]+";
-    static final String REGEXP_EMAIL_ADDRESS_TYPE = "[a-zA-Z| ]*\\<{0,1}[a-zA-Z| ]+@{1}" +
-            "[a-zA-Z| ]+\\.{1}[a-zA-Z| ]+\\>{0,1}";
-    static final String REGEXP_IPV6_ADDRESS_TYPE =
-        "[a-fA-F]{4}\\:{1}[a-fA-F0-9]{4}\\:{1}[a-fA-F0-9]{4}\\:{1}" +
-        "[a-fA-F0-9]{4}\\:{1}[a-fA-F0-9]{4}\\:{1}[a-fA-F0-9]{4}\\:{1}" +
-        "[a-fA-F0-9]{4}\\:{1}[a-fA-F0-9]{4}";
-    static final String REGEXP_IPV4_ADDRESS_TYPE = "[0-9]{1,3}\\.{1}[0-9]{1,3}\\.{1}" +
-            "[0-9]{1,3}\\.{1}[0-9]{1,3}";
-
-    /**
-     * The postfix strings of address.
-     */
-    static final String STRING_PHONE_NUMBER_ADDRESS_TYPE = "/TYPE=PLMN";
-    static final String STRING_IPV4_ADDRESS_TYPE = "/TYPE=IPV4";
-    static final String STRING_IPV6_ADDRESS_TYPE = "/TYPE=IPV6";
-
-    /**
-     * Error values.
-     */
-    static private final int PDU_COMPOSE_SUCCESS = 0;
-    static private final int PDU_COMPOSE_CONTENT_ERROR = 1;
-    static private final int PDU_COMPOSE_FIELD_NOT_SET = 2;
-    static private final int PDU_COMPOSE_FIELD_NOT_SUPPORTED = 3;
-
-    /**
-     * WAP values defined in WSP spec.
-     */
-    static private final int QUOTED_STRING_FLAG = 34;
-    static private final int END_STRING_FLAG = 0;
-    static private final int LENGTH_QUOTE = 31;
-    static private final int TEXT_MAX = 127;
-    static private final int SHORT_INTEGER_MAX = 127;
-    static private final int LONG_INTEGER_LENGTH_MAX = 8;
-
-    /**
-     * Block size when read data from InputStream.
-     */
-    static private final int PDU_COMPOSER_BLOCK_SIZE = 1024;
-
-    /**
-     * The output message.
-     */
-    protected ByteArrayOutputStream mMessage = null;
-
-    /**
-     * The PDU.
-     */
-    private GenericPdu mPdu = null;
-
-    /**
-     * Current visiting position of the mMessage.
-     */
-    protected int mPosition = 0;
-
-    /**
-     * Message compose buffer stack.
-     */
-    private BufferStack mStack = null;
-
-    /**
-     * Content resolver.
-     */
-    private final ContentResolver mResolver;
-
-    /**
-     * Header of this pdu.
-     */
-    private PduHeaders mPduHeader = null;
-
-    /**
-     * Map of all content type
-     */
-    private static HashMap<String, Integer> mContentTypeMap = null;
-
-    static {
-        mContentTypeMap = new HashMap<String, Integer>();
-
-        int i;
-        for (i = 0; i < PduContentTypes.contentTypes.length; i++) {
-            mContentTypeMap.put(PduContentTypes.contentTypes[i], i);
-        }
-    }
-
-    /**
-     * Constructor.
-     *
-     * @param context the context
-     * @param pdu the pdu to be composed
-     */
-    public PduComposer(Context context, GenericPdu pdu) {
-        mPdu = pdu;
-        mResolver = context.getContentResolver();
-        mPduHeader = pdu.getPduHeaders();
-        mStack = new BufferStack();
-        mMessage = new ByteArrayOutputStream();
-        mPosition = 0;
-    }
-
-    /**
-     * Make the message. No need to check whether mandatory fields are set,
-     * because the constructors of outgoing pdus are taking care of this.
-     *
-     * @return OutputStream of maked message. Return null if
-     *         the PDU is invalid.
-     */
-    public byte[] make() {
-        // Get Message-type.
-        int type = mPdu.getMessageType();
-
-        /* make the message */
-        switch (type) {
-            case PduHeaders.MESSAGE_TYPE_SEND_REQ:
-            case PduHeaders.MESSAGE_TYPE_RETRIEVE_CONF:
-                if (makeSendRetrievePdu(type) != PDU_COMPOSE_SUCCESS) {
-                    return null;
-                }
-                break;
-            case PduHeaders.MESSAGE_TYPE_NOTIFYRESP_IND:
-                if (makeNotifyResp() != PDU_COMPOSE_SUCCESS) {
-                    return null;
-                }
-                break;
-            case PduHeaders.MESSAGE_TYPE_ACKNOWLEDGE_IND:
-                if (makeAckInd() != PDU_COMPOSE_SUCCESS) {
-                    return null;
-                }
-                break;
-            case PduHeaders.MESSAGE_TYPE_READ_REC_IND:
-                if (makeReadRecInd() != PDU_COMPOSE_SUCCESS) {
-                    return null;
-                }
-                break;
-            default:
-                return null;
-        }
-
-        return mMessage.toByteArray();
-    }
-
-    /**
-     *  Copy buf to mMessage.
-     */
-    protected void arraycopy(byte[] buf, int pos, int length) {
-        mMessage.write(buf, pos, length);
-        mPosition = mPosition + length;
-    }
-
-    /**
-     * Append a byte to mMessage.
-     */
-    protected void append(int value) {
-        mMessage.write(value);
-        mPosition ++;
-    }
-
-    /**
-     * Append short integer value to mMessage.
-     * This implementation doesn't check the validity of parameter, since it
-     * assumes that the values are validated in the GenericPdu setter methods.
-     */
-    protected void appendShortInteger(int value) {
-        /*
-         * From WAP-230-WSP-20010705-a:
-         * Short-integer = OCTET
-         * ; Integers in range 0-127 shall be encoded as a one octet value
-         * ; with the most significant bit set to one (1xxx xxxx) and with
-         * ; the value in the remaining least significant bits.
-         * In our implementation, only low 7 bits are stored and otherwise
-         * bits are ignored.
-         */
-        append((value | 0x80) & 0xff);
-    }
-
-    /**
-     * Append an octet number between 128 and 255 into mMessage.
-     * NOTE:
-     * A value between 0 and 127 should be appended by using appendShortInteger.
-     * This implementation doesn't check the validity of parameter, since it
-     * assumes that the values are validated in the GenericPdu setter methods.
-     */
-    protected void appendOctet(int number) {
-        append(number);
-    }
-
-    /**
-     * Append a short length into mMessage.
-     * This implementation doesn't check the validity of parameter, since it
-     * assumes that the values are validated in the GenericPdu setter methods.
-     */
-    protected void appendShortLength(int value) {
-        /*
-         * From WAP-230-WSP-20010705-a:
-         * Short-length = <Any octet 0-30>
-         */
-        append(value);
-    }
-
-    /**
-     * Append long integer into mMessage. it's used for really long integers.
-     * This implementation doesn't check the validity of parameter, since it
-     * assumes that the values are validated in the GenericPdu setter methods.
-     */
-    protected void appendLongInteger(long longInt) {
-        /*
-         * From WAP-230-WSP-20010705-a:
-         * Long-integer = Short-length Multi-octet-integer
-         * ; The Short-length indicates the length of the Multi-octet-integer
-         * Multi-octet-integer = 1*30 OCTET
-         * ; The content octets shall be an unsigned integer value with the
-         * ; most significant octet encoded first (big-endian representation).
-         * ; The minimum number of octets must be used to encode the value.
-         */
-        int size;
-        long temp = longInt;
-
-        // Count the length of the long integer.
-        for(size = 0; (temp != 0) && (size < LONG_INTEGER_LENGTH_MAX); size++) {
-            temp = (temp >>> 8);
-        }
-
-        // Set Length.
-        appendShortLength(size);
-
-        // Count and set the long integer.
-        int i;
-        int shift = (size -1) * 8;
-
-        for (i = 0; i < size; i++) {
-            append((int)((longInt >>> shift) & 0xff));
-            shift = shift - 8;
-        }
-    }
-
-    /**
-     * Append text string into mMessage.
-     * This implementation doesn't check the validity of parameter, since it
-     * assumes that the values are validated in the GenericPdu setter methods.
-     */
-    protected void appendTextString(byte[] text) {
-        /*
-         * From WAP-230-WSP-20010705-a:
-         * Text-string = [Quote] *TEXT End-of-string
-         * ; If the first character in the TEXT is in the range of 128-255,
-         * ; a Quote character must precede it. Otherwise the Quote character
-         * ;must be omitted. The Quote is not part of the contents.
-         */
-        if (((text[0])&0xff) > TEXT_MAX) { // No need to check for <= 255
-            append(TEXT_MAX);
-        }
-
-        arraycopy(text, 0, text.length);
-        append(0);
-    }
-
-    /**
-     * Append text string into mMessage.
-     * This implementation doesn't check the validity of parameter, since it
-     * assumes that the values are validated in the GenericPdu setter methods.
-     */
-    protected void appendTextString(String str) {
-        /*
-         * From WAP-230-WSP-20010705-a:
-         * Text-string = [Quote] *TEXT End-of-string
-         * ; If the first character in the TEXT is in the range of 128-255,
-         * ; a Quote character must precede it. Otherwise the Quote character
-         * ;must be omitted. The Quote is not part of the contents.
-         */
-        appendTextString(str.getBytes());
-    }
-
-    /**
-     * Append encoded string value to mMessage.
-     * This implementation doesn't check the validity of parameter, since it
-     * assumes that the values are validated in the GenericPdu setter methods.
-     */
-    protected void appendEncodedString(EncodedStringValue enStr) {
-        /*
-         * From OMA-TS-MMS-ENC-V1_3-20050927-C:
-         * Encoded-string-value = Text-string | Value-length Char-set Text-string
-         */
-        assert(enStr != null);
-
-        int charset = enStr.getCharacterSet();
-        byte[] textString = enStr.getTextString();
-        if (null == textString) {
-            return;
-        }
-
-        /*
-         * In the implementation of EncodedStringValue, the charset field will
-         * never be 0. It will always be composed as
-         * Encoded-string-value = Value-length Char-set Text-string
-         */
-        mStack.newbuf();
-        PositionMarker start = mStack.mark();
-
-        appendShortInteger(charset);
-        appendTextString(textString);
-
-        int len = start.getLength();
-        mStack.pop();
-        appendValueLength(len);
-        mStack.copy();
-    }
-
-    /**
-     * Append uintvar integer into mMessage.
-     * This implementation doesn't check the validity of parameter, since it
-     * assumes that the values are validated in the GenericPdu setter methods.
-     */
-    protected void appendUintvarInteger(long value) {
-        /*
-         * From WAP-230-WSP-20010705-a:
-         * To encode a large unsigned integer, split it into 7-bit fragments
-         * and place them in the payloads of multiple octets. The most significant
-         * bits are placed in the first octets with the least significant bits
-         * ending up in the last octet. All octets MUST set the Continue bit to 1
-         * except the last octet, which MUST set the Continue bit to 0.
-         */
-        int i;
-        long max = SHORT_INTEGER_MAX;
-
-        for (i = 0; i < 5; i++) {
-            if (value < max) {
-                break;
-            }
-
-            max = (max << 7) | 0x7fl;
-        }
-
-        while(i > 0) {
-            long temp = value >>> (i * 7);
-            temp = temp & 0x7f;
-
-            append((int)((temp | 0x80) & 0xff));
-
-            i--;
-        }
-
-        append((int)(value & 0x7f));
-    }
-
-    /**
-     * Append date value into mMessage.
-     * This implementation doesn't check the validity of parameter, since it
-     * assumes that the values are validated in the GenericPdu setter methods.
-     */
-    protected void appendDateValue(long date) {
-        /*
-         * From OMA-TS-MMS-ENC-V1_3-20050927-C:
-         * Date-value = Long-integer
-         */
-        appendLongInteger(date);
-    }
-
-    /**
-     * Append value length to mMessage.
-     * This implementation doesn't check the validity of parameter, since it
-     * assumes that the values are validated in the GenericPdu setter methods.
-     */
-    protected void appendValueLength(long value) {
-        /*
-         * From WAP-230-WSP-20010705-a:
-         * Value-length = Short-length | (Length-quote Length)
-         * ; Value length is used to indicate the length of the value to follow
-         * Short-length = <Any octet 0-30>
-         * Length-quote = <Octet 31>
-         * Length = Uintvar-integer
-         */
-        if (value < LENGTH_QUOTE) {
-            appendShortLength((int) value);
-            return;
-        }
-
-        append(LENGTH_QUOTE);
-        appendUintvarInteger(value);
-    }
-
-    /**
-     * Append quoted string to mMessage.
-     * This implementation doesn't check the validity of parameter, since it
-     * assumes that the values are validated in the GenericPdu setter methods.
-     */
-    protected void appendQuotedString(byte[] text) {
-        /*
-         * From WAP-230-WSP-20010705-a:
-         * Quoted-string = <Octet 34> *TEXT End-of-string
-         * ;The TEXT encodes an RFC2616 Quoted-string with the enclosing
-         * ;quotation-marks <"> removed.
-         */
-        append(QUOTED_STRING_FLAG);
-        arraycopy(text, 0, text.length);
-        append(END_STRING_FLAG);
-    }
-
-    /**
-     * Append quoted string to mMessage.
-     * This implementation doesn't check the validity of parameter, since it
-     * assumes that the values are validated in the GenericPdu setter methods.
-     */
-    protected void appendQuotedString(String str) {
-        /*
-         * From WAP-230-WSP-20010705-a:
-         * Quoted-string = <Octet 34> *TEXT End-of-string
-         * ;The TEXT encodes an RFC2616 Quoted-string with the enclosing
-         * ;quotation-marks <"> removed.
-         */
-        appendQuotedString(str.getBytes());
-    }
-
-    private EncodedStringValue appendAddressType(EncodedStringValue address) {
-        EncodedStringValue temp = null;
-
-        try {
-            int addressType = checkAddressType(address.getString());
-            temp = EncodedStringValue.copy(address);
-            if (PDU_PHONE_NUMBER_ADDRESS_TYPE == addressType) {
-                // Phone number.
-                temp.appendTextString(STRING_PHONE_NUMBER_ADDRESS_TYPE.getBytes());
-            } else if (PDU_IPV4_ADDRESS_TYPE == addressType) {
-                // Ipv4 address.
-                temp.appendTextString(STRING_IPV4_ADDRESS_TYPE.getBytes());
-            } else if (PDU_IPV6_ADDRESS_TYPE == addressType) {
-                // Ipv6 address.
-                temp.appendTextString(STRING_IPV6_ADDRESS_TYPE.getBytes());
-            }
-        } catch (NullPointerException e) {
-            return null;
-        }
-
-        return temp;
-    }
-
-    /**
-     * Append header to mMessage.
-     */
-    private int appendHeader(int field) {
-        switch (field) {
-            case PduHeaders.MMS_VERSION:
-                appendOctet(field);
-
-                int version = mPduHeader.getOctet(field);
-                if (0 == version) {
-                    appendShortInteger(PduHeaders.CURRENT_MMS_VERSION);
-                } else {
-                    appendShortInteger(version);
-                }
-
-                break;
-
-            case PduHeaders.MESSAGE_ID:
-            case PduHeaders.TRANSACTION_ID:
-                byte[] textString = mPduHeader.getTextString(field);
-                if (null == textString) {
-                    return PDU_COMPOSE_FIELD_NOT_SET;
-                }
-
-                appendOctet(field);
-                appendTextString(textString);
-                break;
-
-            case PduHeaders.TO:
-            case PduHeaders.BCC:
-            case PduHeaders.CC:
-                EncodedStringValue[] addr = mPduHeader.getEncodedStringValues(field);
-
-                if (null == addr) {
-                    return PDU_COMPOSE_FIELD_NOT_SET;
-                }
-
-                EncodedStringValue temp;
-                for (int i = 0; i < addr.length; i++) {
-                    temp = appendAddressType(addr[i]);
-                    if (temp == null) {
-                        return PDU_COMPOSE_CONTENT_ERROR;
-                    }
-
-                    appendOctet(field);
-                    appendEncodedString(temp);
-                }
-                break;
-
-            case PduHeaders.FROM:
-                // Value-length (Address-present-token Encoded-string-value | Insert-address-token)
-                appendOctet(field);
-
-                EncodedStringValue from = mPduHeader.getEncodedStringValue(field);
-                if ((from == null)
-                        || TextUtils.isEmpty(from.getString())
-                        || new String(from.getTextString()).equals(
-                                PduHeaders.FROM_INSERT_ADDRESS_TOKEN_STR)) {
-                    // Length of from = 1
-                    append(1);
-                    // Insert-address-token = <Octet 129>
-                    append(PduHeaders.FROM_INSERT_ADDRESS_TOKEN);
-                } else {
-                    mStack.newbuf();
-                    PositionMarker fstart = mStack.mark();
-
-                    // Address-present-token = <Octet 128>
-                    append(PduHeaders.FROM_ADDRESS_PRESENT_TOKEN);
-
-                    temp = appendAddressType(from);
-                    if (temp == null) {
-                        return PDU_COMPOSE_CONTENT_ERROR;
-                    }
-
-                    appendEncodedString(temp);
-
-                    int flen = fstart.getLength();
-                    mStack.pop();
-                    appendValueLength(flen);
-                    mStack.copy();
-                }
-                break;
-
-            case PduHeaders.READ_STATUS:
-            case PduHeaders.STATUS:
-            case PduHeaders.REPORT_ALLOWED:
-            case PduHeaders.PRIORITY:
-            case PduHeaders.DELIVERY_REPORT:
-            case PduHeaders.READ_REPORT:
-            case PduHeaders.RETRIEVE_STATUS:
-                int octet = mPduHeader.getOctet(field);
-                if (0 == octet) {
-                    return PDU_COMPOSE_FIELD_NOT_SET;
-                }
-
-                appendOctet(field);
-                appendOctet(octet);
-                break;
-
-            case PduHeaders.DATE:
-                long date = mPduHeader.getLongInteger(field);
-                if (-1 == date) {
-                    return PDU_COMPOSE_FIELD_NOT_SET;
-                }
-
-                appendOctet(field);
-                appendDateValue(date);
-                break;
-
-            case PduHeaders.SUBJECT:
-            case PduHeaders.RETRIEVE_TEXT:
-                EncodedStringValue enString =
-                    mPduHeader.getEncodedStringValue(field);
-                if (null == enString) {
-                    return PDU_COMPOSE_FIELD_NOT_SET;
-                }
-
-                appendOctet(field);
-                appendEncodedString(enString);
-                break;
-
-            case PduHeaders.MESSAGE_CLASS:
-                byte[] messageClass = mPduHeader.getTextString(field);
-                if (null == messageClass) {
-                    return PDU_COMPOSE_FIELD_NOT_SET;
-                }
-
-                appendOctet(field);
-                if (Arrays.equals(messageClass,
-                        PduHeaders.MESSAGE_CLASS_ADVERTISEMENT_STR.getBytes())) {
-                    appendOctet(PduHeaders.MESSAGE_CLASS_ADVERTISEMENT);
-                } else if (Arrays.equals(messageClass,
-                        PduHeaders.MESSAGE_CLASS_AUTO_STR.getBytes())) {
-                    appendOctet(PduHeaders.MESSAGE_CLASS_AUTO);
-                } else if (Arrays.equals(messageClass,
-                        PduHeaders.MESSAGE_CLASS_PERSONAL_STR.getBytes())) {
-                    appendOctet(PduHeaders.MESSAGE_CLASS_PERSONAL);
-                } else if (Arrays.equals(messageClass,
-                        PduHeaders.MESSAGE_CLASS_INFORMATIONAL_STR.getBytes())) {
-                    appendOctet(PduHeaders.MESSAGE_CLASS_INFORMATIONAL);
-                } else {
-                    appendTextString(messageClass);
-                }
-                break;
-
-            case PduHeaders.EXPIRY:
-                long expiry = mPduHeader.getLongInteger(field);
-                if (-1 == expiry) {
-                    return PDU_COMPOSE_FIELD_NOT_SET;
-                }
-
-                appendOctet(field);
-
-                mStack.newbuf();
-                PositionMarker expiryStart = mStack.mark();
-
-                append(PduHeaders.VALUE_RELATIVE_TOKEN);
-                appendLongInteger(expiry);
-
-                int expiryLength = expiryStart.getLength();
-                mStack.pop();
-                appendValueLength(expiryLength);
-                mStack.copy();
-                break;
-
-            default:
-                return PDU_COMPOSE_FIELD_NOT_SUPPORTED;
-        }
-
-        return PDU_COMPOSE_SUCCESS;
-    }
-
-    /**
-     * Make ReadRec.Ind.
-     */
-    private int makeReadRecInd() {
-        if (mMessage == null) {
-            mMessage = new ByteArrayOutputStream();
-            mPosition = 0;
-        }
-
-        // X-Mms-Message-Type
-        appendOctet(PduHeaders.MESSAGE_TYPE);
-        appendOctet(PduHeaders.MESSAGE_TYPE_READ_REC_IND);
-
-        // X-Mms-MMS-Version
-        if (appendHeader(PduHeaders.MMS_VERSION) != PDU_COMPOSE_SUCCESS) {
-            return PDU_COMPOSE_CONTENT_ERROR;
-        }
-
-        // Message-ID
-        if (appendHeader(PduHeaders.MESSAGE_ID) != PDU_COMPOSE_SUCCESS) {
-            return PDU_COMPOSE_CONTENT_ERROR;
-        }
-
-        // To
-        if (appendHeader(PduHeaders.TO) != PDU_COMPOSE_SUCCESS) {
-            return PDU_COMPOSE_CONTENT_ERROR;
-        }
-
-        // From
-        if (appendHeader(PduHeaders.FROM) != PDU_COMPOSE_SUCCESS) {
-            return PDU_COMPOSE_CONTENT_ERROR;
-        }
-
-        // Date Optional
-        appendHeader(PduHeaders.DATE);
-
-        // X-Mms-Read-Status
-        if (appendHeader(PduHeaders.READ_STATUS) != PDU_COMPOSE_SUCCESS) {
-            return PDU_COMPOSE_CONTENT_ERROR;
-        }
-
-        // X-Mms-Applic-ID Optional(not support)
-        // X-Mms-Reply-Applic-ID Optional(not support)
-        // X-Mms-Aux-Applic-Info Optional(not support)
-
-        return PDU_COMPOSE_SUCCESS;
-    }
-
-    /**
-     * Make NotifyResp.Ind.
-     */
-    private int makeNotifyResp() {
-        if (mMessage == null) {
-            mMessage = new ByteArrayOutputStream();
-            mPosition = 0;
-        }
-
-        //    X-Mms-Message-Type
-        appendOctet(PduHeaders.MESSAGE_TYPE);
-        appendOctet(PduHeaders.MESSAGE_TYPE_NOTIFYRESP_IND);
-
-        // X-Mms-Transaction-ID
-        if (appendHeader(PduHeaders.TRANSACTION_ID) != PDU_COMPOSE_SUCCESS) {
-            return PDU_COMPOSE_CONTENT_ERROR;
-        }
-
-        // X-Mms-MMS-Version
-        if (appendHeader(PduHeaders.MMS_VERSION) != PDU_COMPOSE_SUCCESS) {
-            return PDU_COMPOSE_CONTENT_ERROR;
-        }
-
-        //  X-Mms-Status
-        if (appendHeader(PduHeaders.STATUS) != PDU_COMPOSE_SUCCESS) {
-            return PDU_COMPOSE_CONTENT_ERROR;
-        }
-
-        // X-Mms-Report-Allowed Optional (not support)
-        return PDU_COMPOSE_SUCCESS;
-    }
-
-    /**
-     * Make Acknowledge.Ind.
-     */
-    private int makeAckInd() {
-        if (mMessage == null) {
-            mMessage = new ByteArrayOutputStream();
-            mPosition = 0;
-        }
-
-        //    X-Mms-Message-Type
-        appendOctet(PduHeaders.MESSAGE_TYPE);
-        appendOctet(PduHeaders.MESSAGE_TYPE_ACKNOWLEDGE_IND);
-
-        // X-Mms-Transaction-ID
-        if (appendHeader(PduHeaders.TRANSACTION_ID) != PDU_COMPOSE_SUCCESS) {
-            return PDU_COMPOSE_CONTENT_ERROR;
-        }
-
-        //     X-Mms-MMS-Version
-        if (appendHeader(PduHeaders.MMS_VERSION) != PDU_COMPOSE_SUCCESS) {
-            return PDU_COMPOSE_CONTENT_ERROR;
-        }
-
-        // X-Mms-Report-Allowed Optional
-        appendHeader(PduHeaders.REPORT_ALLOWED);
-
-        return PDU_COMPOSE_SUCCESS;
-    }
-
-    /**
-     * Make Send.req.
-     */
-    private int makeSendRetrievePdu(int type) {
-        if (mMessage == null) {
-            mMessage = new ByteArrayOutputStream();
-            mPosition = 0;
-        }
-
-        // X-Mms-Message-Type
-        appendOctet(PduHeaders.MESSAGE_TYPE);
-        appendOctet(type);
-
-        // X-Mms-Transaction-ID
-        appendOctet(PduHeaders.TRANSACTION_ID);
-
-        byte[] trid = mPduHeader.getTextString(PduHeaders.TRANSACTION_ID);
-        if (trid == null) {
-            // Transaction-ID should be set(by Transaction) before make().
-            throw new IllegalArgumentException("Transaction-ID is null.");
-        }
-        appendTextString(trid);
-
-        //  X-Mms-MMS-Version
-        if (appendHeader(PduHeaders.MMS_VERSION) != PDU_COMPOSE_SUCCESS) {
-            return PDU_COMPOSE_CONTENT_ERROR;
-        }
-
-        // Date Date-value Optional.
-        appendHeader(PduHeaders.DATE);
-
-        // From
-        if (appendHeader(PduHeaders.FROM) != PDU_COMPOSE_SUCCESS) {
-            return PDU_COMPOSE_CONTENT_ERROR;
-        }
-
-        boolean recipient = false;
-
-        // To
-        if (appendHeader(PduHeaders.TO) != PDU_COMPOSE_CONTENT_ERROR) {
-            recipient = true;
-        }
-
-        // Cc
-        if (appendHeader(PduHeaders.CC) != PDU_COMPOSE_CONTENT_ERROR) {
-            recipient = true;
-        }
-
-        // Bcc
-        if (appendHeader(PduHeaders.BCC) != PDU_COMPOSE_CONTENT_ERROR) {
-            recipient = true;
-        }
-
-        // Need at least one of "cc", "bcc" and "to".
-        if (false == recipient) {
-            return PDU_COMPOSE_CONTENT_ERROR;
-        }
-
-        // Subject Optional
-        appendHeader(PduHeaders.SUBJECT);
-
-        // X-Mms-Message-Class Optional
-        // Message-class-value = Class-identifier | Token-text
-        appendHeader(PduHeaders.MESSAGE_CLASS);
-
-        // X-Mms-Expiry Optional
-        appendHeader(PduHeaders.EXPIRY);
-
-        // X-Mms-Priority Optional
-        appendHeader(PduHeaders.PRIORITY);
-
-        // X-Mms-Delivery-Report Optional
-        appendHeader(PduHeaders.DELIVERY_REPORT);
-
-        // X-Mms-Read-Report Optional
-        appendHeader(PduHeaders.READ_REPORT);
-
-        if (type == PduHeaders.MESSAGE_TYPE_RETRIEVE_CONF) {
-            // X-Mms-Retrieve-Status Optional
-            appendHeader(PduHeaders.RETRIEVE_STATUS);
-            // X-Mms-Retrieve-Text Optional
-            appendHeader(PduHeaders.RETRIEVE_TEXT);
-        }
-
-
-        //    Content-Type
-        appendOctet(PduHeaders.CONTENT_TYPE);
-
-        //  Message body
-        return makeMessageBody(type);
-    }
-
-    /**
-     * Make message body.
-     */
-    private int makeMessageBody(int type) {
-        // 1. add body informations
-        mStack.newbuf();  // Switching buffer because we need to
-
-        PositionMarker ctStart = mStack.mark();
-
-        // This contentTypeIdentifier should be used for type of attachment...
-        String contentType = new String(mPduHeader.getTextString(PduHeaders.CONTENT_TYPE));
-        Integer contentTypeIdentifier = mContentTypeMap.get(contentType);
-        if (contentTypeIdentifier == null) {
-            // content type is mandatory
-            return PDU_COMPOSE_CONTENT_ERROR;
-        }
-
-        appendShortInteger(contentTypeIdentifier.intValue());
-
-        // content-type parameter: start
-        PduBody body;
-        if (type == PduHeaders.MESSAGE_TYPE_RETRIEVE_CONF) {
-            body = ((RetrieveConf) mPdu).getBody();
-        } else {
-            body = ((SendReq) mPdu).getBody();
-        }
-        if (null == body || body.getPartsNum() == 0) {
-            // empty message
-            appendUintvarInteger(0);
-            mStack.pop();
-            mStack.copy();
-            return PDU_COMPOSE_SUCCESS;
-        }
-
-        PduPart part;
-        try {
-            part = body.getPart(0);
-
-            byte[] start = part.getContentId();
-            if (start != null) {
-                appendOctet(PduPart.P_DEP_START);
-                if (('<' == start[0]) && ('>' == start[start.length - 1])) {
-                    appendTextString(start);
-                } else {
-                    appendTextString("<" + new String(start) + ">");
-                }
-            }
-
-            // content-type parameter: type
-            appendOctet(PduPart.P_CT_MR_TYPE);
-            appendTextString(part.getContentType());
-        }
-        catch (ArrayIndexOutOfBoundsException e){
-            e.printStackTrace();
-        }
-
-        int ctLength = ctStart.getLength();
-        mStack.pop();
-        appendValueLength(ctLength);
-        mStack.copy();
-
-        // 3. add content
-        int partNum = body.getPartsNum();
-        appendUintvarInteger(partNum);
-        for (int i = 0; i < partNum; i++) {
-            part = body.getPart(i);
-            mStack.newbuf();  // Leaving space for header lengh and data length
-            PositionMarker attachment = mStack.mark();
-
-            mStack.newbuf();  // Leaving space for Content-Type length
-            PositionMarker contentTypeBegin = mStack.mark();
-
-            byte[] partContentType = part.getContentType();
-
-            if (partContentType == null) {
-                // content type is mandatory
-                return PDU_COMPOSE_CONTENT_ERROR;
-            }
-
-            // content-type value
-            Integer partContentTypeIdentifier =
-                mContentTypeMap.get(new String(partContentType));
-            if (partContentTypeIdentifier == null) {
-                appendTextString(partContentType);
-            } else {
-                appendShortInteger(partContentTypeIdentifier.intValue());
-            }
-
-            /* Content-type parameter : name.
-             * The value of name, filename, content-location is the same.
-             * Just one of them is enough for this PDU.
-             */
-            byte[] name = part.getName();
-
-            if (null == name) {
-                name = part.getFilename();
-
-                if (null == name) {
-                    name = part.getContentLocation();
-
-                    if (null == name) {
-                        /* at lease one of name, filename, Content-location
-                         * should be available.
-                         */
-                        return PDU_COMPOSE_CONTENT_ERROR;
-                    }
-                }
-            }
-            appendOctet(PduPart.P_DEP_NAME);
-            appendTextString(name);
-
-            // content-type parameter : charset
-            int charset = part.getCharset();
-            if (charset != 0) {
-                appendOctet(PduPart.P_CHARSET);
-                appendShortInteger(charset);
-            }
-
-            int contentTypeLength = contentTypeBegin.getLength();
-            mStack.pop();
-            appendValueLength(contentTypeLength);
-            mStack.copy();
-
-            // content id
-            byte[] contentId = part.getContentId();
-
-            if (null != contentId) {
-                appendOctet(PduPart.P_CONTENT_ID);
-                if (('<' == contentId[0]) && ('>' == contentId[contentId.length - 1])) {
-                    appendQuotedString(contentId);
-                } else {
-                    appendQuotedString("<" + new String(contentId) + ">");
-                }
-            }
-
-            // content-location
-            byte[] contentLocation = part.getContentLocation();
-            if (null != contentLocation) {
-            	appendOctet(PduPart.P_CONTENT_LOCATION);
-            	appendTextString(contentLocation);
-            }
-
-            // content
-            int headerLength = attachment.getLength();
-
-            int dataLength = 0; // Just for safety...
-            byte[] partData = part.getData();
-
-            if (partData != null) {
-                arraycopy(partData, 0, partData.length);
-                dataLength = partData.length;
-            } else {
-                InputStream cr = null;
-                try {
-                    byte[] buffer = new byte[PDU_COMPOSER_BLOCK_SIZE];
-                    cr = mResolver.openInputStream(part.getDataUri());
-                    int len = 0;
-                    while ((len = cr.read(buffer)) != -1) {
-                        mMessage.write(buffer, 0, len);
-                        mPosition += len;
-                        dataLength += len;
-                    }
-                } catch (FileNotFoundException e) {
-                    return PDU_COMPOSE_CONTENT_ERROR;
-                } catch (IOException e) {
-                    return PDU_COMPOSE_CONTENT_ERROR;
-                } catch (RuntimeException e) {
-                    return PDU_COMPOSE_CONTENT_ERROR;
-                } finally {
-                    if (cr != null) {
-                        try {
-                            cr.close();
-                        } catch (IOException e) {
-                        }
-                    }
-                }
-            }
-
-            if (dataLength != (attachment.getLength() - headerLength)) {
-                throw new RuntimeException("BUG: Length sanity check failed");
-            }
-
-            mStack.pop();
-            appendUintvarInteger(headerLength);
-            appendUintvarInteger(dataLength);
-            mStack.copy();
-        }
-
-        return PDU_COMPOSE_SUCCESS;
-    }
-
-    /**
-     *  Record current message informations.
-     */
-    static private class LengthRecordNode {
-        ByteArrayOutputStream currentMessage = null;
-        public int currentPosition = 0;
-
-        public LengthRecordNode next = null;
-    }
-
-    /**
-     * Mark current message position and stact size.
-     */
-    private class PositionMarker {
-        private int c_pos;   // Current position
-        private int currentStackSize;  // Current stack size
-
-        int getLength() {
-            // If these assert fails, likely that you are finding the
-            // size of buffer that is deep in BufferStack you can only
-            // find the length of the buffer that is on top
-            if (currentStackSize != mStack.stackSize) {
-                throw new RuntimeException("BUG: Invalid call to getLength()");
-            }
-
-            return mPosition - c_pos;
-        }
-    }
-
-    /**
-     * This implementation can be OPTIMIZED to use only
-     * 2 buffers. This optimization involves changing BufferStack
-     * only... Its usage (interface) will not change.
-     */
-    private class BufferStack {
-        private LengthRecordNode stack = null;
-        private LengthRecordNode toCopy = null;
-
-        int stackSize = 0;
-
-        /**
-         *  Create a new message buffer and push it into the stack.
-         */
-        void newbuf() {
-            // You can't create a new buff when toCopy != null
-            // That is after calling pop() and before calling copy()
-            // If you do, it is a bug
-            if (toCopy != null) {
-                throw new RuntimeException("BUG: Invalid newbuf() before copy()");
-            }
-
-            LengthRecordNode temp = new LengthRecordNode();
-
-            temp.currentMessage = mMessage;
-            temp.currentPosition = mPosition;
-
-            temp.next = stack;
-            stack = temp;
-
-            stackSize = stackSize + 1;
-
-            mMessage = new ByteArrayOutputStream();
-            mPosition = 0;
-        }
-
-        /**
-         *  Pop the message before and record current message in the stack.
-         */
-        void pop() {
-            ByteArrayOutputStream currentMessage = mMessage;
-            int currentPosition = mPosition;
-
-            mMessage = stack.currentMessage;
-            mPosition = stack.currentPosition;
-
-            toCopy = stack;
-            // Re using the top element of the stack to avoid memory allocation
-
-            stack = stack.next;
-            stackSize = stackSize - 1;
-
-            toCopy.currentMessage = currentMessage;
-            toCopy.currentPosition = currentPosition;
-        }
-
-        /**
-         *  Append current message to the message before.
-         */
-        void copy() {
-            arraycopy(toCopy.currentMessage.toByteArray(), 0,
-                    toCopy.currentPosition);
-
-            toCopy = null;
-        }
-
-        /**
-         *  Mark current message position
-         */
-        PositionMarker mark() {
-            PositionMarker m = new PositionMarker();
-
-            m.c_pos = mPosition;
-            m.currentStackSize = stackSize;
-
-            return m;
-        }
-    }
-
-    /**
-     * Check address type.
-     *
-     * @param address address string without the postfix stinng type,
-     *        such as "/TYPE=PLMN", "/TYPE=IPv6" and "/TYPE=IPv4"
-     * @return PDU_PHONE_NUMBER_ADDRESS_TYPE if it is phone number,
-     *         PDU_EMAIL_ADDRESS_TYPE if it is email address,
-     *         PDU_IPV4_ADDRESS_TYPE if it is ipv4 address,
-     *         PDU_IPV6_ADDRESS_TYPE if it is ipv6 address,
-     *         PDU_UNKNOWN_ADDRESS_TYPE if it is unknown.
-     */
-    protected static int checkAddressType(String address) {
-        /**
-         * From OMA-TS-MMS-ENC-V1_3-20050927-C.pdf, section 8.
-         * address = ( e-mail / device-address / alphanum-shortcode / num-shortcode)
-         * e-mail = mailbox; to the definition of mailbox as described in
-         * section 3.4 of [RFC2822], but excluding the
-         * obsolete definitions as indicated by the "obs-" prefix.
-         * device-address = ( global-phone-number "/TYPE=PLMN" )
-         * / ( ipv4 "/TYPE=IPv4" ) / ( ipv6 "/TYPE=IPv6" )
-         * / ( escaped-value "/TYPE=" address-type )
-         *
-         * global-phone-number = ["+"] 1*( DIGIT / written-sep )
-         * written-sep =("-"/".")
-         *
-         * ipv4 = 1*3DIGIT 3( "." 1*3DIGIT ) ; IPv4 address value
-         *
-         * ipv6 = 4HEXDIG 7( ":" 4HEXDIG ) ; IPv6 address per RFC 2373
-         */
-
-        if (null == address) {
-            return PDU_UNKNOWN_ADDRESS_TYPE;
-        }
-
-        if (address.matches(REGEXP_IPV4_ADDRESS_TYPE)) {
-            // Ipv4 address.
-            return PDU_IPV4_ADDRESS_TYPE;
-        }else if (address.matches(REGEXP_PHONE_NUMBER_ADDRESS_TYPE)) {
-            // Phone number.
-            return PDU_PHONE_NUMBER_ADDRESS_TYPE;
-        } else if (address.matches(REGEXP_EMAIL_ADDRESS_TYPE)) {
-            // Email address.
-            return PDU_EMAIL_ADDRESS_TYPE;
-        } else if (address.matches(REGEXP_IPV6_ADDRESS_TYPE)) {
-            // Ipv6 address.
-            return PDU_IPV6_ADDRESS_TYPE;
-        } else {
-            // Unknown address.
-            return PDU_UNKNOWN_ADDRESS_TYPE;
-        }
-    }
-}
diff --git a/src/java/com/google/android/mms/pdu/PduContentTypes.java b/src/java/com/google/android/mms/pdu/PduContentTypes.java
deleted file mode 100644
index 7799e0e..0000000
--- a/src/java/com/google/android/mms/pdu/PduContentTypes.java
+++ /dev/null
@@ -1,110 +0,0 @@
-/*
- * Copyright (C) 2007 Esmertec AG.
- * Copyright (C) 2007 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.google.android.mms.pdu;
-
-public class PduContentTypes {
-    /**
-     * All content types. From:
-     * http://www.openmobilealliance.org/tech/omna/omna-wsp-content-type.htm
-     */
-    static final String[] contentTypes = {
-        "*/*",                                        /* 0x00 */
-        "text/*",                                     /* 0x01 */
-        "text/html",                                  /* 0x02 */
-        "text/plain",                                 /* 0x03 */
-        "text/x-hdml",                                /* 0x04 */
-        "text/x-ttml",                                /* 0x05 */
-        "text/x-vCalendar",                           /* 0x06 */
-        "text/x-vCard",                               /* 0x07 */
-        "text/vnd.wap.wml",                           /* 0x08 */
-        "text/vnd.wap.wmlscript",                     /* 0x09 */
-        "text/vnd.wap.wta-event",                     /* 0x0A */
-        "multipart/*",                                /* 0x0B */
-        "multipart/mixed",                            /* 0x0C */
-        "multipart/form-data",                        /* 0x0D */
-        "multipart/byterantes",                       /* 0x0E */
-        "multipart/alternative",                      /* 0x0F */
-        "application/*",                              /* 0x10 */
-        "application/java-vm",                        /* 0x11 */
-        "application/x-www-form-urlencoded",          /* 0x12 */
-        "application/x-hdmlc",                        /* 0x13 */
-        "application/vnd.wap.wmlc",                   /* 0x14 */
-        "application/vnd.wap.wmlscriptc",             /* 0x15 */
-        "application/vnd.wap.wta-eventc",             /* 0x16 */
-        "application/vnd.wap.uaprof",                 /* 0x17 */
-        "application/vnd.wap.wtls-ca-certificate",    /* 0x18 */
-        "application/vnd.wap.wtls-user-certificate",  /* 0x19 */
-        "application/x-x509-ca-cert",                 /* 0x1A */
-        "application/x-x509-user-cert",               /* 0x1B */
-        "image/*",                                    /* 0x1C */
-        "image/gif",                                  /* 0x1D */
-        "image/jpeg",                                 /* 0x1E */
-        "image/tiff",                                 /* 0x1F */
-        "image/png",                                  /* 0x20 */
-        "image/vnd.wap.wbmp",                         /* 0x21 */
-        "application/vnd.wap.multipart.*",            /* 0x22 */
-        "application/vnd.wap.multipart.mixed",        /* 0x23 */
-        "application/vnd.wap.multipart.form-data",    /* 0x24 */
-        "application/vnd.wap.multipart.byteranges",   /* 0x25 */
-        "application/vnd.wap.multipart.alternative",  /* 0x26 */
-        "application/xml",                            /* 0x27 */
-        "text/xml",                                   /* 0x28 */
-        "application/vnd.wap.wbxml",                  /* 0x29 */
-        "application/x-x968-cross-cert",              /* 0x2A */
-        "application/x-x968-ca-cert",                 /* 0x2B */
-        "application/x-x968-user-cert",               /* 0x2C */
-        "text/vnd.wap.si",                            /* 0x2D */
-        "application/vnd.wap.sic",                    /* 0x2E */
-        "text/vnd.wap.sl",                            /* 0x2F */
-        "application/vnd.wap.slc",                    /* 0x30 */
-        "text/vnd.wap.co",                            /* 0x31 */
-        "application/vnd.wap.coc",                    /* 0x32 */
-        "application/vnd.wap.multipart.related",      /* 0x33 */
-        "application/vnd.wap.sia",                    /* 0x34 */
-        "text/vnd.wap.connectivity-xml",              /* 0x35 */
-        "application/vnd.wap.connectivity-wbxml",     /* 0x36 */
-        "application/pkcs7-mime",                     /* 0x37 */
-        "application/vnd.wap.hashed-certificate",     /* 0x38 */
-        "application/vnd.wap.signed-certificate",     /* 0x39 */
-        "application/vnd.wap.cert-response",          /* 0x3A */
-        "application/xhtml+xml",                      /* 0x3B */
-        "application/wml+xml",                        /* 0x3C */
-        "text/css",                                   /* 0x3D */
-        "application/vnd.wap.mms-message",            /* 0x3E */
-        "application/vnd.wap.rollover-certificate",   /* 0x3F */
-        "application/vnd.wap.locc+wbxml",             /* 0x40 */
-        "application/vnd.wap.loc+xml",                /* 0x41 */
-        "application/vnd.syncml.dm+wbxml",            /* 0x42 */
-        "application/vnd.syncml.dm+xml",              /* 0x43 */
-        "application/vnd.syncml.notification",        /* 0x44 */
-        "application/vnd.wap.xhtml+xml",              /* 0x45 */
-        "application/vnd.wv.csp.cir",                 /* 0x46 */
-        "application/vnd.oma.dd+xml",                 /* 0x47 */
-        "application/vnd.oma.drm.message",            /* 0x48 */
-        "application/vnd.oma.drm.content",            /* 0x49 */
-        "application/vnd.oma.drm.rights+xml",         /* 0x4A */
-        "application/vnd.oma.drm.rights+wbxml",       /* 0x4B */
-        "application/vnd.wv.csp+xml",                 /* 0x4C */
-        "application/vnd.wv.csp+wbxml",               /* 0x4D */
-        "application/vnd.syncml.ds.notification",     /* 0x4E */
-        "audio/*",                                    /* 0x4F */
-        "video/*",                                    /* 0x50 */
-        "application/vnd.oma.dd2+xml",                /* 0x51 */
-        "application/mikey"                           /* 0x52 */
-    };
-}
diff --git a/src/java/com/google/android/mms/pdu/PduHeaders.java b/src/java/com/google/android/mms/pdu/PduHeaders.java
deleted file mode 100644
index 4313815..0000000
--- a/src/java/com/google/android/mms/pdu/PduHeaders.java
+++ /dev/null
@@ -1,721 +0,0 @@
-/*
- * Copyright (C) 2007 Esmertec AG.
- * Copyright (C) 2007 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.google.android.mms.pdu;
-
-import com.google.android.mms.InvalidHeaderValueException;
-
-import java.util.ArrayList;
-import java.util.HashMap;
-
-public class PduHeaders {
-    /**
-     * All pdu header fields.
-     */
-    public static final int BCC                             = 0x81;
-    public static final int CC                              = 0x82;
-    public static final int CONTENT_LOCATION                = 0x83;
-    public static final int CONTENT_TYPE                    = 0x84;
-    public static final int DATE                            = 0x85;
-    public static final int DELIVERY_REPORT                 = 0x86;
-    public static final int DELIVERY_TIME                   = 0x87;
-    public static final int EXPIRY                          = 0x88;
-    public static final int FROM                            = 0x89;
-    public static final int MESSAGE_CLASS                   = 0x8A;
-    public static final int MESSAGE_ID                      = 0x8B;
-    public static final int MESSAGE_TYPE                    = 0x8C;
-    public static final int MMS_VERSION                     = 0x8D;
-    public static final int MESSAGE_SIZE                    = 0x8E;
-    public static final int PRIORITY                        = 0x8F;
-
-    public static final int READ_REPLY                      = 0x90;
-    public static final int READ_REPORT                     = 0x90;
-    public static final int REPORT_ALLOWED                  = 0x91;
-    public static final int RESPONSE_STATUS                 = 0x92;
-    public static final int RESPONSE_TEXT                   = 0x93;
-    public static final int SENDER_VISIBILITY               = 0x94;
-    public static final int STATUS                          = 0x95;
-    public static final int SUBJECT                         = 0x96;
-    public static final int TO                              = 0x97;
-    public static final int TRANSACTION_ID                  = 0x98;
-    public static final int RETRIEVE_STATUS                 = 0x99;
-    public static final int RETRIEVE_TEXT                   = 0x9A;
-    public static final int READ_STATUS                     = 0x9B;
-    public static final int REPLY_CHARGING                  = 0x9C;
-    public static final int REPLY_CHARGING_DEADLINE         = 0x9D;
-    public static final int REPLY_CHARGING_ID               = 0x9E;
-    public static final int REPLY_CHARGING_SIZE             = 0x9F;
-
-    public static final int PREVIOUSLY_SENT_BY              = 0xA0;
-    public static final int PREVIOUSLY_SENT_DATE            = 0xA1;
-    public static final int STORE                           = 0xA2;
-    public static final int MM_STATE                        = 0xA3;
-    public static final int MM_FLAGS                        = 0xA4;
-    public static final int STORE_STATUS                    = 0xA5;
-    public static final int STORE_STATUS_TEXT               = 0xA6;
-    public static final int STORED                          = 0xA7;
-    public static final int ATTRIBUTES                      = 0xA8;
-    public static final int TOTALS                          = 0xA9;
-    public static final int MBOX_TOTALS                     = 0xAA;
-    public static final int QUOTAS                          = 0xAB;
-    public static final int MBOX_QUOTAS                     = 0xAC;
-    public static final int MESSAGE_COUNT                   = 0xAD;
-    public static final int CONTENT                         = 0xAE;
-    public static final int START                           = 0xAF;
-
-    public static final int ADDITIONAL_HEADERS              = 0xB0;
-    public static final int DISTRIBUTION_INDICATOR          = 0xB1;
-    public static final int ELEMENT_DESCRIPTOR              = 0xB2;
-    public static final int LIMIT                           = 0xB3;
-    public static final int RECOMMENDED_RETRIEVAL_MODE      = 0xB4;
-    public static final int RECOMMENDED_RETRIEVAL_MODE_TEXT = 0xB5;
-    public static final int STATUS_TEXT                     = 0xB6;
-    public static final int APPLIC_ID                       = 0xB7;
-    public static final int REPLY_APPLIC_ID                 = 0xB8;
-    public static final int AUX_APPLIC_ID                   = 0xB9;
-    public static final int CONTENT_CLASS                   = 0xBA;
-    public static final int DRM_CONTENT                     = 0xBB;
-    public static final int ADAPTATION_ALLOWED              = 0xBC;
-    public static final int REPLACE_ID                      = 0xBD;
-    public static final int CANCEL_ID                       = 0xBE;
-    public static final int CANCEL_STATUS                   = 0xBF;
-
-    /**
-     * X-Mms-Message-Type field types.
-     */
-    public static final int MESSAGE_TYPE_SEND_REQ           = 0x80;
-    public static final int MESSAGE_TYPE_SEND_CONF          = 0x81;
-    public static final int MESSAGE_TYPE_NOTIFICATION_IND   = 0x82;
-    public static final int MESSAGE_TYPE_NOTIFYRESP_IND     = 0x83;
-    public static final int MESSAGE_TYPE_RETRIEVE_CONF      = 0x84;
-    public static final int MESSAGE_TYPE_ACKNOWLEDGE_IND    = 0x85;
-    public static final int MESSAGE_TYPE_DELIVERY_IND       = 0x86;
-    public static final int MESSAGE_TYPE_READ_REC_IND       = 0x87;
-    public static final int MESSAGE_TYPE_READ_ORIG_IND      = 0x88;
-    public static final int MESSAGE_TYPE_FORWARD_REQ        = 0x89;
-    public static final int MESSAGE_TYPE_FORWARD_CONF       = 0x8A;
-    public static final int MESSAGE_TYPE_MBOX_STORE_REQ     = 0x8B;
-    public static final int MESSAGE_TYPE_MBOX_STORE_CONF    = 0x8C;
-    public static final int MESSAGE_TYPE_MBOX_VIEW_REQ      = 0x8D;
-    public static final int MESSAGE_TYPE_MBOX_VIEW_CONF     = 0x8E;
-    public static final int MESSAGE_TYPE_MBOX_UPLOAD_REQ    = 0x8F;
-    public static final int MESSAGE_TYPE_MBOX_UPLOAD_CONF   = 0x90;
-    public static final int MESSAGE_TYPE_MBOX_DELETE_REQ    = 0x91;
-    public static final int MESSAGE_TYPE_MBOX_DELETE_CONF   = 0x92;
-    public static final int MESSAGE_TYPE_MBOX_DESCR         = 0x93;
-    public static final int MESSAGE_TYPE_DELETE_REQ         = 0x94;
-    public static final int MESSAGE_TYPE_DELETE_CONF        = 0x95;
-    public static final int MESSAGE_TYPE_CANCEL_REQ         = 0x96;
-    public static final int MESSAGE_TYPE_CANCEL_CONF        = 0x97;
-
-    /**
-     *  X-Mms-Delivery-Report |
-     *  X-Mms-Read-Report |
-     *  X-Mms-Report-Allowed |
-     *  X-Mms-Sender-Visibility |
-     *  X-Mms-Store |
-     *  X-Mms-Stored |
-     *  X-Mms-Totals |
-     *  X-Mms-Quotas |
-     *  X-Mms-Distribution-Indicator |
-     *  X-Mms-DRM-Content |
-     *  X-Mms-Adaptation-Allowed |
-     *  field types.
-     */
-    public static final int VALUE_YES                       = 0x80;
-    public static final int VALUE_NO                        = 0x81;
-
-    /**
-     *  Delivery-Time |
-     *  Expiry and Reply-Charging-Deadline |
-     *  field type components.
-     */
-    public static final int VALUE_ABSOLUTE_TOKEN            = 0x80;
-    public static final int VALUE_RELATIVE_TOKEN            = 0x81;
-
-    /**
-     * X-Mms-MMS-Version field types.
-     */
-    public static final int MMS_VERSION_1_3                 = ((1 << 4) | 3);
-    public static final int MMS_VERSION_1_2                 = ((1 << 4) | 2);
-    public static final int MMS_VERSION_1_1                 = ((1 << 4) | 1);
-    public static final int MMS_VERSION_1_0                 = ((1 << 4) | 0);
-
-    // Current version is 1.2.
-    public static final int CURRENT_MMS_VERSION             = MMS_VERSION_1_2;
-
-    /**
-     *  From field type components.
-     */
-    public static final int FROM_ADDRESS_PRESENT_TOKEN      = 0x80;
-    public static final int FROM_INSERT_ADDRESS_TOKEN       = 0x81;
-
-    public static final String FROM_ADDRESS_PRESENT_TOKEN_STR = "address-present-token";
-    public static final String FROM_INSERT_ADDRESS_TOKEN_STR = "insert-address-token";
-
-    /**
-     *  X-Mms-Status Field.
-     */
-    public static final int STATUS_EXPIRED                  = 0x80;
-    public static final int STATUS_RETRIEVED                = 0x81;
-    public static final int STATUS_REJECTED                 = 0x82;
-    public static final int STATUS_DEFERRED                 = 0x83;
-    public static final int STATUS_UNRECOGNIZED             = 0x84;
-    public static final int STATUS_INDETERMINATE            = 0x85;
-    public static final int STATUS_FORWARDED                = 0x86;
-    public static final int STATUS_UNREACHABLE              = 0x87;
-
-    /**
-     *  MM-Flags field type components.
-     */
-    public static final int MM_FLAGS_ADD_TOKEN              = 0x80;
-    public static final int MM_FLAGS_REMOVE_TOKEN           = 0x81;
-    public static final int MM_FLAGS_FILTER_TOKEN           = 0x82;
-
-    /**
-     *  X-Mms-Message-Class field types.
-     */
-    public static final int MESSAGE_CLASS_PERSONAL          = 0x80;
-    public static final int MESSAGE_CLASS_ADVERTISEMENT     = 0x81;
-    public static final int MESSAGE_CLASS_INFORMATIONAL     = 0x82;
-    public static final int MESSAGE_CLASS_AUTO              = 0x83;
-
-    public static final String MESSAGE_CLASS_PERSONAL_STR = "personal";
-    public static final String MESSAGE_CLASS_ADVERTISEMENT_STR = "advertisement";
-    public static final String MESSAGE_CLASS_INFORMATIONAL_STR = "informational";
-    public static final String MESSAGE_CLASS_AUTO_STR = "auto";
-
-    /**
-     *  X-Mms-Priority field types.
-     */
-    public static final int PRIORITY_LOW                    = 0x80;
-    public static final int PRIORITY_NORMAL                 = 0x81;
-    public static final int PRIORITY_HIGH                   = 0x82;
-
-    /**
-     *  X-Mms-Response-Status field types.
-     */
-    public static final int RESPONSE_STATUS_OK                   = 0x80;
-    public static final int RESPONSE_STATUS_ERROR_UNSPECIFIED    = 0x81;
-    public static final int RESPONSE_STATUS_ERROR_SERVICE_DENIED = 0x82;
-
-    public static final int RESPONSE_STATUS_ERROR_MESSAGE_FORMAT_CORRUPT     = 0x83;
-    public static final int RESPONSE_STATUS_ERROR_SENDING_ADDRESS_UNRESOLVED = 0x84;
-
-    public static final int RESPONSE_STATUS_ERROR_MESSAGE_NOT_FOUND    = 0x85;
-    public static final int RESPONSE_STATUS_ERROR_NETWORK_PROBLEM      = 0x86;
-    public static final int RESPONSE_STATUS_ERROR_CONTENT_NOT_ACCEPTED = 0x87;
-    public static final int RESPONSE_STATUS_ERROR_UNSUPPORTED_MESSAGE  = 0x88;
-    public static final int RESPONSE_STATUS_ERROR_TRANSIENT_FAILURE    = 0xC0;
-
-    public static final int RESPONSE_STATUS_ERROR_TRANSIENT_SENDNG_ADDRESS_UNRESOLVED = 0xC1;
-    public static final int RESPONSE_STATUS_ERROR_TRANSIENT_MESSAGE_NOT_FOUND         = 0xC2;
-    public static final int RESPONSE_STATUS_ERROR_TRANSIENT_NETWORK_PROBLEM           = 0xC3;
-    public static final int RESPONSE_STATUS_ERROR_TRANSIENT_PARTIAL_SUCCESS           = 0xC4;
-
-    public static final int RESPONSE_STATUS_ERROR_PERMANENT_FAILURE                             = 0xE0;
-    public static final int RESPONSE_STATUS_ERROR_PERMANENT_SERVICE_DENIED                      = 0xE1;
-    public static final int RESPONSE_STATUS_ERROR_PERMANENT_MESSAGE_FORMAT_CORRUPT              = 0xE2;
-    public static final int RESPONSE_STATUS_ERROR_PERMANENT_SENDING_ADDRESS_UNRESOLVED          = 0xE3;
-    public static final int RESPONSE_STATUS_ERROR_PERMANENT_MESSAGE_NOT_FOUND                   = 0xE4;
-    public static final int RESPONSE_STATUS_ERROR_PERMANENT_CONTENT_NOT_ACCEPTED                = 0xE5;
-    public static final int RESPONSE_STATUS_ERROR_PERMANENT_REPLY_CHARGING_LIMITATIONS_NOT_MET  = 0xE6;
-    public static final int RESPONSE_STATUS_ERROR_PERMANENT_REPLY_CHARGING_REQUEST_NOT_ACCEPTED = 0xE6;
-    public static final int RESPONSE_STATUS_ERROR_PERMANENT_REPLY_CHARGING_FORWARDING_DENIED    = 0xE8;
-    public static final int RESPONSE_STATUS_ERROR_PERMANENT_REPLY_CHARGING_NOT_SUPPORTED        = 0xE9;
-    public static final int RESPONSE_STATUS_ERROR_PERMANENT_ADDRESS_HIDING_NOT_SUPPORTED        = 0xEA;
-    public static final int RESPONSE_STATUS_ERROR_PERMANENT_LACK_OF_PREPAID                     = 0xEB;
-    public static final int RESPONSE_STATUS_ERROR_PERMANENT_END                                 = 0xFF;
-
-    /**
-     *  X-Mms-Retrieve-Status field types.
-     */
-    public static final int RETRIEVE_STATUS_OK                                  = 0x80;
-    public static final int RETRIEVE_STATUS_ERROR_TRANSIENT_FAILURE             = 0xC0;
-    public static final int RETRIEVE_STATUS_ERROR_TRANSIENT_MESSAGE_NOT_FOUND   = 0xC1;
-    public static final int RETRIEVE_STATUS_ERROR_TRANSIENT_NETWORK_PROBLEM     = 0xC2;
-    public static final int RETRIEVE_STATUS_ERROR_PERMANENT_FAILURE             = 0xE0;
-    public static final int RETRIEVE_STATUS_ERROR_PERMANENT_SERVICE_DENIED      = 0xE1;
-    public static final int RETRIEVE_STATUS_ERROR_PERMANENT_MESSAGE_NOT_FOUND   = 0xE2;
-    public static final int RETRIEVE_STATUS_ERROR_PERMANENT_CONTENT_UNSUPPORTED = 0xE3;
-    public static final int RETRIEVE_STATUS_ERROR_END                           = 0xFF;
-
-    /**
-     *  X-Mms-Sender-Visibility field types.
-     */
-    public static final int SENDER_VISIBILITY_HIDE          = 0x80;
-    public static final int SENDER_VISIBILITY_SHOW          = 0x81;
-
-    /**
-     *  X-Mms-Read-Status field types.
-     */
-    public static final int READ_STATUS_READ                        = 0x80;
-    public static final int READ_STATUS__DELETED_WITHOUT_BEING_READ = 0x81;
-
-    /**
-     *  X-Mms-Cancel-Status field types.
-     */
-    public static final int CANCEL_STATUS_REQUEST_SUCCESSFULLY_RECEIVED = 0x80;
-    public static final int CANCEL_STATUS_REQUEST_CORRUPTED             = 0x81;
-
-    /**
-     *  X-Mms-Reply-Charging field types.
-     */
-    public static final int REPLY_CHARGING_REQUESTED           = 0x80;
-    public static final int REPLY_CHARGING_REQUESTED_TEXT_ONLY = 0x81;
-    public static final int REPLY_CHARGING_ACCEPTED            = 0x82;
-    public static final int REPLY_CHARGING_ACCEPTED_TEXT_ONLY  = 0x83;
-
-    /**
-     *  X-Mms-MM-State field types.
-     */
-    public static final int MM_STATE_DRAFT                  = 0x80;
-    public static final int MM_STATE_SENT                   = 0x81;
-    public static final int MM_STATE_NEW                    = 0x82;
-    public static final int MM_STATE_RETRIEVED              = 0x83;
-    public static final int MM_STATE_FORWARDED              = 0x84;
-
-    /**
-     * X-Mms-Recommended-Retrieval-Mode field types.
-     */
-    public static final int RECOMMENDED_RETRIEVAL_MODE_MANUAL = 0x80;
-
-    /**
-     *  X-Mms-Content-Class field types.
-     */
-    public static final int CONTENT_CLASS_TEXT              = 0x80;
-    public static final int CONTENT_CLASS_IMAGE_BASIC       = 0x81;
-    public static final int CONTENT_CLASS_IMAGE_RICH        = 0x82;
-    public static final int CONTENT_CLASS_VIDEO_BASIC       = 0x83;
-    public static final int CONTENT_CLASS_VIDEO_RICH        = 0x84;
-    public static final int CONTENT_CLASS_MEGAPIXEL         = 0x85;
-    public static final int CONTENT_CLASS_CONTENT_BASIC     = 0x86;
-    public static final int CONTENT_CLASS_CONTENT_RICH      = 0x87;
-
-    /**
-     *  X-Mms-Store-Status field types.
-     */
-    public static final int STORE_STATUS_SUCCESS                                = 0x80;
-    public static final int STORE_STATUS_ERROR_TRANSIENT_FAILURE                = 0xC0;
-    public static final int STORE_STATUS_ERROR_TRANSIENT_NETWORK_PROBLEM        = 0xC1;
-    public static final int STORE_STATUS_ERROR_PERMANENT_FAILURE                = 0xE0;
-    public static final int STORE_STATUS_ERROR_PERMANENT_SERVICE_DENIED         = 0xE1;
-    public static final int STORE_STATUS_ERROR_PERMANENT_MESSAGE_FORMAT_CORRUPT = 0xE2;
-    public static final int STORE_STATUS_ERROR_PERMANENT_MESSAGE_NOT_FOUND      = 0xE3;
-    public static final int STORE_STATUS_ERROR_PERMANENT_MMBOX_FULL             = 0xE4;
-    public static final int STORE_STATUS_ERROR_END                              = 0xFF;
-
-    /**
-     * The map contains the value of all headers.
-     */
-    private HashMap<Integer, Object> mHeaderMap = null;
-
-    /**
-     * Constructor of PduHeaders.
-     */
-    public PduHeaders() {
-        mHeaderMap = new HashMap<Integer, Object>();
-    }
-
-    /**
-     * Get octet value by header field.
-     *
-     * @param field the field
-     * @return the octet value of the pdu header
-     *          with specified header field. Return 0 if
-     *          the value is not set.
-     */
-    protected int getOctet(int field) {
-        Integer octet = (Integer) mHeaderMap.get(field);
-        if (null == octet) {
-            return 0;
-        }
-
-        return octet;
-    }
-
-    /**
-     * Set octet value to pdu header by header field.
-     *
-     * @param value the value
-     * @param field the field
-     * @throws InvalidHeaderValueException if the value is invalid.
-     */
-    protected void setOctet(int value, int field)
-            throws InvalidHeaderValueException{
-        /**
-         * Check whether this field can be set for specific
-         * header and check validity of the field.
-         */
-        switch (field) {
-            case REPORT_ALLOWED:
-            case ADAPTATION_ALLOWED:
-            case DELIVERY_REPORT:
-            case DRM_CONTENT:
-            case DISTRIBUTION_INDICATOR:
-            case QUOTAS:
-            case READ_REPORT:
-            case STORE:
-            case STORED:
-            case TOTALS:
-            case SENDER_VISIBILITY:
-                if ((VALUE_YES != value) && (VALUE_NO != value)) {
-                    // Invalid value.
-                    throw new InvalidHeaderValueException("Invalid Octet value!");
-                }
-                break;
-            case READ_STATUS:
-                if ((READ_STATUS_READ != value) &&
-                        (READ_STATUS__DELETED_WITHOUT_BEING_READ != value)) {
-                    // Invalid value.
-                    throw new InvalidHeaderValueException("Invalid Octet value!");
-                }
-                break;
-            case CANCEL_STATUS:
-                if ((CANCEL_STATUS_REQUEST_SUCCESSFULLY_RECEIVED != value) &&
-                        (CANCEL_STATUS_REQUEST_CORRUPTED != value)) {
-                    // Invalid value.
-                    throw new InvalidHeaderValueException("Invalid Octet value!");
-                }
-                break;
-            case PRIORITY:
-                if ((value < PRIORITY_LOW) || (value > PRIORITY_HIGH)) {
-                    // Invalid value.
-                    throw new InvalidHeaderValueException("Invalid Octet value!");
-                }
-                break;
-            case STATUS:
-                if ((value < STATUS_EXPIRED) || (value > STATUS_UNREACHABLE)) {
-                    // Invalid value.
-                    throw new InvalidHeaderValueException("Invalid Octet value!");
-                }
-                break;
-            case REPLY_CHARGING:
-                if ((value < REPLY_CHARGING_REQUESTED)
-                        || (value > REPLY_CHARGING_ACCEPTED_TEXT_ONLY)) {
-                    // Invalid value.
-                    throw new InvalidHeaderValueException("Invalid Octet value!");
-                }
-                break;
-            case MM_STATE:
-                if ((value < MM_STATE_DRAFT) || (value > MM_STATE_FORWARDED)) {
-                    // Invalid value.
-                    throw new InvalidHeaderValueException("Invalid Octet value!");
-                }
-                break;
-            case RECOMMENDED_RETRIEVAL_MODE:
-                if (RECOMMENDED_RETRIEVAL_MODE_MANUAL != value) {
-                    // Invalid value.
-                    throw new InvalidHeaderValueException("Invalid Octet value!");
-                }
-                break;
-            case CONTENT_CLASS:
-                if ((value < CONTENT_CLASS_TEXT)
-                        || (value > CONTENT_CLASS_CONTENT_RICH)) {
-                    // Invalid value.
-                    throw new InvalidHeaderValueException("Invalid Octet value!");
-                }
-                break;
-            case RETRIEVE_STATUS:
-                // According to oma-ts-mms-enc-v1_3, section 7.3.50, we modify the invalid value.
-                if ((value > RETRIEVE_STATUS_ERROR_TRANSIENT_NETWORK_PROBLEM) &&
-                        (value < RETRIEVE_STATUS_ERROR_PERMANENT_FAILURE)) {
-                    value = RETRIEVE_STATUS_ERROR_TRANSIENT_FAILURE;
-                } else if ((value > RETRIEVE_STATUS_ERROR_PERMANENT_CONTENT_UNSUPPORTED) &&
-                        (value <= RETRIEVE_STATUS_ERROR_END)) {
-                    value = RETRIEVE_STATUS_ERROR_PERMANENT_FAILURE;
-                } else if ((value < RETRIEVE_STATUS_OK) ||
-                        ((value > RETRIEVE_STATUS_OK) &&
-                                (value < RETRIEVE_STATUS_ERROR_TRANSIENT_FAILURE)) ||
-                                (value > RETRIEVE_STATUS_ERROR_END)) {
-                    value = RETRIEVE_STATUS_ERROR_PERMANENT_FAILURE;
-                }
-                break;
-            case STORE_STATUS:
-                // According to oma-ts-mms-enc-v1_3, section 7.3.58, we modify the invalid value.
-                if ((value > STORE_STATUS_ERROR_TRANSIENT_NETWORK_PROBLEM) &&
-                        (value < STORE_STATUS_ERROR_PERMANENT_FAILURE)) {
-                    value = STORE_STATUS_ERROR_TRANSIENT_FAILURE;
-                } else if ((value > STORE_STATUS_ERROR_PERMANENT_MMBOX_FULL) &&
-                        (value <= STORE_STATUS_ERROR_END)) {
-                    value = STORE_STATUS_ERROR_PERMANENT_FAILURE;
-                } else if ((value < STORE_STATUS_SUCCESS) ||
-                        ((value > STORE_STATUS_SUCCESS) &&
-                                (value < STORE_STATUS_ERROR_TRANSIENT_FAILURE)) ||
-                                (value > STORE_STATUS_ERROR_END)) {
-                    value = STORE_STATUS_ERROR_PERMANENT_FAILURE;
-                }
-                break;
-            case RESPONSE_STATUS:
-                // According to oma-ts-mms-enc-v1_3, section 7.3.48, we modify the invalid value.
-                if ((value > RESPONSE_STATUS_ERROR_TRANSIENT_PARTIAL_SUCCESS) &&
-                        (value < RESPONSE_STATUS_ERROR_PERMANENT_FAILURE)) {
-                    value = RESPONSE_STATUS_ERROR_TRANSIENT_FAILURE;
-                } else if (((value > RESPONSE_STATUS_ERROR_PERMANENT_LACK_OF_PREPAID) &&
-                        (value <= RESPONSE_STATUS_ERROR_PERMANENT_END)) ||
-                        (value < RESPONSE_STATUS_OK) ||
-                        ((value > RESPONSE_STATUS_ERROR_UNSUPPORTED_MESSAGE) &&
-                                (value < RESPONSE_STATUS_ERROR_TRANSIENT_FAILURE)) ||
-                                (value > RESPONSE_STATUS_ERROR_PERMANENT_END)) {
-                    value = RESPONSE_STATUS_ERROR_PERMANENT_FAILURE;
-                }
-                break;
-            case MMS_VERSION:
-                if ((value < MMS_VERSION_1_0)|| (value > MMS_VERSION_1_3)) {
-                    value = CURRENT_MMS_VERSION; // Current version is the default value.
-                }
-                break;
-            case MESSAGE_TYPE:
-                if ((value < MESSAGE_TYPE_SEND_REQ) || (value > MESSAGE_TYPE_CANCEL_CONF)) {
-                    // Invalid value.
-                    throw new InvalidHeaderValueException("Invalid Octet value!");
-                }
-                break;
-            default:
-                // This header value should not be Octect.
-                throw new RuntimeException("Invalid header field!");
-        }
-        mHeaderMap.put(field, value);
-    }
-
-    /**
-     * Get TextString value by header field.
-     *
-     * @param field the field
-     * @return the TextString value of the pdu header
-     *          with specified header field
-     */
-    protected byte[] getTextString(int field) {
-        return (byte[]) mHeaderMap.get(field);
-    }
-
-    /**
-     * Set TextString value to pdu header by header field.
-     *
-     * @param value the value
-     * @param field the field
-     * @return the TextString value of the pdu header
-     *          with specified header field
-     * @throws NullPointerException if the value is null.
-     */
-    protected void setTextString(byte[] value, int field) {
-        /**
-         * Check whether this field can be set for specific
-         * header and check validity of the field.
-         */
-        if (null == value) {
-            throw new NullPointerException();
-        }
-
-        switch (field) {
-            case TRANSACTION_ID:
-            case REPLY_CHARGING_ID:
-            case AUX_APPLIC_ID:
-            case APPLIC_ID:
-            case REPLY_APPLIC_ID:
-            case MESSAGE_ID:
-            case REPLACE_ID:
-            case CANCEL_ID:
-            case CONTENT_LOCATION:
-            case MESSAGE_CLASS:
-            case CONTENT_TYPE:
-                break;
-            default:
-                // This header value should not be Text-String.
-                throw new RuntimeException("Invalid header field!");
-        }
-        mHeaderMap.put(field, value);
-    }
-
-    /**
-     * Get EncodedStringValue value by header field.
-     *
-     * @param field the field
-     * @return the EncodedStringValue value of the pdu header
-     *          with specified header field
-     */
-    protected EncodedStringValue getEncodedStringValue(int field) {
-        return (EncodedStringValue) mHeaderMap.get(field);
-    }
-
-    /**
-     * Get TO, CC or BCC header value.
-     *
-     * @param field the field
-     * @return the EncodeStringValue array of the pdu header
-     *          with specified header field
-     */
-    protected EncodedStringValue[] getEncodedStringValues(int field) {
-        ArrayList<EncodedStringValue> list =
-                (ArrayList<EncodedStringValue>) mHeaderMap.get(field);
-        if (null == list) {
-            return null;
-        }
-        EncodedStringValue[] values = new EncodedStringValue[list.size()];
-        return list.toArray(values);
-    }
-
-    /**
-     * Set EncodedStringValue value to pdu header by header field.
-     *
-     * @param value the value
-     * @param field the field
-     * @return the EncodedStringValue value of the pdu header
-     *          with specified header field
-     * @throws NullPointerException if the value is null.
-     */
-    protected void setEncodedStringValue(EncodedStringValue value, int field) {
-        /**
-         * Check whether this field can be set for specific
-         * header and check validity of the field.
-         */
-        if (null == value) {
-            throw new NullPointerException();
-        }
-
-        switch (field) {
-            case SUBJECT:
-            case RECOMMENDED_RETRIEVAL_MODE_TEXT:
-            case RETRIEVE_TEXT:
-            case STATUS_TEXT:
-            case STORE_STATUS_TEXT:
-            case RESPONSE_TEXT:
-            case FROM:
-            case PREVIOUSLY_SENT_BY:
-            case MM_FLAGS:
-                break;
-            default:
-                // This header value should not be Encoded-String-Value.
-                throw new RuntimeException("Invalid header field!");
-        }
-
-        mHeaderMap.put(field, value);
-    }
-
-    /**
-     * Set TO, CC or BCC header value.
-     *
-     * @param value the value
-     * @param field the field
-     * @return the EncodedStringValue value array of the pdu header
-     *          with specified header field
-     * @throws NullPointerException if the value is null.
-     */
-    protected void setEncodedStringValues(EncodedStringValue[] value, int field) {
-        /**
-         * Check whether this field can be set for specific
-         * header and check validity of the field.
-         */
-        if (null == value) {
-            throw new NullPointerException();
-        }
-
-        switch (field) {
-            case BCC:
-            case CC:
-            case TO:
-                break;
-            default:
-                // This header value should not be Encoded-String-Value.
-                throw new RuntimeException("Invalid header field!");
-        }
-
-        ArrayList<EncodedStringValue> list = new ArrayList<EncodedStringValue>();
-        for (int i = 0; i < value.length; i++) {
-            list.add(value[i]);
-        }
-        mHeaderMap.put(field, list);
-    }
-
-    /**
-     * Append one EncodedStringValue to another.
-     *
-     * @param value the EncodedStringValue to append
-     * @param field the field
-     * @throws NullPointerException if the value is null.
-     */
-    protected void appendEncodedStringValue(EncodedStringValue value,
-                                    int field) {
-        if (null == value) {
-            throw new NullPointerException();
-        }
-
-        switch (field) {
-            case BCC:
-            case CC:
-            case TO:
-                break;
-            default:
-                throw new RuntimeException("Invalid header field!");
-        }
-
-        ArrayList<EncodedStringValue> list =
-            (ArrayList<EncodedStringValue>) mHeaderMap.get(field);
-        if (null == list) {
-            list  = new ArrayList<EncodedStringValue>();
-        }
-        list.add(value);
-        mHeaderMap.put(field, list);
-    }
-
-    /**
-     * Get LongInteger value by header field.
-     *
-     * @param field the field
-     * @return the LongInteger value of the pdu header
-     *          with specified header field. if return -1, the
-     *          field is not existed in pdu header.
-     */
-    protected long getLongInteger(int field) {
-        Long longInteger = (Long) mHeaderMap.get(field);
-        if (null == longInteger) {
-            return -1;
-        }
-
-        return longInteger.longValue();
-    }
-
-    /**
-     * Set LongInteger value to pdu header by header field.
-     *
-     * @param value the value
-     * @param field the field
-     */
-    protected void setLongInteger(long value, int field) {
-        /**
-         * Check whether this field can be set for specific
-         * header and check validity of the field.
-         */
-        switch (field) {
-            case DATE:
-            case REPLY_CHARGING_SIZE:
-            case MESSAGE_SIZE:
-            case MESSAGE_COUNT:
-            case START:
-            case LIMIT:
-            case DELIVERY_TIME:
-            case EXPIRY:
-            case REPLY_CHARGING_DEADLINE:
-            case PREVIOUSLY_SENT_DATE:
-                break;
-            default:
-                // This header value should not be LongInteger.
-                throw new RuntimeException("Invalid header field!");
-        }
-        mHeaderMap.put(field, value);
-    }
-}
diff --git a/src/java/com/google/android/mms/pdu/PduParser.java b/src/java/com/google/android/mms/pdu/PduParser.java
deleted file mode 100755
index 9fbdb66..0000000
--- a/src/java/com/google/android/mms/pdu/PduParser.java
+++ /dev/null
@@ -1,2011 +0,0 @@
-/*
- * Copyright (C) 2007-2008 Esmertec AG.
- * Copyright (C) 2007-2008 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.google.android.mms.pdu;
-
-import android.util.Log;
-
-import com.google.android.mms.ContentType;
-import com.google.android.mms.InvalidHeaderValueException;
-
-import java.io.ByteArrayInputStream;
-import java.io.ByteArrayOutputStream;
-import java.io.UnsupportedEncodingException;
-import java.util.Arrays;
-import java.util.HashMap;
-
-public class PduParser {
-    /**
-     *  The next are WAP values defined in WSP specification.
-     */
-    private static final int QUOTE = 127;
-    private static final int LENGTH_QUOTE = 31;
-    private static final int TEXT_MIN = 32;
-    private static final int TEXT_MAX = 127;
-    private static final int SHORT_INTEGER_MAX = 127;
-    private static final int SHORT_LENGTH_MAX = 30;
-    private static final int LONG_INTEGER_LENGTH_MAX = 8;
-    private static final int QUOTED_STRING_FLAG = 34;
-    private static final int END_STRING_FLAG = 0x00;
-    //The next two are used by the interface "parseWapString" to
-    //distinguish Text-String and Quoted-String.
-    private static final int TYPE_TEXT_STRING = 0;
-    private static final int TYPE_QUOTED_STRING = 1;
-    private static final int TYPE_TOKEN_STRING = 2;
-
-    /**
-     * Specify the part position.
-     */
-    private static final int THE_FIRST_PART = 0;
-    private static final int THE_LAST_PART = 1;
-
-    /**
-     * The pdu data.
-     */
-    private ByteArrayInputStream mPduDataStream = null;
-
-    /**
-     * Store pdu headers
-     */
-    private PduHeaders mHeaders = null;
-
-    /**
-     * Store pdu parts.
-     */
-    private PduBody mBody = null;
-
-    /**
-     * Store the "type" parameter in "Content-Type" header field.
-     */
-    private static byte[] mTypeParam = null;
-
-    /**
-     * Store the "start" parameter in "Content-Type" header field.
-     */
-    private static byte[] mStartParam = null;
-
-    /**
-     * The log tag.
-     */
-    private static final String LOG_TAG = "PduParser";
-    private static final boolean DEBUG = false;
-    private static final boolean LOCAL_LOGV = false;
-
-    /**
-     * Whether to parse content-disposition part header
-     */
-    private final boolean mParseContentDisposition;
-
-    /**
-     * Constructor.
-     *
-     * @param pduDataStream pdu data to be parsed
-     * @param parseContentDisposition whether to parse the Content-Disposition part header
-     */
-    public PduParser(byte[] pduDataStream, boolean parseContentDisposition) {
-        mPduDataStream = new ByteArrayInputStream(pduDataStream);
-        mParseContentDisposition = parseContentDisposition;
-    }
-
-    /**
-     * Parse the pdu.
-     *
-     * @return the pdu structure if parsing successfully.
-     *         null if parsing error happened or mandatory fields are not set.
-     */
-    public GenericPdu parse(){
-        if (mPduDataStream == null) {
-            return null;
-        }
-
-        /* parse headers */
-        mHeaders = parseHeaders(mPduDataStream);
-        if (null == mHeaders) {
-            // Parse headers failed.
-            return null;
-        }
-
-        /* get the message type */
-        int messageType = mHeaders.getOctet(PduHeaders.MESSAGE_TYPE);
-
-        /* check mandatory header fields */
-        if (false == checkMandatoryHeader(mHeaders)) {
-            log("check mandatory headers failed!");
-            return null;
-        }
-
-        if ((PduHeaders.MESSAGE_TYPE_SEND_REQ == messageType) ||
-                (PduHeaders.MESSAGE_TYPE_RETRIEVE_CONF == messageType)) {
-            /* need to parse the parts */
-            mBody = parseParts(mPduDataStream);
-            if (null == mBody) {
-                // Parse parts failed.
-                return null;
-            }
-        }
-
-        switch (messageType) {
-            case PduHeaders.MESSAGE_TYPE_SEND_REQ:
-                if (LOCAL_LOGV) {
-                    Log.v(LOG_TAG, "parse: MESSAGE_TYPE_SEND_REQ");
-                }
-                SendReq sendReq = new SendReq(mHeaders, mBody);
-                return sendReq;
-            case PduHeaders.MESSAGE_TYPE_SEND_CONF:
-                if (LOCAL_LOGV) {
-                    Log.v(LOG_TAG, "parse: MESSAGE_TYPE_SEND_CONF");
-                }
-                SendConf sendConf = new SendConf(mHeaders);
-                return sendConf;
-            case PduHeaders.MESSAGE_TYPE_NOTIFICATION_IND:
-                if (LOCAL_LOGV) {
-                    Log.v(LOG_TAG, "parse: MESSAGE_TYPE_NOTIFICATION_IND");
-                }
-                NotificationInd notificationInd =
-                    new NotificationInd(mHeaders);
-                return notificationInd;
-            case PduHeaders.MESSAGE_TYPE_NOTIFYRESP_IND:
-                if (LOCAL_LOGV) {
-                    Log.v(LOG_TAG, "parse: MESSAGE_TYPE_NOTIFYRESP_IND");
-                }
-                NotifyRespInd notifyRespInd =
-                    new NotifyRespInd(mHeaders);
-                return notifyRespInd;
-            case PduHeaders.MESSAGE_TYPE_RETRIEVE_CONF:
-                if (LOCAL_LOGV) {
-                    Log.v(LOG_TAG, "parse: MESSAGE_TYPE_RETRIEVE_CONF");
-                }
-                RetrieveConf retrieveConf =
-                    new RetrieveConf(mHeaders, mBody);
-
-                byte[] contentType = retrieveConf.getContentType();
-                if (null == contentType) {
-                    return null;
-                }
-                String ctTypeStr = new String(contentType);
-                if (ctTypeStr.equals(ContentType.MULTIPART_MIXED)
-                        || ctTypeStr.equals(ContentType.MULTIPART_RELATED)
-                        || ctTypeStr.equals(ContentType.MULTIPART_ALTERNATIVE)) {
-                    // The MMS content type must be "application/vnd.wap.multipart.mixed"
-                    // or "application/vnd.wap.multipart.related"
-                    // or "application/vnd.wap.multipart.alternative"
-                    return retrieveConf;
-                } else if (ctTypeStr.equals(ContentType.MULTIPART_ALTERNATIVE)) {
-                    // "application/vnd.wap.multipart.alternative"
-                    // should take only the first part.
-                    PduPart firstPart = mBody.getPart(0);
-                    mBody.removeAll();
-                    mBody.addPart(0, firstPart);
-                    return retrieveConf;
-                }
-                return null;
-            case PduHeaders.MESSAGE_TYPE_DELIVERY_IND:
-                if (LOCAL_LOGV) {
-                    Log.v(LOG_TAG, "parse: MESSAGE_TYPE_DELIVERY_IND");
-                }
-                DeliveryInd deliveryInd =
-                    new DeliveryInd(mHeaders);
-                return deliveryInd;
-            case PduHeaders.MESSAGE_TYPE_ACKNOWLEDGE_IND:
-                if (LOCAL_LOGV) {
-                    Log.v(LOG_TAG, "parse: MESSAGE_TYPE_ACKNOWLEDGE_IND");
-                }
-                AcknowledgeInd acknowledgeInd =
-                    new AcknowledgeInd(mHeaders);
-                return acknowledgeInd;
-            case PduHeaders.MESSAGE_TYPE_READ_ORIG_IND:
-                if (LOCAL_LOGV) {
-                    Log.v(LOG_TAG, "parse: MESSAGE_TYPE_READ_ORIG_IND");
-                }
-                ReadOrigInd readOrigInd =
-                    new ReadOrigInd(mHeaders);
-                return readOrigInd;
-            case PduHeaders.MESSAGE_TYPE_READ_REC_IND:
-                if (LOCAL_LOGV) {
-                    Log.v(LOG_TAG, "parse: MESSAGE_TYPE_READ_REC_IND");
-                }
-                ReadRecInd readRecInd =
-                    new ReadRecInd(mHeaders);
-                return readRecInd;
-            default:
-                log("Parser doesn't support this message type in this version!");
-            return null;
-        }
-    }
-
-    /**
-     * Parse pdu headers.
-     *
-     * @param pduDataStream pdu data input stream
-     * @return headers in PduHeaders structure, null when parse fail
-     */
-    protected PduHeaders parseHeaders(ByteArrayInputStream pduDataStream){
-        if (pduDataStream == null) {
-            return null;
-        }
-        boolean keepParsing = true;
-        PduHeaders headers = new PduHeaders();
-
-        while (keepParsing && (pduDataStream.available() > 0)) {
-            pduDataStream.mark(1);
-            int headerField = extractByteValue(pduDataStream);
-            /* parse custom text header */
-            if ((headerField >= TEXT_MIN) && (headerField <= TEXT_MAX)) {
-                pduDataStream.reset();
-                byte [] bVal = parseWapString(pduDataStream, TYPE_TEXT_STRING);
-                if (LOCAL_LOGV) {
-                    Log.v(LOG_TAG, "TextHeader: " + new String(bVal));
-                }
-                /* we should ignore it at the moment */
-                continue;
-            }
-            switch (headerField) {
-                case PduHeaders.MESSAGE_TYPE:
-                {
-                    int messageType = extractByteValue(pduDataStream);
-                    if (LOCAL_LOGV) {
-                        Log.v(LOG_TAG, "parseHeaders: messageType: " + messageType);
-                    }
-                    switch (messageType) {
-                        // We don't support these kind of messages now.
-                        case PduHeaders.MESSAGE_TYPE_FORWARD_REQ:
-                        case PduHeaders.MESSAGE_TYPE_FORWARD_CONF:
-                        case PduHeaders.MESSAGE_TYPE_MBOX_STORE_REQ:
-                        case PduHeaders.MESSAGE_TYPE_MBOX_STORE_CONF:
-                        case PduHeaders.MESSAGE_TYPE_MBOX_VIEW_REQ:
-                        case PduHeaders.MESSAGE_TYPE_MBOX_VIEW_CONF:
-                        case PduHeaders.MESSAGE_TYPE_MBOX_UPLOAD_REQ:
-                        case PduHeaders.MESSAGE_TYPE_MBOX_UPLOAD_CONF:
-                        case PduHeaders.MESSAGE_TYPE_MBOX_DELETE_REQ:
-                        case PduHeaders.MESSAGE_TYPE_MBOX_DELETE_CONF:
-                        case PduHeaders.MESSAGE_TYPE_MBOX_DESCR:
-                        case PduHeaders.MESSAGE_TYPE_DELETE_REQ:
-                        case PduHeaders.MESSAGE_TYPE_DELETE_CONF:
-                        case PduHeaders.MESSAGE_TYPE_CANCEL_REQ:
-                        case PduHeaders.MESSAGE_TYPE_CANCEL_CONF:
-                            return null;
-                    }
-                    try {
-                        headers.setOctet(messageType, headerField);
-                    } catch(InvalidHeaderValueException e) {
-                        log("Set invalid Octet value: " + messageType +
-                                " into the header filed: " + headerField);
-                        return null;
-                    } catch(RuntimeException e) {
-                        log(headerField + "is not Octet header field!");
-                        return null;
-                    }
-                    break;
-                }
-                /* Octect value */
-                case PduHeaders.REPORT_ALLOWED:
-                case PduHeaders.ADAPTATION_ALLOWED:
-                case PduHeaders.DELIVERY_REPORT:
-                case PduHeaders.DRM_CONTENT:
-                case PduHeaders.DISTRIBUTION_INDICATOR:
-                case PduHeaders.QUOTAS:
-                case PduHeaders.READ_REPORT:
-                case PduHeaders.STORE:
-                case PduHeaders.STORED:
-                case PduHeaders.TOTALS:
-                case PduHeaders.SENDER_VISIBILITY:
-                case PduHeaders.READ_STATUS:
-                case PduHeaders.CANCEL_STATUS:
-                case PduHeaders.PRIORITY:
-                case PduHeaders.STATUS:
-                case PduHeaders.REPLY_CHARGING:
-                case PduHeaders.MM_STATE:
-                case PduHeaders.RECOMMENDED_RETRIEVAL_MODE:
-                case PduHeaders.CONTENT_CLASS:
-                case PduHeaders.RETRIEVE_STATUS:
-                case PduHeaders.STORE_STATUS:
-                    /**
-                     * The following field has a different value when
-                     * used in the M-Mbox-Delete.conf and M-Delete.conf PDU.
-                     * For now we ignore this fact, since we do not support these PDUs
-                     */
-                case PduHeaders.RESPONSE_STATUS:
-                {
-                    int value = extractByteValue(pduDataStream);
-                    if (LOCAL_LOGV) {
-                        Log.v(LOG_TAG, "parseHeaders: byte: " + headerField + " value: " +
-                                value);
-                    }
-
-                    try {
-                        headers.setOctet(value, headerField);
-                    } catch(InvalidHeaderValueException e) {
-                        log("Set invalid Octet value: " + value +
-                                " into the header filed: " + headerField);
-                        return null;
-                    } catch(RuntimeException e) {
-                        log(headerField + "is not Octet header field!");
-                        return null;
-                    }
-                    break;
-                }
-
-                /* Long-Integer */
-                case PduHeaders.DATE:
-                case PduHeaders.REPLY_CHARGING_SIZE:
-                case PduHeaders.MESSAGE_SIZE:
-                {
-                    try {
-                        long value = parseLongInteger(pduDataStream);
-                        if (LOCAL_LOGV) {
-                            Log.v(LOG_TAG, "parseHeaders: longint: " + headerField + " value: " +
-                                    value);
-                        }
-                        headers.setLongInteger(value, headerField);
-                    } catch(RuntimeException e) {
-                        log(headerField + "is not Long-Integer header field!");
-                        return null;
-                    }
-                    break;
-                }
-
-                /* Integer-Value */
-                case PduHeaders.MESSAGE_COUNT:
-                case PduHeaders.START:
-                case PduHeaders.LIMIT:
-                {
-                    try {
-                        long value = parseIntegerValue(pduDataStream);
-                        if (LOCAL_LOGV) {
-                            Log.v(LOG_TAG, "parseHeaders: int: " + headerField + " value: " +
-                                    value);
-                        }
-                        headers.setLongInteger(value, headerField);
-                    } catch(RuntimeException e) {
-                        log(headerField + "is not Long-Integer header field!");
-                        return null;
-                    }
-                    break;
-                }
-
-                /* Text-String */
-                case PduHeaders.TRANSACTION_ID:
-                case PduHeaders.REPLY_CHARGING_ID:
-                case PduHeaders.AUX_APPLIC_ID:
-                case PduHeaders.APPLIC_ID:
-                case PduHeaders.REPLY_APPLIC_ID:
-                    /**
-                     * The next three header fields are email addresses
-                     * as defined in RFC2822,
-                     * not including the characters "<" and ">"
-                     */
-                case PduHeaders.MESSAGE_ID:
-                case PduHeaders.REPLACE_ID:
-                case PduHeaders.CANCEL_ID:
-                    /**
-                     * The following field has a different value when
-                     * used in the M-Mbox-Delete.conf and M-Delete.conf PDU.
-                     * For now we ignore this fact, since we do not support these PDUs
-                     */
-                case PduHeaders.CONTENT_LOCATION:
-                {
-                    byte[] value = parseWapString(pduDataStream, TYPE_TEXT_STRING);
-                    if (null != value) {
-                        try {
-                            if (LOCAL_LOGV) {
-                                Log.v(LOG_TAG, "parseHeaders: string: " + headerField + " value: " +
-                                        new String(value));
-                            }
-                            headers.setTextString(value, headerField);
-                        } catch(NullPointerException e) {
-                            log("null pointer error!");
-                        } catch(RuntimeException e) {
-                            log(headerField + "is not Text-String header field!");
-                            return null;
-                        }
-                    }
-                    break;
-                }
-
-                /* Encoded-string-value */
-                case PduHeaders.SUBJECT:
-                case PduHeaders.RECOMMENDED_RETRIEVAL_MODE_TEXT:
-                case PduHeaders.RETRIEVE_TEXT:
-                case PduHeaders.STATUS_TEXT:
-                case PduHeaders.STORE_STATUS_TEXT:
-                    /* the next one is not support
-                     * M-Mbox-Delete.conf and M-Delete.conf now */
-                case PduHeaders.RESPONSE_TEXT:
-                {
-                    EncodedStringValue value =
-                        parseEncodedStringValue(pduDataStream);
-                    if (null != value) {
-                        try {
-                            if (LOCAL_LOGV) {
-                                Log.v(LOG_TAG, "parseHeaders: encoded string: " + headerField
-                                        + " value: " + value.getString());
-                            }
-                            headers.setEncodedStringValue(value, headerField);
-                        } catch(NullPointerException e) {
-                            log("null pointer error!");
-                        } catch (RuntimeException e) {
-                            log(headerField + "is not Encoded-String-Value header field!");
-                            return null;
-                        }
-                    }
-                    break;
-                }
-
-                /* Addressing model */
-                case PduHeaders.BCC:
-                case PduHeaders.CC:
-                case PduHeaders.TO:
-                {
-                    EncodedStringValue value =
-                        parseEncodedStringValue(pduDataStream);
-                    if (null != value) {
-                        byte[] address = value.getTextString();
-                        if (null != address) {
-                            String str = new String(address);
-                            if (LOCAL_LOGV) {
-                                Log.v(LOG_TAG, "parseHeaders: (to/cc/bcc) address: " + headerField
-                                        + " value: " + str);
-                            }
-                            int endIndex = str.indexOf("/");
-                            if (endIndex > 0) {
-                                str = str.substring(0, endIndex);
-                            }
-                            try {
-                                value.setTextString(str.getBytes());
-                            } catch(NullPointerException e) {
-                                log("null pointer error!");
-                                return null;
-                            }
-                        }
-
-                        try {
-                            headers.appendEncodedStringValue(value, headerField);
-                        } catch(NullPointerException e) {
-                            log("null pointer error!");
-                        } catch(RuntimeException e) {
-                            log(headerField + "is not Encoded-String-Value header field!");
-                            return null;
-                        }
-                    }
-                    break;
-                }
-
-                /* Value-length
-                 * (Absolute-token Date-value | Relative-token Delta-seconds-value) */
-                case PduHeaders.DELIVERY_TIME:
-                case PduHeaders.EXPIRY:
-                case PduHeaders.REPLY_CHARGING_DEADLINE:
-                {
-                    /* parse Value-length */
-                    parseValueLength(pduDataStream);
-
-                    /* Absolute-token or Relative-token */
-                    int token = extractByteValue(pduDataStream);
-
-                    /* Date-value or Delta-seconds-value */
-                    long timeValue;
-                    try {
-                        timeValue = parseLongInteger(pduDataStream);
-                    } catch(RuntimeException e) {
-                        log(headerField + "is not Long-Integer header field!");
-                        return null;
-                    }
-                    if (PduHeaders.VALUE_RELATIVE_TOKEN == token) {
-                        /* need to convert the Delta-seconds-value
-                         * into Date-value */
-                        timeValue = System.currentTimeMillis()/1000 + timeValue;
-                    }
-
-                    try {
-                        if (LOCAL_LOGV) {
-                            Log.v(LOG_TAG, "parseHeaders: time value: " + headerField
-                                    + " value: " + timeValue);
-                        }
-                        headers.setLongInteger(timeValue, headerField);
-                    } catch(RuntimeException e) {
-                        log(headerField + "is not Long-Integer header field!");
-                        return null;
-                    }
-                    break;
-                }
-
-                case PduHeaders.FROM: {
-                    /* From-value =
-                     * Value-length
-                     * (Address-present-token Encoded-string-value | Insert-address-token)
-                     */
-                    EncodedStringValue from = null;
-                    parseValueLength(pduDataStream); /* parse value-length */
-
-                    /* Address-present-token or Insert-address-token */
-                    int fromToken = extractByteValue(pduDataStream);
-
-                    /* Address-present-token or Insert-address-token */
-                    if (PduHeaders.FROM_ADDRESS_PRESENT_TOKEN == fromToken) {
-                        /* Encoded-string-value */
-                        from = parseEncodedStringValue(pduDataStream);
-                        if (null != from) {
-                            byte[] address = from.getTextString();
-                            if (null != address) {
-                                String str = new String(address);
-                                int endIndex = str.indexOf("/");
-                                if (endIndex > 0) {
-                                    str = str.substring(0, endIndex);
-                                }
-                                try {
-                                    from.setTextString(str.getBytes());
-                                } catch(NullPointerException e) {
-                                    log("null pointer error!");
-                                    return null;
-                                }
-                            }
-                        }
-                    } else {
-                        try {
-                            from = new EncodedStringValue(
-                                    PduHeaders.FROM_INSERT_ADDRESS_TOKEN_STR.getBytes());
-                        } catch(NullPointerException e) {
-                            log(headerField + "is not Encoded-String-Value header field!");
-                            return null;
-                        }
-                    }
-
-                    try {
-                        if (LOCAL_LOGV) {
-                            Log.v(LOG_TAG, "parseHeaders: from address: " + headerField
-                                    + " value: " + from.getString());
-                        }
-                        headers.setEncodedStringValue(from, PduHeaders.FROM);
-                    } catch(NullPointerException e) {
-                        log("null pointer error!");
-                    } catch(RuntimeException e) {
-                        log(headerField + "is not Encoded-String-Value header field!");
-                        return null;
-                    }
-                    break;
-                }
-
-                case PduHeaders.MESSAGE_CLASS: {
-                    /* Message-class-value = Class-identifier | Token-text */
-                    pduDataStream.mark(1);
-                    int messageClass = extractByteValue(pduDataStream);
-                    if (LOCAL_LOGV) {
-                        Log.v(LOG_TAG, "parseHeaders: MESSAGE_CLASS: " + headerField
-                                + " value: " + messageClass);
-                    }
-
-                    if (messageClass >= PduHeaders.MESSAGE_CLASS_PERSONAL) {
-                        /* Class-identifier */
-                        try {
-                            if (PduHeaders.MESSAGE_CLASS_PERSONAL == messageClass) {
-                                headers.setTextString(
-                                        PduHeaders.MESSAGE_CLASS_PERSONAL_STR.getBytes(),
-                                        PduHeaders.MESSAGE_CLASS);
-                            } else if (PduHeaders.MESSAGE_CLASS_ADVERTISEMENT == messageClass) {
-                                headers.setTextString(
-                                        PduHeaders.MESSAGE_CLASS_ADVERTISEMENT_STR.getBytes(),
-                                        PduHeaders.MESSAGE_CLASS);
-                            } else if (PduHeaders.MESSAGE_CLASS_INFORMATIONAL == messageClass) {
-                                headers.setTextString(
-                                        PduHeaders.MESSAGE_CLASS_INFORMATIONAL_STR.getBytes(),
-                                        PduHeaders.MESSAGE_CLASS);
-                            } else if (PduHeaders.MESSAGE_CLASS_AUTO == messageClass) {
-                                headers.setTextString(
-                                        PduHeaders.MESSAGE_CLASS_AUTO_STR.getBytes(),
-                                        PduHeaders.MESSAGE_CLASS);
-                            }
-                        } catch(NullPointerException e) {
-                            log("null pointer error!");
-                        } catch(RuntimeException e) {
-                            log(headerField + "is not Text-String header field!");
-                            return null;
-                        }
-                    } else {
-                        /* Token-text */
-                        pduDataStream.reset();
-                        byte[] messageClassString = parseWapString(pduDataStream, TYPE_TEXT_STRING);
-                        if (null != messageClassString) {
-                            try {
-                                headers.setTextString(messageClassString, PduHeaders.MESSAGE_CLASS);
-                            } catch(NullPointerException e) {
-                                log("null pointer error!");
-                            } catch(RuntimeException e) {
-                                log(headerField + "is not Text-String header field!");
-                                return null;
-                            }
-                        }
-                    }
-                    break;
-                }
-
-                case PduHeaders.MMS_VERSION: {
-                    int version = parseShortInteger(pduDataStream);
-
-                    try {
-                        if (LOCAL_LOGV) {
-                            Log.v(LOG_TAG, "parseHeaders: MMS_VERSION: " + headerField
-                                    + " value: " + version);
-                        }
-                        headers.setOctet(version, PduHeaders.MMS_VERSION);
-                    } catch(InvalidHeaderValueException e) {
-                        log("Set invalid Octet value: " + version +
-                                " into the header filed: " + headerField);
-                        return null;
-                    } catch(RuntimeException e) {
-                        log(headerField + "is not Octet header field!");
-                        return null;
-                    }
-                    break;
-                }
-
-                case PduHeaders.PREVIOUSLY_SENT_BY: {
-                    /* Previously-sent-by-value =
-                     * Value-length Forwarded-count-value Encoded-string-value */
-                    /* parse value-length */
-                    parseValueLength(pduDataStream);
-
-                    /* parse Forwarded-count-value */
-                    try {
-                        parseIntegerValue(pduDataStream);
-                    } catch(RuntimeException e) {
-                        log(headerField + " is not Integer-Value");
-                        return null;
-                    }
-
-                    /* parse Encoded-string-value */
-                    EncodedStringValue previouslySentBy =
-                        parseEncodedStringValue(pduDataStream);
-                    if (null != previouslySentBy) {
-                        try {
-                            if (LOCAL_LOGV) {
-                                Log.v(LOG_TAG, "parseHeaders: PREVIOUSLY_SENT_BY: " + headerField
-                                        + " value: " + previouslySentBy.getString());
-                            }
-                            headers.setEncodedStringValue(previouslySentBy,
-                                    PduHeaders.PREVIOUSLY_SENT_BY);
-                        } catch(NullPointerException e) {
-                            log("null pointer error!");
-                        } catch(RuntimeException e) {
-                            log(headerField + "is not Encoded-String-Value header field!");
-                            return null;
-                        }
-                    }
-                    break;
-                }
-
-                case PduHeaders.PREVIOUSLY_SENT_DATE: {
-                    /* Previously-sent-date-value =
-                     * Value-length Forwarded-count-value Date-value */
-                    /* parse value-length */
-                    parseValueLength(pduDataStream);
-
-                    /* parse Forwarded-count-value */
-                    try {
-                        parseIntegerValue(pduDataStream);
-                    } catch(RuntimeException e) {
-                        log(headerField + " is not Integer-Value");
-                        return null;
-                    }
-
-                    /* Date-value */
-                    try {
-                        long perviouslySentDate = parseLongInteger(pduDataStream);
-                        if (LOCAL_LOGV) {
-                            Log.v(LOG_TAG, "parseHeaders: PREVIOUSLY_SENT_DATE: " + headerField
-                                    + " value: " + perviouslySentDate);
-                        }
-                        headers.setLongInteger(perviouslySentDate,
-                                PduHeaders.PREVIOUSLY_SENT_DATE);
-                    } catch(RuntimeException e) {
-                        log(headerField + "is not Long-Integer header field!");
-                        return null;
-                    }
-                    break;
-                }
-
-                case PduHeaders.MM_FLAGS: {
-                    /* MM-flags-value =
-                     * Value-length
-                     * ( Add-token | Remove-token | Filter-token )
-                     * Encoded-string-value
-                     */
-                    if (LOCAL_LOGV) {
-                        Log.v(LOG_TAG, "parseHeaders: MM_FLAGS: " + headerField
-                                + " NOT REALLY SUPPORTED");
-                    }
-
-                    /* parse Value-length */
-                    parseValueLength(pduDataStream);
-
-                    /* Add-token | Remove-token | Filter-token */
-                    extractByteValue(pduDataStream);
-
-                    /* Encoded-string-value */
-                    parseEncodedStringValue(pduDataStream);
-
-                    /* not store this header filed in "headers",
-                     * because now PduHeaders doesn't support it */
-                    break;
-                }
-
-                /* Value-length
-                 * (Message-total-token | Size-total-token) Integer-Value */
-                case PduHeaders.MBOX_TOTALS:
-                case PduHeaders.MBOX_QUOTAS:
-                {
-                    if (LOCAL_LOGV) {
-                        Log.v(LOG_TAG, "parseHeaders: MBOX_TOTALS: " + headerField);
-                    }
-                    /* Value-length */
-                    parseValueLength(pduDataStream);
-
-                    /* Message-total-token | Size-total-token */
-                    extractByteValue(pduDataStream);
-
-                    /*Integer-Value*/
-                    try {
-                        parseIntegerValue(pduDataStream);
-                    } catch(RuntimeException e) {
-                        log(headerField + " is not Integer-Value");
-                        return null;
-                    }
-
-                    /* not store these headers filed in "headers",
-                    because now PduHeaders doesn't support them */
-                    break;
-                }
-
-                case PduHeaders.ELEMENT_DESCRIPTOR: {
-                    if (LOCAL_LOGV) {
-                        Log.v(LOG_TAG, "parseHeaders: ELEMENT_DESCRIPTOR: " + headerField);
-                    }
-                    parseContentType(pduDataStream, null);
-
-                    /* not store this header filed in "headers",
-                    because now PduHeaders doesn't support it */
-                    break;
-                }
-
-                case PduHeaders.CONTENT_TYPE: {
-                    HashMap<Integer, Object> map =
-                        new HashMap<Integer, Object>();
-                    byte[] contentType =
-                        parseContentType(pduDataStream, map);
-
-                    if (null != contentType) {
-                        try {
-                            if (LOCAL_LOGV) {
-                                Log.v(LOG_TAG, "parseHeaders: CONTENT_TYPE: " + headerField +
-                                        contentType.toString());
-                            }
-                            headers.setTextString(contentType, PduHeaders.CONTENT_TYPE);
-                        } catch(NullPointerException e) {
-                            log("null pointer error!");
-                        } catch(RuntimeException e) {
-                            log(headerField + "is not Text-String header field!");
-                            return null;
-                        }
-                    }
-
-                    /* get start parameter */
-                    mStartParam = (byte[]) map.get(PduPart.P_START);
-
-                    /* get charset parameter */
-                    mTypeParam= (byte[]) map.get(PduPart.P_TYPE);
-
-                    keepParsing = false;
-                    break;
-                }
-
-                case PduHeaders.CONTENT:
-                case PduHeaders.ADDITIONAL_HEADERS:
-                case PduHeaders.ATTRIBUTES:
-                default: {
-                    if (LOCAL_LOGV) {
-                        Log.v(LOG_TAG, "parseHeaders: Unknown header: " + headerField);
-                    }
-                    log("Unknown header");
-                }
-            }
-        }
-
-        return headers;
-    }
-
-    /**
-     * Parse pdu parts.
-     *
-     * @param pduDataStream pdu data input stream
-     * @return parts in PduBody structure
-     */
-    protected PduBody parseParts(ByteArrayInputStream pduDataStream) {
-        if (pduDataStream == null) {
-            return null;
-        }
-
-        int count = parseUnsignedInt(pduDataStream); // get the number of parts
-        PduBody body = new PduBody();
-
-        for (int i = 0 ; i < count ; i++) {
-            int headerLength = parseUnsignedInt(pduDataStream);
-            int dataLength = parseUnsignedInt(pduDataStream);
-            PduPart part = new PduPart();
-            int startPos = pduDataStream.available();
-            if (startPos <= 0) {
-                // Invalid part.
-                return null;
-            }
-
-            /* parse part's content-type */
-            HashMap<Integer, Object> map = new HashMap<Integer, Object>();
-            byte[] contentType = parseContentType(pduDataStream, map);
-            if (null != contentType) {
-                part.setContentType(contentType);
-            } else {
-                part.setContentType((PduContentTypes.contentTypes[0]).getBytes()); //"*/*"
-            }
-
-            /* get name parameter */
-            byte[] name = (byte[]) map.get(PduPart.P_NAME);
-            if (null != name) {
-                part.setName(name);
-            }
-
-            /* get charset parameter */
-            Integer charset = (Integer) map.get(PduPart.P_CHARSET);
-            if (null != charset) {
-                part.setCharset(charset);
-            }
-
-            /* parse part's headers */
-            int endPos = pduDataStream.available();
-            int partHeaderLen = headerLength - (startPos - endPos);
-            if (partHeaderLen > 0) {
-                if (false == parsePartHeaders(pduDataStream, part, partHeaderLen)) {
-                    // Parse part header faild.
-                    return null;
-                }
-            } else if (partHeaderLen < 0) {
-                // Invalid length of content-type.
-                return null;
-            }
-
-            /* FIXME: check content-id, name, filename and content location,
-             * if not set anyone of them, generate a default content-location
-             */
-            if ((null == part.getContentLocation())
-                    && (null == part.getName())
-                    && (null == part.getFilename())
-                    && (null == part.getContentId())) {
-                part.setContentLocation(Long.toOctalString(
-                        System.currentTimeMillis()).getBytes());
-            }
-
-            /* get part's data */
-            if (dataLength > 0) {
-                byte[] partData = new byte[dataLength];
-                String partContentType = new String(part.getContentType());
-                pduDataStream.read(partData, 0, dataLength);
-                if (partContentType.equalsIgnoreCase(ContentType.MULTIPART_ALTERNATIVE)) {
-                    // parse "multipart/vnd.wap.multipart.alternative".
-                    PduBody childBody = parseParts(new ByteArrayInputStream(partData));
-                    // take the first part of children.
-                    part = childBody.getPart(0);
-                } else {
-                    // Check Content-Transfer-Encoding.
-                    byte[] partDataEncoding = part.getContentTransferEncoding();
-                    if (null != partDataEncoding) {
-                        String encoding = new String(partDataEncoding);
-                        if (encoding.equalsIgnoreCase(PduPart.P_BASE64)) {
-                            // Decode "base64" into "binary".
-                            partData = Base64.decodeBase64(partData);
-                        } else if (encoding.equalsIgnoreCase(PduPart.P_QUOTED_PRINTABLE)) {
-                            // Decode "quoted-printable" into "binary".
-                            partData = QuotedPrintable.decodeQuotedPrintable(partData);
-                        } else {
-                            // "binary" is the default encoding.
-                        }
-                    }
-                    if (null == partData) {
-                        log("Decode part data error!");
-                        return null;
-                    }
-                    part.setData(partData);
-                }
-            }
-
-            /* add this part to body */
-            if (THE_FIRST_PART == checkPartPosition(part)) {
-                /* this is the first part */
-                body.addPart(0, part);
-            } else {
-                /* add the part to the end */
-                body.addPart(part);
-            }
-        }
-
-        return body;
-    }
-
-    /**
-     * Log status.
-     *
-     * @param text log information
-     */
-    private static void log(String text) {
-        if (LOCAL_LOGV) {
-            Log.v(LOG_TAG, text);
-        }
-    }
-
-    /**
-     * Parse unsigned integer.
-     *
-     * @param pduDataStream pdu data input stream
-     * @return the integer, -1 when failed
-     */
-    protected static int parseUnsignedInt(ByteArrayInputStream pduDataStream) {
-        /**
-         * From wap-230-wsp-20010705-a.pdf
-         * The maximum size of a uintvar is 32 bits.
-         * So it will be encoded in no more than 5 octets.
-         */
-        assert(null != pduDataStream);
-        int result = 0;
-        int temp = pduDataStream.read();
-        if (temp == -1) {
-            return temp;
-        }
-
-        while((temp & 0x80) != 0) {
-            result = result << 7;
-            result |= temp & 0x7F;
-            temp = pduDataStream.read();
-            if (temp == -1) {
-                return temp;
-            }
-        }
-
-        result = result << 7;
-        result |= temp & 0x7F;
-
-        return result;
-    }
-
-    /**
-     * Parse value length.
-     *
-     * @param pduDataStream pdu data input stream
-     * @return the integer
-     */
-    protected static int parseValueLength(ByteArrayInputStream pduDataStream) {
-        /**
-         * From wap-230-wsp-20010705-a.pdf
-         * Value-length = Short-length | (Length-quote Length)
-         * Short-length = <Any octet 0-30>
-         * Length-quote = <Octet 31>
-         * Length = Uintvar-integer
-         * Uintvar-integer = 1*5 OCTET
-         */
-        assert(null != pduDataStream);
-        int temp = pduDataStream.read();
-        assert(-1 != temp);
-        int first = temp & 0xFF;
-
-        if (first <= SHORT_LENGTH_MAX) {
-            return first;
-        } else if (first == LENGTH_QUOTE) {
-            return parseUnsignedInt(pduDataStream);
-        }
-
-        throw new RuntimeException ("Value length > LENGTH_QUOTE!");
-    }
-
-    /**
-     * Parse encoded string value.
-     *
-     * @param pduDataStream pdu data input stream
-     * @return the EncodedStringValue
-     */
-    protected static EncodedStringValue parseEncodedStringValue(ByteArrayInputStream pduDataStream){
-        /**
-         * From OMA-TS-MMS-ENC-V1_3-20050927-C.pdf
-         * Encoded-string-value = Text-string | Value-length Char-set Text-string
-         */
-        assert(null != pduDataStream);
-        pduDataStream.mark(1);
-        EncodedStringValue returnValue = null;
-        int charset = 0;
-        int temp = pduDataStream.read();
-        assert(-1 != temp);
-        int first = temp & 0xFF;
-        if (first == 0) {
-            return new EncodedStringValue("");
-        }
-
-        pduDataStream.reset();
-        if (first < TEXT_MIN) {
-            parseValueLength(pduDataStream);
-
-            charset = parseShortInteger(pduDataStream); //get the "Charset"
-        }
-
-        byte[] textString = parseWapString(pduDataStream, TYPE_TEXT_STRING);
-
-        try {
-            if (0 != charset) {
-                returnValue = new EncodedStringValue(charset, textString);
-            } else {
-                returnValue = new EncodedStringValue(textString);
-            }
-        } catch(Exception e) {
-            return null;
-        }
-
-        return returnValue;
-    }
-
-    /**
-     * Parse Text-String or Quoted-String.
-     *
-     * @param pduDataStream pdu data input stream
-     * @param stringType TYPE_TEXT_STRING or TYPE_QUOTED_STRING
-     * @return the string without End-of-string in byte array
-     */
-    protected static byte[] parseWapString(ByteArrayInputStream pduDataStream,
-            int stringType) {
-        assert(null != pduDataStream);
-        /**
-         * From wap-230-wsp-20010705-a.pdf
-         * Text-string = [Quote] *TEXT End-of-string
-         * If the first character in the TEXT is in the range of 128-255,
-         * a Quote character must precede it.
-         * Otherwise the Quote character must be omitted.
-         * The Quote is not part of the contents.
-         * Quote = <Octet 127>
-         * End-of-string = <Octet 0>
-         *
-         * Quoted-string = <Octet 34> *TEXT End-of-string
-         *
-         * Token-text = Token End-of-string
-         */
-
-        // Mark supposed beginning of Text-string
-        // We will have to mark again if first char is QUOTE or QUOTED_STRING_FLAG
-        pduDataStream.mark(1);
-
-        // Check first char
-        int temp = pduDataStream.read();
-        assert(-1 != temp);
-        if ((TYPE_QUOTED_STRING == stringType) &&
-                (QUOTED_STRING_FLAG == temp)) {
-            // Mark again if QUOTED_STRING_FLAG and ignore it
-            pduDataStream.mark(1);
-        } else if ((TYPE_TEXT_STRING == stringType) &&
-                (QUOTE == temp)) {
-            // Mark again if QUOTE and ignore it
-            pduDataStream.mark(1);
-        } else {
-            // Otherwise go back to origin
-            pduDataStream.reset();
-        }
-
-        // We are now definitely at the beginning of string
-        /**
-         * Return *TOKEN or *TEXT (Text-String without QUOTE,
-         * Quoted-String without QUOTED_STRING_FLAG and without End-of-string)
-         */
-        return getWapString(pduDataStream, stringType);
-    }
-
-    /**
-     * Check TOKEN data defined in RFC2616.
-     * @param ch checking data
-     * @return true when ch is TOKEN, false when ch is not TOKEN
-     */
-    protected static boolean isTokenCharacter(int ch) {
-        /**
-         * Token      = 1*<any CHAR except CTLs or separators>
-         * separators = "("(40) | ")"(41) | "<"(60) | ">"(62) | "@"(64)
-         *            | ","(44) | ";"(59) | ":"(58) | "\"(92) | <">(34)
-         *            | "/"(47) | "["(91) | "]"(93) | "?"(63) | "="(61)
-         *            | "{"(123) | "}"(125) | SP(32) | HT(9)
-         * CHAR       = <any US-ASCII character (octets 0 - 127)>
-         * CTL        = <any US-ASCII control character
-         *            (octets 0 - 31) and DEL (127)>
-         * SP         = <US-ASCII SP, space (32)>
-         * HT         = <US-ASCII HT, horizontal-tab (9)>
-         */
-        if((ch < 33) || (ch > 126)) {
-            return false;
-        }
-
-        switch(ch) {
-            case '"': /* '"' */
-            case '(': /* '(' */
-            case ')': /* ')' */
-            case ',': /* ',' */
-            case '/': /* '/' */
-            case ':': /* ':' */
-            case ';': /* ';' */
-            case '<': /* '<' */
-            case '=': /* '=' */
-            case '>': /* '>' */
-            case '?': /* '?' */
-            case '@': /* '@' */
-            case '[': /* '[' */
-            case '\\': /* '\' */
-            case ']': /* ']' */
-            case '{': /* '{' */
-            case '}': /* '}' */
-                return false;
-        }
-
-        return true;
-    }
-
-    /**
-     * Check TEXT data defined in RFC2616.
-     * @param ch checking data
-     * @return true when ch is TEXT, false when ch is not TEXT
-     */
-    protected static boolean isText(int ch) {
-        /**
-         * TEXT = <any OCTET except CTLs,
-         *      but including LWS>
-         * CTL  = <any US-ASCII control character
-         *      (octets 0 - 31) and DEL (127)>
-         * LWS  = [CRLF] 1*( SP | HT )
-         * CRLF = CR LF
-         * CR   = <US-ASCII CR, carriage return (13)>
-         * LF   = <US-ASCII LF, linefeed (10)>
-         */
-        if(((ch >= 32) && (ch <= 126)) || ((ch >= 128) && (ch <= 255))) {
-            return true;
-        }
-
-        switch(ch) {
-            case '\t': /* '\t' */
-            case '\n': /* '\n' */
-            case '\r': /* '\r' */
-                return true;
-        }
-
-        return false;
-    }
-
-    protected static byte[] getWapString(ByteArrayInputStream pduDataStream,
-            int stringType) {
-        assert(null != pduDataStream);
-        ByteArrayOutputStream out = new ByteArrayOutputStream();
-        int temp = pduDataStream.read();
-        assert(-1 != temp);
-        while((-1 != temp) && ('\0' != temp)) {
-            // check each of the character
-            if (stringType == TYPE_TOKEN_STRING) {
-                if (isTokenCharacter(temp)) {
-                    out.write(temp);
-                }
-            } else {
-                if (isText(temp)) {
-                    out.write(temp);
-                }
-            }
-
-            temp = pduDataStream.read();
-            assert(-1 != temp);
-        }
-
-        if (out.size() > 0) {
-            return out.toByteArray();
-        }
-
-        return null;
-    }
-
-    /**
-     * Extract a byte value from the input stream.
-     *
-     * @param pduDataStream pdu data input stream
-     * @return the byte
-     */
-    protected static int extractByteValue(ByteArrayInputStream pduDataStream) {
-        assert(null != pduDataStream);
-        int temp = pduDataStream.read();
-        assert(-1 != temp);
-        return temp & 0xFF;
-    }
-
-    /**
-     * Parse Short-Integer.
-     *
-     * @param pduDataStream pdu data input stream
-     * @return the byte
-     */
-    protected static int parseShortInteger(ByteArrayInputStream pduDataStream) {
-        /**
-         * From wap-230-wsp-20010705-a.pdf
-         * Short-integer = OCTET
-         * Integers in range 0-127 shall be encoded as a one
-         * octet value with the most significant bit set to one (1xxx xxxx)
-         * and with the value in the remaining least significant bits.
-         */
-        assert(null != pduDataStream);
-        int temp = pduDataStream.read();
-        assert(-1 != temp);
-        return temp & 0x7F;
-    }
-
-    /**
-     * Parse Long-Integer.
-     *
-     * @param pduDataStream pdu data input stream
-     * @return long integer
-     */
-    protected static long parseLongInteger(ByteArrayInputStream pduDataStream) {
-        /**
-         * From wap-230-wsp-20010705-a.pdf
-         * Long-integer = Short-length Multi-octet-integer
-         * The Short-length indicates the length of the Multi-octet-integer
-         * Multi-octet-integer = 1*30 OCTET
-         * The content octets shall be an unsigned integer value
-         * with the most significant octet encoded first (big-endian representation).
-         * The minimum number of octets must be used to encode the value.
-         * Short-length = <Any octet 0-30>
-         */
-        assert(null != pduDataStream);
-        int temp = pduDataStream.read();
-        assert(-1 != temp);
-        int count = temp & 0xFF;
-
-        if (count > LONG_INTEGER_LENGTH_MAX) {
-            throw new RuntimeException("Octet count greater than 8 and I can't represent that!");
-        }
-
-        long result = 0;
-
-        for (int i = 0 ; i < count ; i++) {
-            temp = pduDataStream.read();
-            assert(-1 != temp);
-            result <<= 8;
-            result += (temp & 0xFF);
-        }
-
-        return result;
-    }
-
-    /**
-     * Parse Integer-Value.
-     *
-     * @param pduDataStream pdu data input stream
-     * @return long integer
-     */
-    protected static long parseIntegerValue(ByteArrayInputStream pduDataStream) {
-        /**
-         * From wap-230-wsp-20010705-a.pdf
-         * Integer-Value = Short-integer | Long-integer
-         */
-        assert(null != pduDataStream);
-        pduDataStream.mark(1);
-        int temp = pduDataStream.read();
-        assert(-1 != temp);
-        pduDataStream.reset();
-        if (temp > SHORT_INTEGER_MAX) {
-            return parseShortInteger(pduDataStream);
-        } else {
-            return parseLongInteger(pduDataStream);
-        }
-    }
-
-    /**
-     * To skip length of the wap value.
-     *
-     * @param pduDataStream pdu data input stream
-     * @param length area size
-     * @return the values in this area
-     */
-    protected static int skipWapValue(ByteArrayInputStream pduDataStream, int length) {
-        assert(null != pduDataStream);
-        byte[] area = new byte[length];
-        int readLen = pduDataStream.read(area, 0, length);
-        if (readLen < length) { //The actually read length is lower than the length
-            return -1;
-        } else {
-            return readLen;
-        }
-    }
-
-    /**
-     * Parse content type parameters. For now we just support
-     * four parameters used in mms: "type", "start", "name", "charset".
-     *
-     * @param pduDataStream pdu data input stream
-     * @param map to store parameters of Content-Type field
-     * @param length length of all the parameters
-     */
-    protected static void parseContentTypeParams(ByteArrayInputStream pduDataStream,
-            HashMap<Integer, Object> map, Integer length) {
-        /**
-         * From wap-230-wsp-20010705-a.pdf
-         * Parameter = Typed-parameter | Untyped-parameter
-         * Typed-parameter = Well-known-parameter-token Typed-value
-         * the actual expected type of the value is implied by the well-known parameter
-         * Well-known-parameter-token = Integer-value
-         * the code values used for parameters are specified in the Assigned Numbers appendix
-         * Typed-value = Compact-value | Text-value
-         * In addition to the expected type, there may be no value.
-         * If the value cannot be encoded using the expected type, it shall be encoded as text.
-         * Compact-value = Integer-value |
-         * Date-value | Delta-seconds-value | Q-value | Version-value |
-         * Uri-value
-         * Untyped-parameter = Token-text Untyped-value
-         * the type of the value is unknown, but it shall be encoded as an integer,
-         * if that is possible.
-         * Untyped-value = Integer-value | Text-value
-         */
-        assert(null != pduDataStream);
-        assert(length > 0);
-
-        int startPos = pduDataStream.available();
-        int tempPos = 0;
-        int lastLen = length;
-        while(0 < lastLen) {
-            int param = pduDataStream.read();
-            assert(-1 != param);
-            lastLen--;
-
-            switch (param) {
-                /**
-                 * From rfc2387, chapter 3.1
-                 * The type parameter must be specified and its value is the MIME media
-                 * type of the "root" body part. It permits a MIME user agent to
-                 * determine the content-type without reference to the enclosed body
-                 * part. If the value of the type parameter and the root body part's
-                 * content-type differ then the User Agent's behavior is undefined.
-                 *
-                 * From wap-230-wsp-20010705-a.pdf
-                 * type = Constrained-encoding
-                 * Constrained-encoding = Extension-Media | Short-integer
-                 * Extension-media = *TEXT End-of-string
-                 */
-                case PduPart.P_TYPE:
-                case PduPart.P_CT_MR_TYPE:
-                    pduDataStream.mark(1);
-                    int first = extractByteValue(pduDataStream);
-                    pduDataStream.reset();
-                    if (first > TEXT_MAX) {
-                        // Short-integer (well-known type)
-                        int index = parseShortInteger(pduDataStream);
-
-                        if (index < PduContentTypes.contentTypes.length) {
-                            byte[] type = (PduContentTypes.contentTypes[index]).getBytes();
-                            map.put(PduPart.P_TYPE, type);
-                        } else {
-                            //not support this type, ignore it.
-                        }
-                    } else {
-                        // Text-String (extension-media)
-                        byte[] type = parseWapString(pduDataStream, TYPE_TEXT_STRING);
-                        if ((null != type) && (null != map)) {
-                            map.put(PduPart.P_TYPE, type);
-                        }
-                    }
-
-                    tempPos = pduDataStream.available();
-                    lastLen = length - (startPos - tempPos);
-                    break;
-
-                    /**
-                     * From oma-ts-mms-conf-v1_3.pdf, chapter 10.2.3.
-                     * Start Parameter Referring to Presentation
-                     *
-                     * From rfc2387, chapter 3.2
-                     * The start parameter, if given, is the content-ID of the compound
-                     * object's "root". If not present the "root" is the first body part in
-                     * the Multipart/Related entity. The "root" is the element the
-                     * applications processes first.
-                     *
-                     * From wap-230-wsp-20010705-a.pdf
-                     * start = Text-String
-                     */
-                case PduPart.P_START:
-                case PduPart.P_DEP_START:
-                    byte[] start = parseWapString(pduDataStream, TYPE_TEXT_STRING);
-                    if ((null != start) && (null != map)) {
-                        map.put(PduPart.P_START, start);
-                    }
-
-                    tempPos = pduDataStream.available();
-                    lastLen = length - (startPos - tempPos);
-                    break;
-
-                    /**
-                     * From oma-ts-mms-conf-v1_3.pdf
-                     * In creation, the character set SHALL be either us-ascii
-                     * (IANA MIBenum 3) or utf-8 (IANA MIBenum 106)[Unicode].
-                     * In retrieval, both us-ascii and utf-8 SHALL be supported.
-                     *
-                     * From wap-230-wsp-20010705-a.pdf
-                     * charset = Well-known-charset|Text-String
-                     * Well-known-charset = Any-charset | Integer-value
-                     * Both are encoded using values from Character Set
-                     * Assignments table in Assigned Numbers
-                     * Any-charset = <Octet 128>
-                     * Equivalent to the special RFC2616 charset value "*"
-                     */
-                case PduPart.P_CHARSET:
-                    pduDataStream.mark(1);
-                    int firstValue = extractByteValue(pduDataStream);
-                    pduDataStream.reset();
-                    //Check first char
-                    if (((firstValue > TEXT_MIN) && (firstValue < TEXT_MAX)) ||
-                            (END_STRING_FLAG == firstValue)) {
-                        //Text-String (extension-charset)
-                        byte[] charsetStr = parseWapString(pduDataStream, TYPE_TEXT_STRING);
-                        try {
-                            int charsetInt = CharacterSets.getMibEnumValue(
-                                    new String(charsetStr));
-                            map.put(PduPart.P_CHARSET, charsetInt);
-                        } catch (UnsupportedEncodingException e) {
-                            // Not a well-known charset, use "*".
-                            Log.e(LOG_TAG, Arrays.toString(charsetStr), e);
-                            map.put(PduPart.P_CHARSET, CharacterSets.ANY_CHARSET);
-                        }
-                    } else {
-                        //Well-known-charset
-                        int charset = (int) parseIntegerValue(pduDataStream);
-                        if (map != null) {
-                            map.put(PduPart.P_CHARSET, charset);
-                        }
-                    }
-
-                    tempPos = pduDataStream.available();
-                    lastLen = length - (startPos - tempPos);
-                    break;
-
-                    /**
-                     * From oma-ts-mms-conf-v1_3.pdf
-                     * A name for multipart object SHALL be encoded using name-parameter
-                     * for Content-Type header in WSP multipart headers.
-                     *
-                     * From wap-230-wsp-20010705-a.pdf
-                     * name = Text-String
-                     */
-                case PduPart.P_DEP_NAME:
-                case PduPart.P_NAME:
-                    byte[] name = parseWapString(pduDataStream, TYPE_TEXT_STRING);
-                    if ((null != name) && (null != map)) {
-                        map.put(PduPart.P_NAME, name);
-                    }
-
-                    tempPos = pduDataStream.available();
-                    lastLen = length - (startPos - tempPos);
-                    break;
-                default:
-                    if (LOCAL_LOGV) {
-                        Log.v(LOG_TAG, "Not supported Content-Type parameter");
-                    }
-                if (-1 == skipWapValue(pduDataStream, lastLen)) {
-                    Log.e(LOG_TAG, "Corrupt Content-Type");
-                } else {
-                    lastLen = 0;
-                }
-                break;
-            }
-        }
-
-        if (0 != lastLen) {
-            Log.e(LOG_TAG, "Corrupt Content-Type");
-        }
-    }
-
-    /**
-     * Parse content type.
-     *
-     * @param pduDataStream pdu data input stream
-     * @param map to store parameters in Content-Type header field
-     * @return Content-Type value
-     */
-    protected static byte[] parseContentType(ByteArrayInputStream pduDataStream,
-            HashMap<Integer, Object> map) {
-        /**
-         * From wap-230-wsp-20010705-a.pdf
-         * Content-type-value = Constrained-media | Content-general-form
-         * Content-general-form = Value-length Media-type
-         * Media-type = (Well-known-media | Extension-Media) *(Parameter)
-         */
-        assert(null != pduDataStream);
-
-        byte[] contentType = null;
-        pduDataStream.mark(1);
-        int temp = pduDataStream.read();
-        assert(-1 != temp);
-        pduDataStream.reset();
-
-        int cur = (temp & 0xFF);
-
-        if (cur < TEXT_MIN) {
-            int length = parseValueLength(pduDataStream);
-            int startPos = pduDataStream.available();
-            pduDataStream.mark(1);
-            temp = pduDataStream.read();
-            assert(-1 != temp);
-            pduDataStream.reset();
-            int first = (temp & 0xFF);
-
-            if ((first >= TEXT_MIN) && (first <= TEXT_MAX)) {
-                contentType = parseWapString(pduDataStream, TYPE_TEXT_STRING);
-            } else if (first > TEXT_MAX) {
-                int index = parseShortInteger(pduDataStream);
-
-                if (index < PduContentTypes.contentTypes.length) { //well-known type
-                    contentType = (PduContentTypes.contentTypes[index]).getBytes();
-                } else {
-                    pduDataStream.reset();
-                    contentType = parseWapString(pduDataStream, TYPE_TEXT_STRING);
-                }
-            } else {
-                Log.e(LOG_TAG, "Corrupt content-type");
-                return (PduContentTypes.contentTypes[0]).getBytes(); //"*/*"
-            }
-
-            int endPos = pduDataStream.available();
-            int parameterLen = length - (startPos - endPos);
-            if (parameterLen > 0) {//have parameters
-                parseContentTypeParams(pduDataStream, map, parameterLen);
-            }
-
-            if (parameterLen < 0) {
-                Log.e(LOG_TAG, "Corrupt MMS message");
-                return (PduContentTypes.contentTypes[0]).getBytes(); //"*/*"
-            }
-        } else if (cur <= TEXT_MAX) {
-            contentType = parseWapString(pduDataStream, TYPE_TEXT_STRING);
-        } else {
-            contentType =
-                (PduContentTypes.contentTypes[parseShortInteger(pduDataStream)]).getBytes();
-        }
-
-        return contentType;
-    }
-
-    /**
-     * Parse part's headers.
-     *
-     * @param pduDataStream pdu data input stream
-     * @param part to store the header informations of the part
-     * @param length length of the headers
-     * @return true if parse successfully, false otherwise
-     */
-    protected boolean parsePartHeaders(ByteArrayInputStream pduDataStream,
-            PduPart part, int length) {
-        assert(null != pduDataStream);
-        assert(null != part);
-        assert(length > 0);
-
-        /**
-         * From oma-ts-mms-conf-v1_3.pdf, chapter 10.2.
-         * A name for multipart object SHALL be encoded using name-parameter
-         * for Content-Type header in WSP multipart headers.
-         * In decoding, name-parameter of Content-Type SHALL be used if available.
-         * If name-parameter of Content-Type is not available,
-         * filename parameter of Content-Disposition header SHALL be used if available.
-         * If neither name-parameter of Content-Type header nor filename parameter
-         * of Content-Disposition header is available,
-         * Content-Location header SHALL be used if available.
-         *
-         * Within SMIL part the reference to the media object parts SHALL use
-         * either Content-ID or Content-Location mechanism [RFC2557]
-         * and the corresponding WSP part headers in media object parts
-         * contain the corresponding definitions.
-         */
-        int startPos = pduDataStream.available();
-        int tempPos = 0;
-        int lastLen = length;
-        while(0 < lastLen) {
-            int header = pduDataStream.read();
-            assert(-1 != header);
-            lastLen--;
-
-            if (header > TEXT_MAX) {
-                // Number assigned headers.
-                switch (header) {
-                    case PduPart.P_CONTENT_LOCATION:
-                        /**
-                         * From wap-230-wsp-20010705-a.pdf, chapter 8.4.2.21
-                         * Content-location-value = Uri-value
-                         */
-                        byte[] contentLocation = parseWapString(pduDataStream, TYPE_TEXT_STRING);
-                        if (null != contentLocation) {
-                            part.setContentLocation(contentLocation);
-                        }
-
-                        tempPos = pduDataStream.available();
-                        lastLen = length - (startPos - tempPos);
-                        break;
-                    case PduPart.P_CONTENT_ID:
-                        /**
-                         * From wap-230-wsp-20010705-a.pdf, chapter 8.4.2.21
-                         * Content-ID-value = Quoted-string
-                         */
-                        byte[] contentId = parseWapString(pduDataStream, TYPE_QUOTED_STRING);
-                        if (null != contentId) {
-                            part.setContentId(contentId);
-                        }
-
-                        tempPos = pduDataStream.available();
-                        lastLen = length - (startPos - tempPos);
-                        break;
-                    case PduPart.P_DEP_CONTENT_DISPOSITION:
-                    case PduPart.P_CONTENT_DISPOSITION:
-                        /**
-                         * From wap-230-wsp-20010705-a.pdf, chapter 8.4.2.21
-                         * Content-disposition-value = Value-length Disposition *(Parameter)
-                         * Disposition = Form-data | Attachment | Inline | Token-text
-                         * Form-data = <Octet 128>
-                         * Attachment = <Octet 129>
-                         * Inline = <Octet 130>
-                         */
-
-                        /*
-                         * some carrier mmsc servers do not support content_disposition
-                         * field correctly
-                         */
-                        if (mParseContentDisposition) {
-                            int len = parseValueLength(pduDataStream);
-                            pduDataStream.mark(1);
-                            int thisStartPos = pduDataStream.available();
-                            int thisEndPos = 0;
-                            int value = pduDataStream.read();
-
-                            if (value == PduPart.P_DISPOSITION_FROM_DATA ) {
-                                part.setContentDisposition(PduPart.DISPOSITION_FROM_DATA);
-                            } else if (value == PduPart.P_DISPOSITION_ATTACHMENT) {
-                                part.setContentDisposition(PduPart.DISPOSITION_ATTACHMENT);
-                            } else if (value == PduPart.P_DISPOSITION_INLINE) {
-                                part.setContentDisposition(PduPart.DISPOSITION_INLINE);
-                            } else {
-                                pduDataStream.reset();
-                                /* Token-text */
-                                part.setContentDisposition(parseWapString(pduDataStream
-                                        , TYPE_TEXT_STRING));
-                            }
-
-                            /* get filename parameter and skip other parameters */
-                            thisEndPos = pduDataStream.available();
-                            if (thisStartPos - thisEndPos < len) {
-                                value = pduDataStream.read();
-                                if (value == PduPart.P_FILENAME) { //filename is text-string
-                                    part.setFilename(parseWapString(pduDataStream
-                                            , TYPE_TEXT_STRING));
-                                }
-
-                                /* skip other parameters */
-                                thisEndPos = pduDataStream.available();
-                                if (thisStartPos - thisEndPos < len) {
-                                    int last = len - (thisStartPos - thisEndPos);
-                                    byte[] temp = new byte[last];
-                                    pduDataStream.read(temp, 0, last);
-                                }
-                            }
-
-                            tempPos = pduDataStream.available();
-                            lastLen = length - (startPos - tempPos);
-                        }
-                        break;
-                    default:
-                        if (LOCAL_LOGV) {
-                            Log.v(LOG_TAG, "Not supported Part headers: " + header);
-                        }
-                    if (-1 == skipWapValue(pduDataStream, lastLen)) {
-                        Log.e(LOG_TAG, "Corrupt Part headers");
-                        return false;
-                    }
-                    lastLen = 0;
-                    break;
-                }
-            } else if ((header >= TEXT_MIN) && (header <= TEXT_MAX)) {
-                // Not assigned header.
-                byte[] tempHeader = parseWapString(pduDataStream, TYPE_TEXT_STRING);
-                byte[] tempValue = parseWapString(pduDataStream, TYPE_TEXT_STRING);
-
-                // Check the header whether it is "Content-Transfer-Encoding".
-                if (true ==
-                    PduPart.CONTENT_TRANSFER_ENCODING.equalsIgnoreCase(new String(tempHeader))) {
-                    part.setContentTransferEncoding(tempValue);
-                }
-
-                tempPos = pduDataStream.available();
-                lastLen = length - (startPos - tempPos);
-            } else {
-                if (LOCAL_LOGV) {
-                    Log.v(LOG_TAG, "Not supported Part headers: " + header);
-                }
-                // Skip all headers of this part.
-                if (-1 == skipWapValue(pduDataStream, lastLen)) {
-                    Log.e(LOG_TAG, "Corrupt Part headers");
-                    return false;
-                }
-                lastLen = 0;
-            }
-        }
-
-        if (0 != lastLen) {
-            Log.e(LOG_TAG, "Corrupt Part headers");
-            return false;
-        }
-
-        return true;
-    }
-
-    /**
-     * Check the position of a specified part.
-     *
-     * @param part the part to be checked
-     * @return part position, THE_FIRST_PART when it's the
-     * first one, THE_LAST_PART when it's the last one.
-     */
-    private static int checkPartPosition(PduPart part) {
-        assert(null != part);
-        if ((null == mTypeParam) &&
-                (null == mStartParam)) {
-            return THE_LAST_PART;
-        }
-
-        /* check part's content-id */
-        if (null != mStartParam) {
-            byte[] contentId = part.getContentId();
-            if (null != contentId) {
-                if (true == Arrays.equals(mStartParam, contentId)) {
-                    return THE_FIRST_PART;
-                }
-            }
-            // This is not the first part, so append to end (keeping the original order)
-            // Check b/19607294 for details of this change
-            return THE_LAST_PART;
-        }
-
-        /* check part's content-type */
-        if (null != mTypeParam) {
-            byte[] contentType = part.getContentType();
-            if (null != contentType) {
-                if (true == Arrays.equals(mTypeParam, contentType)) {
-                    return THE_FIRST_PART;
-                }
-            }
-        }
-
-        return THE_LAST_PART;
-    }
-
-    /**
-     * Check mandatory headers of a pdu.
-     *
-     * @param headers pdu headers
-     * @return true if the pdu has all of the mandatory headers, false otherwise.
-     */
-    protected static boolean checkMandatoryHeader(PduHeaders headers) {
-        if (null == headers) {
-            return false;
-        }
-
-        /* get message type */
-        int messageType = headers.getOctet(PduHeaders.MESSAGE_TYPE);
-
-        /* check Mms-Version field */
-        int mmsVersion = headers.getOctet(PduHeaders.MMS_VERSION);
-        if (0 == mmsVersion) {
-            // Every message should have Mms-Version field.
-            return false;
-        }
-
-        /* check mandatory header fields */
-        switch (messageType) {
-            case PduHeaders.MESSAGE_TYPE_SEND_REQ:
-                // Content-Type field.
-                byte[] srContentType = headers.getTextString(PduHeaders.CONTENT_TYPE);
-                if (null == srContentType) {
-                    return false;
-                }
-
-                // From field.
-                EncodedStringValue srFrom = headers.getEncodedStringValue(PduHeaders.FROM);
-                if (null == srFrom) {
-                    return false;
-                }
-
-                // Transaction-Id field.
-                byte[] srTransactionId = headers.getTextString(PduHeaders.TRANSACTION_ID);
-                if (null == srTransactionId) {
-                    return false;
-                }
-
-                break;
-            case PduHeaders.MESSAGE_TYPE_SEND_CONF:
-                // Response-Status field.
-                int scResponseStatus = headers.getOctet(PduHeaders.RESPONSE_STATUS);
-                if (0 == scResponseStatus) {
-                    return false;
-                }
-
-                // Transaction-Id field.
-                byte[] scTransactionId = headers.getTextString(PduHeaders.TRANSACTION_ID);
-                if (null == scTransactionId) {
-                    return false;
-                }
-
-                break;
-            case PduHeaders.MESSAGE_TYPE_NOTIFICATION_IND:
-                // Content-Location field.
-                byte[] niContentLocation = headers.getTextString(PduHeaders.CONTENT_LOCATION);
-                if (null == niContentLocation) {
-                    return false;
-                }
-
-                // Expiry field.
-                long niExpiry = headers.getLongInteger(PduHeaders.EXPIRY);
-                if (-1 == niExpiry) {
-                    return false;
-                }
-
-                // Message-Class field.
-                byte[] niMessageClass = headers.getTextString(PduHeaders.MESSAGE_CLASS);
-                if (null == niMessageClass) {
-                    return false;
-                }
-
-                // Message-Size field.
-                long niMessageSize = headers.getLongInteger(PduHeaders.MESSAGE_SIZE);
-                if (-1 == niMessageSize) {
-                    return false;
-                }
-
-                // Transaction-Id field.
-                byte[] niTransactionId = headers.getTextString(PduHeaders.TRANSACTION_ID);
-                if (null == niTransactionId) {
-                    return false;
-                }
-
-                break;
-            case PduHeaders.MESSAGE_TYPE_NOTIFYRESP_IND:
-                // Status field.
-                int nriStatus = headers.getOctet(PduHeaders.STATUS);
-                if (0 == nriStatus) {
-                    return false;
-                }
-
-                // Transaction-Id field.
-                byte[] nriTransactionId = headers.getTextString(PduHeaders.TRANSACTION_ID);
-                if (null == nriTransactionId) {
-                    return false;
-                }
-
-                break;
-            case PduHeaders.MESSAGE_TYPE_RETRIEVE_CONF:
-                // Content-Type field.
-                byte[] rcContentType = headers.getTextString(PduHeaders.CONTENT_TYPE);
-                if (null == rcContentType) {
-                    return false;
-                }
-
-                // Date field.
-                long rcDate = headers.getLongInteger(PduHeaders.DATE);
-                if (-1 == rcDate) {
-                    return false;
-                }
-
-                break;
-            case PduHeaders.MESSAGE_TYPE_DELIVERY_IND:
-                // Date field.
-                long diDate = headers.getLongInteger(PduHeaders.DATE);
-                if (-1 == diDate) {
-                    return false;
-                }
-
-                // Message-Id field.
-                byte[] diMessageId = headers.getTextString(PduHeaders.MESSAGE_ID);
-                if (null == diMessageId) {
-                    return false;
-                }
-
-                // Status field.
-                int diStatus = headers.getOctet(PduHeaders.STATUS);
-                if (0 == diStatus) {
-                    return false;
-                }
-
-                // To field.
-                EncodedStringValue[] diTo = headers.getEncodedStringValues(PduHeaders.TO);
-                if (null == diTo) {
-                    return false;
-                }
-
-                break;
-            case PduHeaders.MESSAGE_TYPE_ACKNOWLEDGE_IND:
-                // Transaction-Id field.
-                byte[] aiTransactionId = headers.getTextString(PduHeaders.TRANSACTION_ID);
-                if (null == aiTransactionId) {
-                    return false;
-                }
-
-                break;
-            case PduHeaders.MESSAGE_TYPE_READ_ORIG_IND:
-                // Date field.
-                long roDate = headers.getLongInteger(PduHeaders.DATE);
-                if (-1 == roDate) {
-                    return false;
-                }
-
-                // From field.
-                EncodedStringValue roFrom = headers.getEncodedStringValue(PduHeaders.FROM);
-                if (null == roFrom) {
-                    return false;
-                }
-
-                // Message-Id field.
-                byte[] roMessageId = headers.getTextString(PduHeaders.MESSAGE_ID);
-                if (null == roMessageId) {
-                    return false;
-                }
-
-                // Read-Status field.
-                int roReadStatus = headers.getOctet(PduHeaders.READ_STATUS);
-                if (0 == roReadStatus) {
-                    return false;
-                }
-
-                // To field.
-                EncodedStringValue[] roTo = headers.getEncodedStringValues(PduHeaders.TO);
-                if (null == roTo) {
-                    return false;
-                }
-
-                break;
-            case PduHeaders.MESSAGE_TYPE_READ_REC_IND:
-                // From field.
-                EncodedStringValue rrFrom = headers.getEncodedStringValue(PduHeaders.FROM);
-                if (null == rrFrom) {
-                    return false;
-                }
-
-                // Message-Id field.
-                byte[] rrMessageId = headers.getTextString(PduHeaders.MESSAGE_ID);
-                if (null == rrMessageId) {
-                    return false;
-                }
-
-                // Read-Status field.
-                int rrReadStatus = headers.getOctet(PduHeaders.READ_STATUS);
-                if (0 == rrReadStatus) {
-                    return false;
-                }
-
-                // To field.
-                EncodedStringValue[] rrTo = headers.getEncodedStringValues(PduHeaders.TO);
-                if (null == rrTo) {
-                    return false;
-                }
-
-                break;
-            default:
-                // Parser doesn't support this message type in this version.
-                return false;
-        }
-
-        return true;
-    }
-}
diff --git a/src/java/com/google/android/mms/pdu/PduPart.java b/src/java/com/google/android/mms/pdu/PduPart.java
deleted file mode 100644
index 1d196f7..0000000
--- a/src/java/com/google/android/mms/pdu/PduPart.java
+++ /dev/null
@@ -1,414 +0,0 @@
-/*
- * Copyright (C) 2007-2008 Esmertec AG.
- * Copyright (C) 2007-2008 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.google.android.mms.pdu;
-
-import android.net.Uri;
-
-import java.util.HashMap;
-import java.util.Map;
-
-/**
- * The pdu part.
- */
-public class PduPart {
-    /**
-     * Well-Known Parameters.
-     */
-    public static final int P_Q                  = 0x80;
-    public static final int P_CHARSET            = 0x81;
-    public static final int P_LEVEL              = 0x82;
-    public static final int P_TYPE               = 0x83;
-    public static final int P_DEP_NAME           = 0x85;
-    public static final int P_DEP_FILENAME       = 0x86;
-    public static final int P_DIFFERENCES        = 0x87;
-    public static final int P_PADDING            = 0x88;
-    // This value of "TYPE" s used with Content-Type: multipart/related
-    public static final int P_CT_MR_TYPE         = 0x89;
-    public static final int P_DEP_START          = 0x8A;
-    public static final int P_DEP_START_INFO     = 0x8B;
-    public static final int P_DEP_COMMENT        = 0x8C;
-    public static final int P_DEP_DOMAIN         = 0x8D;
-    public static final int P_MAX_AGE            = 0x8E;
-    public static final int P_DEP_PATH           = 0x8F;
-    public static final int P_SECURE             = 0x90;
-    public static final int P_SEC                = 0x91;
-    public static final int P_MAC                = 0x92;
-    public static final int P_CREATION_DATE      = 0x93;
-    public static final int P_MODIFICATION_DATE  = 0x94;
-    public static final int P_READ_DATE          = 0x95;
-    public static final int P_SIZE               = 0x96;
-    public static final int P_NAME               = 0x97;
-    public static final int P_FILENAME           = 0x98;
-    public static final int P_START              = 0x99;
-    public static final int P_START_INFO         = 0x9A;
-    public static final int P_COMMENT            = 0x9B;
-    public static final int P_DOMAIN             = 0x9C;
-    public static final int P_PATH               = 0x9D;
-
-    /**
-     *  Header field names.
-     */
-     public static final int P_CONTENT_TYPE       = 0x91;
-     public static final int P_CONTENT_LOCATION   = 0x8E;
-     public static final int P_CONTENT_ID         = 0xC0;
-     public static final int P_DEP_CONTENT_DISPOSITION = 0xAE;
-     public static final int P_CONTENT_DISPOSITION = 0xC5;
-    // The next header is unassigned header, use reserved header(0x48) value.
-     public static final int P_CONTENT_TRANSFER_ENCODING = 0xC8;
-
-     /**
-      * Content=Transfer-Encoding string.
-      */
-     public static final String CONTENT_TRANSFER_ENCODING =
-             "Content-Transfer-Encoding";
-
-     /**
-      * Value of Content-Transfer-Encoding.
-      */
-     public static final String P_BINARY = "binary";
-     public static final String P_7BIT = "7bit";
-     public static final String P_8BIT = "8bit";
-     public static final String P_BASE64 = "base64";
-     public static final String P_QUOTED_PRINTABLE = "quoted-printable";
-
-     /**
-      * Value of disposition can be set to PduPart when the value is octet in
-      * the PDU.
-      * "from-data" instead of Form-data<Octet 128>.
-      * "attachment" instead of Attachment<Octet 129>.
-      * "inline" instead of Inline<Octet 130>.
-      */
-     static final byte[] DISPOSITION_FROM_DATA = "from-data".getBytes();
-     static final byte[] DISPOSITION_ATTACHMENT = "attachment".getBytes();
-     static final byte[] DISPOSITION_INLINE = "inline".getBytes();
-
-     /**
-      * Content-Disposition value.
-      */
-     public static final int P_DISPOSITION_FROM_DATA  = 0x80;
-     public static final int P_DISPOSITION_ATTACHMENT = 0x81;
-     public static final int P_DISPOSITION_INLINE     = 0x82;
-
-     /**
-      * Header of part.
-      */
-     private Map<Integer, Object> mPartHeader = null;
-
-     /**
-      * Data uri.
-      */
-     private Uri mUri = null;
-
-     /**
-      * Part data.
-      */
-     private byte[] mPartData = null;
-
-     private static final String TAG = "PduPart";
-
-     /**
-      * Empty Constructor.
-      */
-     public PduPart() {
-         mPartHeader = new HashMap<Integer, Object>();
-     }
-
-     /**
-      * Set part data. The data are stored as byte array.
-      *
-      * @param data the data
-      */
-     public void setData(byte[] data) {
-         if(data == null) {
-            return;
-        }
-
-         mPartData = new byte[data.length];
-         System.arraycopy(data, 0, mPartData, 0, data.length);
-     }
-
-     /**
-      * @return A copy of the part data or null if the data wasn't set or
-      *         the data is stored as Uri.
-      * @see #getDataUri
-      */
-     public byte[] getData() {
-         if(mPartData == null) {
-            return null;
-         }
-
-         byte[] byteArray = new byte[mPartData.length];
-         System.arraycopy(mPartData, 0, byteArray, 0, mPartData.length);
-         return byteArray;
-     }
-
-    /**
-     * @return The length of the data, if this object have data, else 0.
-     */
-     public int getDataLength() {
-         if(mPartData != null){
-             return mPartData.length;
-         } else {
-             return 0;
-         }
-     }
-
-
-     /**
-      * Set data uri. The data are stored as Uri.
-      *
-      * @param uri the uri
-      */
-     public void setDataUri(Uri uri) {
-         mUri = uri;
-     }
-
-     /**
-      * @return The Uri of the part data or null if the data wasn't set or
-      *         the data is stored as byte array.
-      * @see #getData
-      */
-     public Uri getDataUri() {
-         return mUri;
-     }
-
-     /**
-      * Set Content-id value
-      *
-      * @param contentId the content-id value
-      * @throws NullPointerException if the value is null.
-      */
-     public void setContentId(byte[] contentId) {
-         if((contentId == null) || (contentId.length == 0)) {
-             throw new IllegalArgumentException(
-                     "Content-Id may not be null or empty.");
-         }
-
-         if ((contentId.length > 1)
-                 && ((char) contentId[0] == '<')
-                 && ((char) contentId[contentId.length - 1] == '>')) {
-             mPartHeader.put(P_CONTENT_ID, contentId);
-             return;
-         }
-
-         // Insert beginning '<' and trailing '>' for Content-Id.
-         byte[] buffer = new byte[contentId.length + 2];
-         buffer[0] = (byte) (0xff & '<');
-         buffer[buffer.length - 1] = (byte) (0xff & '>');
-         System.arraycopy(contentId, 0, buffer, 1, contentId.length);
-         mPartHeader.put(P_CONTENT_ID, buffer);
-     }
-
-     /**
-      * Get Content-id value.
-      *
-      * @return the value
-      */
-     public byte[] getContentId() {
-         return (byte[]) mPartHeader.get(P_CONTENT_ID);
-     }
-
-     /**
-      * Set Char-set value.
-      *
-      * @param charset the value
-      */
-     public void setCharset(int charset) {
-         mPartHeader.put(P_CHARSET, charset);
-     }
-
-     /**
-      * Get Char-set value
-      *
-      * @return the charset value. Return 0 if charset was not set.
-      */
-     public int getCharset() {
-         Integer charset = (Integer) mPartHeader.get(P_CHARSET);
-         if(charset == null) {
-             return 0;
-         } else {
-             return charset.intValue();
-         }
-     }
-
-     /**
-      * Set Content-Location value.
-      *
-      * @param contentLocation the value
-      * @throws NullPointerException if the value is null.
-      */
-     public void setContentLocation(byte[] contentLocation) {
-         if(contentLocation == null) {
-             throw new NullPointerException("null content-location");
-         }
-
-         mPartHeader.put(P_CONTENT_LOCATION, contentLocation);
-     }
-
-     /**
-      * Get Content-Location value.
-      *
-      * @return the value
-      *     return PduPart.disposition[0] instead of <Octet 128> (Form-data).
-      *     return PduPart.disposition[1] instead of <Octet 129> (Attachment).
-      *     return PduPart.disposition[2] instead of <Octet 130> (Inline).
-      */
-     public byte[] getContentLocation() {
-         return (byte[]) mPartHeader.get(P_CONTENT_LOCATION);
-     }
-
-     /**
-      * Set Content-Disposition value.
-      * Use PduPart.disposition[0] instead of <Octet 128> (Form-data).
-      * Use PduPart.disposition[1] instead of <Octet 129> (Attachment).
-      * Use PduPart.disposition[2] instead of <Octet 130> (Inline).
-      *
-      * @param contentDisposition the value
-      * @throws NullPointerException if the value is null.
-      */
-     public void setContentDisposition(byte[] contentDisposition) {
-         if(contentDisposition == null) {
-             throw new NullPointerException("null content-disposition");
-         }
-
-         mPartHeader.put(P_CONTENT_DISPOSITION, contentDisposition);
-     }
-
-     /**
-      * Get Content-Disposition value.
-      *
-      * @return the value
-      */
-     public byte[] getContentDisposition() {
-         return (byte[]) mPartHeader.get(P_CONTENT_DISPOSITION);
-     }
-
-     /**
-      *  Set Content-Type value.
-      *
-      *  @param value the value
-      *  @throws NullPointerException if the value is null.
-      */
-     public void setContentType(byte[] contentType) {
-         if(contentType == null) {
-             throw new NullPointerException("null content-type");
-         }
-
-         mPartHeader.put(P_CONTENT_TYPE, contentType);
-     }
-
-     /**
-      * Get Content-Type value of part.
-      *
-      * @return the value
-      */
-     public byte[] getContentType() {
-         return (byte[]) mPartHeader.get(P_CONTENT_TYPE);
-     }
-
-     /**
-      * Set Content-Transfer-Encoding value
-      *
-      * @param contentId the content-id value
-      * @throws NullPointerException if the value is null.
-      */
-     public void setContentTransferEncoding(byte[] contentTransferEncoding) {
-         if(contentTransferEncoding == null) {
-             throw new NullPointerException("null content-transfer-encoding");
-         }
-
-         mPartHeader.put(P_CONTENT_TRANSFER_ENCODING, contentTransferEncoding);
-     }
-
-     /**
-      * Get Content-Transfer-Encoding value.
-      *
-      * @return the value
-      */
-     public byte[] getContentTransferEncoding() {
-         return (byte[]) mPartHeader.get(P_CONTENT_TRANSFER_ENCODING);
-     }
-
-     /**
-      * Set Content-type parameter: name.
-      *
-      * @param name the name value
-      * @throws NullPointerException if the value is null.
-      */
-     public void setName(byte[] name) {
-         if(null == name) {
-             throw new NullPointerException("null content-id");
-         }
-
-         mPartHeader.put(P_NAME, name);
-     }
-
-     /**
-      *  Get content-type parameter: name.
-      *
-      *  @return the name
-      */
-     public byte[] getName() {
-         return (byte[]) mPartHeader.get(P_NAME);
-     }
-
-     /**
-      * Get Content-disposition parameter: filename
-      *
-      * @param fileName the filename value
-      * @throws NullPointerException if the value is null.
-      */
-     public void setFilename(byte[] fileName) {
-         if(null == fileName) {
-             throw new NullPointerException("null content-id");
-         }
-
-         mPartHeader.put(P_FILENAME, fileName);
-     }
-
-     /**
-      * Set Content-disposition parameter: filename
-      *
-      * @return the filename
-      */
-     public byte[] getFilename() {
-         return (byte[]) mPartHeader.get(P_FILENAME);
-     }
-
-    public String generateLocation() {
-        // Assumption: At least one of the content-location / name / filename
-        // or content-id should be set. This is guaranteed by the PduParser
-        // for incoming messages and by MM composer for outgoing messages.
-        byte[] location = (byte[]) mPartHeader.get(P_NAME);
-        if(null == location) {
-            location = (byte[]) mPartHeader.get(P_FILENAME);
-
-            if (null == location) {
-                location = (byte[]) mPartHeader.get(P_CONTENT_LOCATION);
-            }
-        }
-
-        if (null == location) {
-            byte[] contentId = (byte[]) mPartHeader.get(P_CONTENT_ID);
-            return "cid:" + new String(contentId);
-        } else {
-            return new String(location);
-        }
-    }
-}
-
diff --git a/src/java/com/google/android/mms/pdu/PduPersister.java b/src/java/com/google/android/mms/pdu/PduPersister.java
deleted file mode 100755
index ee659ae..0000000
--- a/src/java/com/google/android/mms/pdu/PduPersister.java
+++ /dev/null
@@ -1,1543 +0,0 @@
-/*
- * Copyright (C) 2007-2008 Esmertec AG.
- * Copyright (C) 2007-2008 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.google.android.mms.pdu;
-
-import android.content.ContentResolver;
-import android.content.ContentUris;
-import android.content.ContentValues;
-import android.content.Context;
-import android.database.Cursor;
-import android.database.DatabaseUtils;
-import android.drm.DrmManagerClient;
-import android.net.Uri;
-import android.os.ParcelFileDescriptor;
-import android.provider.Telephony;
-import android.provider.Telephony.Mms;
-import android.provider.Telephony.Mms.Addr;
-import android.provider.Telephony.Mms.Part;
-import android.provider.Telephony.MmsSms;
-import android.provider.Telephony.MmsSms.PendingMessages;
-import android.provider.Telephony.Threads;
-import android.telephony.PhoneNumberUtils;
-import android.telephony.SubscriptionManager;
-import android.telephony.TelephonyManager;
-import android.text.TextUtils;
-import android.util.Log;
-
-import com.google.android.mms.ContentType;
-import com.google.android.mms.InvalidHeaderValueException;
-import com.google.android.mms.MmsException;
-import com.google.android.mms.util.DownloadDrmHelper;
-import com.google.android.mms.util.DrmConvertSession;
-import com.google.android.mms.util.PduCache;
-import com.google.android.mms.util.PduCacheEntry;
-import com.google.android.mms.util.SqliteWrapper;
-
-import java.io.ByteArrayOutputStream;
-import java.io.File;
-import java.io.FileNotFoundException;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.io.UnsupportedEncodingException;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.Map;
-import java.util.Map.Entry;
-import java.util.Set;
-
-/**
- * This class is the high-level manager of PDU storage.
- */
-public class PduPersister {
-    private static final String TAG = "PduPersister";
-    private static final boolean DEBUG = false;
-    private static final boolean LOCAL_LOGV = false;
-
-    private static final long DUMMY_THREAD_ID = Long.MAX_VALUE;
-
-    /**
-     * The uri of temporary drm objects.
-     */
-    public static final String TEMPORARY_DRM_OBJECT_URI =
-        "content://mms/" + Long.MAX_VALUE + "/part";
-    /**
-     * Indicate that we transiently failed to process a MM.
-     */
-    public static final int PROC_STATUS_TRANSIENT_FAILURE   = 1;
-    /**
-     * Indicate that we permanently failed to process a MM.
-     */
-    public static final int PROC_STATUS_PERMANENTLY_FAILURE = 2;
-    /**
-     * Indicate that we have successfully processed a MM.
-     */
-    public static final int PROC_STATUS_COMPLETED           = 3;
-
-    private static PduPersister sPersister;
-    private static final PduCache PDU_CACHE_INSTANCE;
-
-    private static final int[] ADDRESS_FIELDS = new int[] {
-            PduHeaders.BCC,
-            PduHeaders.CC,
-            PduHeaders.FROM,
-            PduHeaders.TO
-    };
-
-    private static final String[] PDU_PROJECTION = new String[] {
-        Mms._ID,
-        Mms.MESSAGE_BOX,
-        Mms.THREAD_ID,
-        Mms.RETRIEVE_TEXT,
-        Mms.SUBJECT,
-        Mms.CONTENT_LOCATION,
-        Mms.CONTENT_TYPE,
-        Mms.MESSAGE_CLASS,
-        Mms.MESSAGE_ID,
-        Mms.RESPONSE_TEXT,
-        Mms.TRANSACTION_ID,
-        Mms.CONTENT_CLASS,
-        Mms.DELIVERY_REPORT,
-        Mms.MESSAGE_TYPE,
-        Mms.MMS_VERSION,
-        Mms.PRIORITY,
-        Mms.READ_REPORT,
-        Mms.READ_STATUS,
-        Mms.REPORT_ALLOWED,
-        Mms.RETRIEVE_STATUS,
-        Mms.STATUS,
-        Mms.DATE,
-        Mms.DELIVERY_TIME,
-        Mms.EXPIRY,
-        Mms.MESSAGE_SIZE,
-        Mms.SUBJECT_CHARSET,
-        Mms.RETRIEVE_TEXT_CHARSET,
-    };
-
-    private static final int PDU_COLUMN_ID                    = 0;
-    private static final int PDU_COLUMN_MESSAGE_BOX           = 1;
-    private static final int PDU_COLUMN_THREAD_ID             = 2;
-    private static final int PDU_COLUMN_RETRIEVE_TEXT         = 3;
-    private static final int PDU_COLUMN_SUBJECT               = 4;
-    private static final int PDU_COLUMN_CONTENT_LOCATION      = 5;
-    private static final int PDU_COLUMN_CONTENT_TYPE          = 6;
-    private static final int PDU_COLUMN_MESSAGE_CLASS         = 7;
-    private static final int PDU_COLUMN_MESSAGE_ID            = 8;
-    private static final int PDU_COLUMN_RESPONSE_TEXT         = 9;
-    private static final int PDU_COLUMN_TRANSACTION_ID        = 10;
-    private static final int PDU_COLUMN_CONTENT_CLASS         = 11;
-    private static final int PDU_COLUMN_DELIVERY_REPORT       = 12;
-    private static final int PDU_COLUMN_MESSAGE_TYPE          = 13;
-    private static final int PDU_COLUMN_MMS_VERSION           = 14;
-    private static final int PDU_COLUMN_PRIORITY              = 15;
-    private static final int PDU_COLUMN_READ_REPORT           = 16;
-    private static final int PDU_COLUMN_READ_STATUS           = 17;
-    private static final int PDU_COLUMN_REPORT_ALLOWED        = 18;
-    private static final int PDU_COLUMN_RETRIEVE_STATUS       = 19;
-    private static final int PDU_COLUMN_STATUS                = 20;
-    private static final int PDU_COLUMN_DATE                  = 21;
-    private static final int PDU_COLUMN_DELIVERY_TIME         = 22;
-    private static final int PDU_COLUMN_EXPIRY                = 23;
-    private static final int PDU_COLUMN_MESSAGE_SIZE          = 24;
-    private static final int PDU_COLUMN_SUBJECT_CHARSET       = 25;
-    private static final int PDU_COLUMN_RETRIEVE_TEXT_CHARSET = 26;
-
-    private static final String[] PART_PROJECTION = new String[] {
-        Part._ID,
-        Part.CHARSET,
-        Part.CONTENT_DISPOSITION,
-        Part.CONTENT_ID,
-        Part.CONTENT_LOCATION,
-        Part.CONTENT_TYPE,
-        Part.FILENAME,
-        Part.NAME,
-        Part.TEXT
-    };
-
-    private static final int PART_COLUMN_ID                  = 0;
-    private static final int PART_COLUMN_CHARSET             = 1;
-    private static final int PART_COLUMN_CONTENT_DISPOSITION = 2;
-    private static final int PART_COLUMN_CONTENT_ID          = 3;
-    private static final int PART_COLUMN_CONTENT_LOCATION    = 4;
-    private static final int PART_COLUMN_CONTENT_TYPE        = 5;
-    private static final int PART_COLUMN_FILENAME            = 6;
-    private static final int PART_COLUMN_NAME                = 7;
-    private static final int PART_COLUMN_TEXT                = 8;
-
-    private static final HashMap<Uri, Integer> MESSAGE_BOX_MAP;
-    // These map are used for convenience in persist() and load().
-    private static final HashMap<Integer, Integer> CHARSET_COLUMN_INDEX_MAP;
-    private static final HashMap<Integer, Integer> ENCODED_STRING_COLUMN_INDEX_MAP;
-    private static final HashMap<Integer, Integer> TEXT_STRING_COLUMN_INDEX_MAP;
-    private static final HashMap<Integer, Integer> OCTET_COLUMN_INDEX_MAP;
-    private static final HashMap<Integer, Integer> LONG_COLUMN_INDEX_MAP;
-    private static final HashMap<Integer, String> CHARSET_COLUMN_NAME_MAP;
-    private static final HashMap<Integer, String> ENCODED_STRING_COLUMN_NAME_MAP;
-    private static final HashMap<Integer, String> TEXT_STRING_COLUMN_NAME_MAP;
-    private static final HashMap<Integer, String> OCTET_COLUMN_NAME_MAP;
-    private static final HashMap<Integer, String> LONG_COLUMN_NAME_MAP;
-
-    static {
-        MESSAGE_BOX_MAP = new HashMap<Uri, Integer>();
-        MESSAGE_BOX_MAP.put(Mms.Inbox.CONTENT_URI,  Mms.MESSAGE_BOX_INBOX);
-        MESSAGE_BOX_MAP.put(Mms.Sent.CONTENT_URI,   Mms.MESSAGE_BOX_SENT);
-        MESSAGE_BOX_MAP.put(Mms.Draft.CONTENT_URI,  Mms.MESSAGE_BOX_DRAFTS);
-        MESSAGE_BOX_MAP.put(Mms.Outbox.CONTENT_URI, Mms.MESSAGE_BOX_OUTBOX);
-
-        CHARSET_COLUMN_INDEX_MAP = new HashMap<Integer, Integer>();
-        CHARSET_COLUMN_INDEX_MAP.put(PduHeaders.SUBJECT, PDU_COLUMN_SUBJECT_CHARSET);
-        CHARSET_COLUMN_INDEX_MAP.put(PduHeaders.RETRIEVE_TEXT, PDU_COLUMN_RETRIEVE_TEXT_CHARSET);
-
-        CHARSET_COLUMN_NAME_MAP = new HashMap<Integer, String>();
-        CHARSET_COLUMN_NAME_MAP.put(PduHeaders.SUBJECT, Mms.SUBJECT_CHARSET);
-        CHARSET_COLUMN_NAME_MAP.put(PduHeaders.RETRIEVE_TEXT, Mms.RETRIEVE_TEXT_CHARSET);
-
-        // Encoded string field code -> column index/name map.
-        ENCODED_STRING_COLUMN_INDEX_MAP = new HashMap<Integer, Integer>();
-        ENCODED_STRING_COLUMN_INDEX_MAP.put(PduHeaders.RETRIEVE_TEXT, PDU_COLUMN_RETRIEVE_TEXT);
-        ENCODED_STRING_COLUMN_INDEX_MAP.put(PduHeaders.SUBJECT, PDU_COLUMN_SUBJECT);
-
-        ENCODED_STRING_COLUMN_NAME_MAP = new HashMap<Integer, String>();
-        ENCODED_STRING_COLUMN_NAME_MAP.put(PduHeaders.RETRIEVE_TEXT, Mms.RETRIEVE_TEXT);
-        ENCODED_STRING_COLUMN_NAME_MAP.put(PduHeaders.SUBJECT, Mms.SUBJECT);
-
-        // Text string field code -> column index/name map.
-        TEXT_STRING_COLUMN_INDEX_MAP = new HashMap<Integer, Integer>();
-        TEXT_STRING_COLUMN_INDEX_MAP.put(PduHeaders.CONTENT_LOCATION, PDU_COLUMN_CONTENT_LOCATION);
-        TEXT_STRING_COLUMN_INDEX_MAP.put(PduHeaders.CONTENT_TYPE, PDU_COLUMN_CONTENT_TYPE);
-        TEXT_STRING_COLUMN_INDEX_MAP.put(PduHeaders.MESSAGE_CLASS, PDU_COLUMN_MESSAGE_CLASS);
-        TEXT_STRING_COLUMN_INDEX_MAP.put(PduHeaders.MESSAGE_ID, PDU_COLUMN_MESSAGE_ID);
-        TEXT_STRING_COLUMN_INDEX_MAP.put(PduHeaders.RESPONSE_TEXT, PDU_COLUMN_RESPONSE_TEXT);
-        TEXT_STRING_COLUMN_INDEX_MAP.put(PduHeaders.TRANSACTION_ID, PDU_COLUMN_TRANSACTION_ID);
-
-        TEXT_STRING_COLUMN_NAME_MAP = new HashMap<Integer, String>();
-        TEXT_STRING_COLUMN_NAME_MAP.put(PduHeaders.CONTENT_LOCATION, Mms.CONTENT_LOCATION);
-        TEXT_STRING_COLUMN_NAME_MAP.put(PduHeaders.CONTENT_TYPE, Mms.CONTENT_TYPE);
-        TEXT_STRING_COLUMN_NAME_MAP.put(PduHeaders.MESSAGE_CLASS, Mms.MESSAGE_CLASS);
-        TEXT_STRING_COLUMN_NAME_MAP.put(PduHeaders.MESSAGE_ID, Mms.MESSAGE_ID);
-        TEXT_STRING_COLUMN_NAME_MAP.put(PduHeaders.RESPONSE_TEXT, Mms.RESPONSE_TEXT);
-        TEXT_STRING_COLUMN_NAME_MAP.put(PduHeaders.TRANSACTION_ID, Mms.TRANSACTION_ID);
-
-        // Octet field code -> column index/name map.
-        OCTET_COLUMN_INDEX_MAP = new HashMap<Integer, Integer>();
-        OCTET_COLUMN_INDEX_MAP.put(PduHeaders.CONTENT_CLASS, PDU_COLUMN_CONTENT_CLASS);
-        OCTET_COLUMN_INDEX_MAP.put(PduHeaders.DELIVERY_REPORT, PDU_COLUMN_DELIVERY_REPORT);
-        OCTET_COLUMN_INDEX_MAP.put(PduHeaders.MESSAGE_TYPE, PDU_COLUMN_MESSAGE_TYPE);
-        OCTET_COLUMN_INDEX_MAP.put(PduHeaders.MMS_VERSION, PDU_COLUMN_MMS_VERSION);
-        OCTET_COLUMN_INDEX_MAP.put(PduHeaders.PRIORITY, PDU_COLUMN_PRIORITY);
-        OCTET_COLUMN_INDEX_MAP.put(PduHeaders.READ_REPORT, PDU_COLUMN_READ_REPORT);
-        OCTET_COLUMN_INDEX_MAP.put(PduHeaders.READ_STATUS, PDU_COLUMN_READ_STATUS);
-        OCTET_COLUMN_INDEX_MAP.put(PduHeaders.REPORT_ALLOWED, PDU_COLUMN_REPORT_ALLOWED);
-        OCTET_COLUMN_INDEX_MAP.put(PduHeaders.RETRIEVE_STATUS, PDU_COLUMN_RETRIEVE_STATUS);
-        OCTET_COLUMN_INDEX_MAP.put(PduHeaders.STATUS, PDU_COLUMN_STATUS);
-
-        OCTET_COLUMN_NAME_MAP = new HashMap<Integer, String>();
-        OCTET_COLUMN_NAME_MAP.put(PduHeaders.CONTENT_CLASS, Mms.CONTENT_CLASS);
-        OCTET_COLUMN_NAME_MAP.put(PduHeaders.DELIVERY_REPORT, Mms.DELIVERY_REPORT);
-        OCTET_COLUMN_NAME_MAP.put(PduHeaders.MESSAGE_TYPE, Mms.MESSAGE_TYPE);
-        OCTET_COLUMN_NAME_MAP.put(PduHeaders.MMS_VERSION, Mms.MMS_VERSION);
-        OCTET_COLUMN_NAME_MAP.put(PduHeaders.PRIORITY, Mms.PRIORITY);
-        OCTET_COLUMN_NAME_MAP.put(PduHeaders.READ_REPORT, Mms.READ_REPORT);
-        OCTET_COLUMN_NAME_MAP.put(PduHeaders.READ_STATUS, Mms.READ_STATUS);
-        OCTET_COLUMN_NAME_MAP.put(PduHeaders.REPORT_ALLOWED, Mms.REPORT_ALLOWED);
-        OCTET_COLUMN_NAME_MAP.put(PduHeaders.RETRIEVE_STATUS, Mms.RETRIEVE_STATUS);
-        OCTET_COLUMN_NAME_MAP.put(PduHeaders.STATUS, Mms.STATUS);
-
-        // Long field code -> column index/name map.
-        LONG_COLUMN_INDEX_MAP = new HashMap<Integer, Integer>();
-        LONG_COLUMN_INDEX_MAP.put(PduHeaders.DATE, PDU_COLUMN_DATE);
-        LONG_COLUMN_INDEX_MAP.put(PduHeaders.DELIVERY_TIME, PDU_COLUMN_DELIVERY_TIME);
-        LONG_COLUMN_INDEX_MAP.put(PduHeaders.EXPIRY, PDU_COLUMN_EXPIRY);
-        LONG_COLUMN_INDEX_MAP.put(PduHeaders.MESSAGE_SIZE, PDU_COLUMN_MESSAGE_SIZE);
-
-        LONG_COLUMN_NAME_MAP = new HashMap<Integer, String>();
-        LONG_COLUMN_NAME_MAP.put(PduHeaders.DATE, Mms.DATE);
-        LONG_COLUMN_NAME_MAP.put(PduHeaders.DELIVERY_TIME, Mms.DELIVERY_TIME);
-        LONG_COLUMN_NAME_MAP.put(PduHeaders.EXPIRY, Mms.EXPIRY);
-        LONG_COLUMN_NAME_MAP.put(PduHeaders.MESSAGE_SIZE, Mms.MESSAGE_SIZE);
-
-        PDU_CACHE_INSTANCE = PduCache.getInstance();
-     }
-
-    private final Context mContext;
-    private final ContentResolver mContentResolver;
-    private final DrmManagerClient mDrmManagerClient;
-    private final TelephonyManager mTelephonyManager;
-
-    private PduPersister(Context context) {
-        mContext = context;
-        mContentResolver = context.getContentResolver();
-        mDrmManagerClient = new DrmManagerClient(context);
-        mTelephonyManager = (TelephonyManager)context
-                .getSystemService(Context.TELEPHONY_SERVICE);
-     }
-
-    /** Get(or create if not exist) an instance of PduPersister */
-    public static PduPersister getPduPersister(Context context) {
-        if ((sPersister == null)) {
-            sPersister = new PduPersister(context);
-        } else if (!context.equals(sPersister.mContext)) {
-            sPersister.release();
-            sPersister = new PduPersister(context);
-        }
-
-        return sPersister;
-    }
-
-    private void setEncodedStringValueToHeaders(
-            Cursor c, int columnIndex,
-            PduHeaders headers, int mapColumn) {
-        String s = c.getString(columnIndex);
-        if ((s != null) && (s.length() > 0)) {
-            int charsetColumnIndex = CHARSET_COLUMN_INDEX_MAP.get(mapColumn);
-            int charset = c.getInt(charsetColumnIndex);
-            EncodedStringValue value = new EncodedStringValue(
-                    charset, getBytes(s));
-            headers.setEncodedStringValue(value, mapColumn);
-        }
-    }
-
-    private void setTextStringToHeaders(
-            Cursor c, int columnIndex,
-            PduHeaders headers, int mapColumn) {
-        String s = c.getString(columnIndex);
-        if (s != null) {
-            headers.setTextString(getBytes(s), mapColumn);
-        }
-    }
-
-    private void setOctetToHeaders(
-            Cursor c, int columnIndex,
-            PduHeaders headers, int mapColumn) throws InvalidHeaderValueException {
-        if (!c.isNull(columnIndex)) {
-            int b = c.getInt(columnIndex);
-            headers.setOctet(b, mapColumn);
-        }
-    }
-
-    private void setLongToHeaders(
-            Cursor c, int columnIndex,
-            PduHeaders headers, int mapColumn) {
-        if (!c.isNull(columnIndex)) {
-            long l = c.getLong(columnIndex);
-            headers.setLongInteger(l, mapColumn);
-        }
-    }
-
-    private Integer getIntegerFromPartColumn(Cursor c, int columnIndex) {
-        if (!c.isNull(columnIndex)) {
-            return c.getInt(columnIndex);
-        }
-        return null;
-    }
-
-    private byte[] getByteArrayFromPartColumn(Cursor c, int columnIndex) {
-        if (!c.isNull(columnIndex)) {
-            return getBytes(c.getString(columnIndex));
-        }
-        return null;
-    }
-
-    private PduPart[] loadParts(long msgId) throws MmsException {
-        Cursor c = SqliteWrapper.query(mContext, mContentResolver,
-                Uri.parse("content://mms/" + msgId + "/part"),
-                PART_PROJECTION, null, null, null);
-
-        PduPart[] parts = null;
-
-        try {
-            if ((c == null) || (c.getCount() == 0)) {
-                if (LOCAL_LOGV) {
-                    Log.v(TAG, "loadParts(" + msgId + "): no part to load.");
-                }
-                return null;
-            }
-
-            int partCount = c.getCount();
-            int partIdx = 0;
-            parts = new PduPart[partCount];
-            while (c.moveToNext()) {
-                PduPart part = new PduPart();
-                Integer charset = getIntegerFromPartColumn(
-                        c, PART_COLUMN_CHARSET);
-                if (charset != null) {
-                    part.setCharset(charset);
-                }
-
-                byte[] contentDisposition = getByteArrayFromPartColumn(
-                        c, PART_COLUMN_CONTENT_DISPOSITION);
-                if (contentDisposition != null) {
-                    part.setContentDisposition(contentDisposition);
-                }
-
-                byte[] contentId = getByteArrayFromPartColumn(
-                        c, PART_COLUMN_CONTENT_ID);
-                if (contentId != null) {
-                    part.setContentId(contentId);
-                }
-
-                byte[] contentLocation = getByteArrayFromPartColumn(
-                        c, PART_COLUMN_CONTENT_LOCATION);
-                if (contentLocation != null) {
-                    part.setContentLocation(contentLocation);
-                }
-
-                byte[] contentType = getByteArrayFromPartColumn(
-                        c, PART_COLUMN_CONTENT_TYPE);
-                if (contentType != null) {
-                    part.setContentType(contentType);
-                } else {
-                    throw new MmsException("Content-Type must be set.");
-                }
-
-                byte[] fileName = getByteArrayFromPartColumn(
-                        c, PART_COLUMN_FILENAME);
-                if (fileName != null) {
-                    part.setFilename(fileName);
-                }
-
-                byte[] name = getByteArrayFromPartColumn(
-                        c, PART_COLUMN_NAME);
-                if (name != null) {
-                    part.setName(name);
-                }
-
-                // Construct a Uri for this part.
-                long partId = c.getLong(PART_COLUMN_ID);
-                Uri partURI = Uri.parse("content://mms/part/" + partId);
-                part.setDataUri(partURI);
-
-                // For images/audio/video, we won't keep their data in Part
-                // because their renderer accept Uri as source.
-                String type = toIsoString(contentType);
-                if (!ContentType.isImageType(type)
-                        && !ContentType.isAudioType(type)
-                        && !ContentType.isVideoType(type)) {
-                    ByteArrayOutputStream baos = new ByteArrayOutputStream();
-                    InputStream is = null;
-
-                    // Store simple string values directly in the database instead of an
-                    // external file.  This makes the text searchable and retrieval slightly
-                    // faster.
-                    if (ContentType.TEXT_PLAIN.equals(type) || ContentType.APP_SMIL.equals(type)
-                            || ContentType.TEXT_HTML.equals(type)) {
-                        String text = c.getString(PART_COLUMN_TEXT);
-                        byte [] blob = new EncodedStringValue(text != null ? text : "")
-                            .getTextString();
-                        baos.write(blob, 0, blob.length);
-                    } else {
-
-                        try {
-                            is = mContentResolver.openInputStream(partURI);
-
-                            byte[] buffer = new byte[256];
-                            int len = is.read(buffer);
-                            while (len >= 0) {
-                                baos.write(buffer, 0, len);
-                                len = is.read(buffer);
-                            }
-                        } catch (IOException e) {
-                            Log.e(TAG, "Failed to load part data", e);
-                            c.close();
-                            throw new MmsException(e);
-                        } finally {
-                            if (is != null) {
-                                try {
-                                    is.close();
-                                } catch (IOException e) {
-                                    Log.e(TAG, "Failed to close stream", e);
-                                } // Ignore
-                            }
-                        }
-                    }
-                    part.setData(baos.toByteArray());
-                }
-                parts[partIdx++] = part;
-            }
-        } finally {
-            if (c != null) {
-                c.close();
-            }
-        }
-
-        return parts;
-    }
-
-    private void loadAddress(long msgId, PduHeaders headers) {
-        Cursor c = SqliteWrapper.query(mContext, mContentResolver,
-                Uri.parse("content://mms/" + msgId + "/addr"),
-                new String[] { Addr.ADDRESS, Addr.CHARSET, Addr.TYPE },
-                null, null, null);
-
-        if (c != null) {
-            try {
-                while (c.moveToNext()) {
-                    String addr = c.getString(0);
-                    if (!TextUtils.isEmpty(addr)) {
-                        int addrType = c.getInt(2);
-                        switch (addrType) {
-                            case PduHeaders.FROM:
-                                headers.setEncodedStringValue(
-                                        new EncodedStringValue(c.getInt(1), getBytes(addr)),
-                                        addrType);
-                                break;
-                            case PduHeaders.TO:
-                            case PduHeaders.CC:
-                            case PduHeaders.BCC:
-                                headers.appendEncodedStringValue(
-                                        new EncodedStringValue(c.getInt(1), getBytes(addr)),
-                                        addrType);
-                                break;
-                            default:
-                                Log.e(TAG, "Unknown address type: " + addrType);
-                                break;
-                        }
-                    }
-                }
-            } finally {
-                c.close();
-            }
-        }
-    }
-
-    /**
-     * Load a PDU from storage by given Uri.
-     *
-     * @param uri The Uri of the PDU to be loaded.
-     * @return A generic PDU object, it may be cast to dedicated PDU.
-     * @throws MmsException Failed to load some fields of a PDU.
-     */
-    public GenericPdu load(Uri uri) throws MmsException {
-        GenericPdu pdu = null;
-        PduCacheEntry cacheEntry = null;
-        int msgBox = 0;
-        long threadId = -1;
-        try {
-            synchronized(PDU_CACHE_INSTANCE) {
-                if (PDU_CACHE_INSTANCE.isUpdating(uri)) {
-                    if (LOCAL_LOGV) {
-                        Log.v(TAG, "load: " + uri + " blocked by isUpdating()");
-                    }
-                    try {
-                        PDU_CACHE_INSTANCE.wait();
-                    } catch (InterruptedException e) {
-                        Log.e(TAG, "load: ", e);
-                    }
-                    cacheEntry = PDU_CACHE_INSTANCE.get(uri);
-                    if (cacheEntry != null) {
-                        return cacheEntry.getPdu();
-                    }
-                }
-                // Tell the cache to indicate to other callers that this item
-                // is currently being updated.
-                PDU_CACHE_INSTANCE.setUpdating(uri, true);
-            }
-
-            Cursor c = SqliteWrapper.query(mContext, mContentResolver, uri,
-                    PDU_PROJECTION, null, null, null);
-            PduHeaders headers = new PduHeaders();
-            Set<Entry<Integer, Integer>> set;
-            long msgId = ContentUris.parseId(uri);
-
-            try {
-                if ((c == null) || (c.getCount() != 1) || !c.moveToFirst()) {
-                    throw new MmsException("Bad uri: " + uri);
-                }
-
-                msgBox = c.getInt(PDU_COLUMN_MESSAGE_BOX);
-                threadId = c.getLong(PDU_COLUMN_THREAD_ID);
-
-                set = ENCODED_STRING_COLUMN_INDEX_MAP.entrySet();
-                for (Entry<Integer, Integer> e : set) {
-                    setEncodedStringValueToHeaders(
-                            c, e.getValue(), headers, e.getKey());
-                }
-
-                set = TEXT_STRING_COLUMN_INDEX_MAP.entrySet();
-                for (Entry<Integer, Integer> e : set) {
-                    setTextStringToHeaders(
-                            c, e.getValue(), headers, e.getKey());
-                }
-
-                set = OCTET_COLUMN_INDEX_MAP.entrySet();
-                for (Entry<Integer, Integer> e : set) {
-                    setOctetToHeaders(
-                            c, e.getValue(), headers, e.getKey());
-                }
-
-                set = LONG_COLUMN_INDEX_MAP.entrySet();
-                for (Entry<Integer, Integer> e : set) {
-                    setLongToHeaders(
-                            c, e.getValue(), headers, e.getKey());
-                }
-            } finally {
-                if (c != null) {
-                    c.close();
-                }
-            }
-
-            // Check whether 'msgId' has been assigned a valid value.
-            if (msgId == -1L) {
-                throw new MmsException("Error! ID of the message: -1.");
-            }
-
-            // Load address information of the MM.
-            loadAddress(msgId, headers);
-
-            int msgType = headers.getOctet(PduHeaders.MESSAGE_TYPE);
-            PduBody body = new PduBody();
-
-            // For PDU which type is M_retrieve.conf or Send.req, we should
-            // load multiparts and put them into the body of the PDU.
-            if ((msgType == PduHeaders.MESSAGE_TYPE_RETRIEVE_CONF)
-                    || (msgType == PduHeaders.MESSAGE_TYPE_SEND_REQ)) {
-                PduPart[] parts = loadParts(msgId);
-                if (parts != null) {
-                    int partsNum = parts.length;
-                    for (int i = 0; i < partsNum; i++) {
-                        body.addPart(parts[i]);
-                    }
-                }
-            }
-
-            switch (msgType) {
-            case PduHeaders.MESSAGE_TYPE_NOTIFICATION_IND:
-                pdu = new NotificationInd(headers);
-                break;
-            case PduHeaders.MESSAGE_TYPE_DELIVERY_IND:
-                pdu = new DeliveryInd(headers);
-                break;
-            case PduHeaders.MESSAGE_TYPE_READ_ORIG_IND:
-                pdu = new ReadOrigInd(headers);
-                break;
-            case PduHeaders.MESSAGE_TYPE_RETRIEVE_CONF:
-                pdu = new RetrieveConf(headers, body);
-                break;
-            case PduHeaders.MESSAGE_TYPE_SEND_REQ:
-                pdu = new SendReq(headers, body);
-                break;
-            case PduHeaders.MESSAGE_TYPE_ACKNOWLEDGE_IND:
-                pdu = new AcknowledgeInd(headers);
-                break;
-            case PduHeaders.MESSAGE_TYPE_NOTIFYRESP_IND:
-                pdu = new NotifyRespInd(headers);
-                break;
-            case PduHeaders.MESSAGE_TYPE_READ_REC_IND:
-                pdu = new ReadRecInd(headers);
-                break;
-            case PduHeaders.MESSAGE_TYPE_SEND_CONF:
-            case PduHeaders.MESSAGE_TYPE_FORWARD_REQ:
-            case PduHeaders.MESSAGE_TYPE_FORWARD_CONF:
-            case PduHeaders.MESSAGE_TYPE_MBOX_STORE_REQ:
-            case PduHeaders.MESSAGE_TYPE_MBOX_STORE_CONF:
-            case PduHeaders.MESSAGE_TYPE_MBOX_VIEW_REQ:
-            case PduHeaders.MESSAGE_TYPE_MBOX_VIEW_CONF:
-            case PduHeaders.MESSAGE_TYPE_MBOX_UPLOAD_REQ:
-            case PduHeaders.MESSAGE_TYPE_MBOX_UPLOAD_CONF:
-            case PduHeaders.MESSAGE_TYPE_MBOX_DELETE_REQ:
-            case PduHeaders.MESSAGE_TYPE_MBOX_DELETE_CONF:
-            case PduHeaders.MESSAGE_TYPE_MBOX_DESCR:
-            case PduHeaders.MESSAGE_TYPE_DELETE_REQ:
-            case PduHeaders.MESSAGE_TYPE_DELETE_CONF:
-            case PduHeaders.MESSAGE_TYPE_CANCEL_REQ:
-            case PduHeaders.MESSAGE_TYPE_CANCEL_CONF:
-                throw new MmsException(
-                        "Unsupported PDU type: " + Integer.toHexString(msgType));
-
-            default:
-                throw new MmsException(
-                        "Unrecognized PDU type: " + Integer.toHexString(msgType));
-            }
-        } finally {
-            synchronized(PDU_CACHE_INSTANCE) {
-                if (pdu != null) {
-                    assert(PDU_CACHE_INSTANCE.get(uri) == null);
-                    // Update the cache entry with the real info
-                    cacheEntry = new PduCacheEntry(pdu, msgBox, threadId);
-                    PDU_CACHE_INSTANCE.put(uri, cacheEntry);
-                }
-                PDU_CACHE_INSTANCE.setUpdating(uri, false);
-                PDU_CACHE_INSTANCE.notifyAll(); // tell anybody waiting on this entry to go ahead
-            }
-        }
-        return pdu;
-    }
-
-    private void persistAddress(
-            long msgId, int type, EncodedStringValue[] array) {
-        ContentValues values = new ContentValues(3);
-
-        for (EncodedStringValue addr : array) {
-            values.clear(); // Clear all values first.
-            values.put(Addr.ADDRESS, toIsoString(addr.getTextString()));
-            values.put(Addr.CHARSET, addr.getCharacterSet());
-            values.put(Addr.TYPE, type);
-
-            Uri uri = Uri.parse("content://mms/" + msgId + "/addr");
-            SqliteWrapper.insert(mContext, mContentResolver, uri, values);
-        }
-    }
-
-    private static String getPartContentType(PduPart part) {
-        return part.getContentType() == null ? null : toIsoString(part.getContentType());
-    }
-
-    public Uri persistPart(PduPart part, long msgId, HashMap<Uri, InputStream> preOpenedFiles)
-            throws MmsException {
-        Uri uri = Uri.parse("content://mms/" + msgId + "/part");
-        ContentValues values = new ContentValues(8);
-
-        int charset = part.getCharset();
-        if (charset != 0 ) {
-            values.put(Part.CHARSET, charset);
-        }
-
-        String contentType = getPartContentType(part);
-        if (contentType != null) {
-            // There is no "image/jpg" in Android (and it's an invalid mimetype).
-            // Change it to "image/jpeg"
-            if (ContentType.IMAGE_JPG.equals(contentType)) {
-                contentType = ContentType.IMAGE_JPEG;
-            }
-
-            values.put(Part.CONTENT_TYPE, contentType);
-            // To ensure the SMIL part is always the first part.
-            if (ContentType.APP_SMIL.equals(contentType)) {
-                values.put(Part.SEQ, -1);
-            }
-        } else {
-            throw new MmsException("MIME type of the part must be set.");
-        }
-
-        if (part.getFilename() != null) {
-            String fileName = new String(part.getFilename());
-            values.put(Part.FILENAME, fileName);
-        }
-
-        if (part.getName() != null) {
-            String name = new String(part.getName());
-            values.put(Part.NAME, name);
-        }
-
-        Object value = null;
-        if (part.getContentDisposition() != null) {
-            value = toIsoString(part.getContentDisposition());
-            values.put(Part.CONTENT_DISPOSITION, (String) value);
-        }
-
-        if (part.getContentId() != null) {
-            value = toIsoString(part.getContentId());
-            values.put(Part.CONTENT_ID, (String) value);
-        }
-
-        if (part.getContentLocation() != null) {
-            value = toIsoString(part.getContentLocation());
-            values.put(Part.CONTENT_LOCATION, (String) value);
-        }
-
-        Uri res = SqliteWrapper.insert(mContext, mContentResolver, uri, values);
-        if (res == null) {
-            throw new MmsException("Failed to persist part, return null.");
-        }
-
-        persistData(part, res, contentType, preOpenedFiles);
-        // After successfully store the data, we should update
-        // the dataUri of the part.
-        part.setDataUri(res);
-
-        return res;
-    }
-
-    /**
-     * Save data of the part into storage. The source data may be given
-     * by a byte[] or a Uri. If it's a byte[], directly save it
-     * into storage, otherwise load source data from the dataUri and then
-     * save it. If the data is an image, we may scale down it according
-     * to user preference.
-     *
-     * @param part The PDU part which contains data to be saved.
-     * @param uri The URI of the part.
-     * @param contentType The MIME type of the part.
-     * @param preOpenedFiles if not null, a map of preopened InputStreams for the parts.
-     * @throws MmsException Cannot find source data or error occurred
-     *         while saving the data.
-     */
-    private void persistData(PduPart part, Uri uri,
-            String contentType, HashMap<Uri, InputStream> preOpenedFiles)
-            throws MmsException {
-        OutputStream os = null;
-        InputStream is = null;
-        DrmConvertSession drmConvertSession = null;
-        Uri dataUri = null;
-        String path = null;
-
-        try {
-            byte[] data = part.getData();
-            if (ContentType.TEXT_PLAIN.equals(contentType)
-                    || ContentType.APP_SMIL.equals(contentType)
-                    || ContentType.TEXT_HTML.equals(contentType)) {
-                ContentValues cv = new ContentValues();
-                if (data == null) {
-                    data = new String("").getBytes(CharacterSets.DEFAULT_CHARSET_NAME);
-                }
-                cv.put(Telephony.Mms.Part.TEXT, new EncodedStringValue(data).getString());
-                if (mContentResolver.update(uri, cv, null, null) != 1) {
-                    throw new MmsException("unable to update " + uri.toString());
-                }
-            } else {
-                boolean isDrm = DownloadDrmHelper.isDrmConvertNeeded(contentType);
-                if (isDrm) {
-                    if (uri != null) {
-                        try (ParcelFileDescriptor pfd =
-                                mContentResolver.openFileDescriptor(uri, "r")) {
-                            if (pfd.getStatSize() > 0) {
-                                // we're not going to re-persist and re-encrypt an already
-                                // converted drm file
-                                return;
-                            }
-                        } catch (Exception e) {
-                            Log.e(TAG, "Can't get file info for: " + part.getDataUri(), e);
-                        }
-                    }
-                    // We haven't converted the file yet, start the conversion
-                    drmConvertSession = DrmConvertSession.open(mContext, contentType);
-                    if (drmConvertSession == null) {
-                        throw new MmsException("Mimetype " + contentType +
-                                " can not be converted.");
-                    }
-                }
-                // uri can look like:
-                // content://mms/part/98
-                os = mContentResolver.openOutputStream(uri);
-                if (data == null) {
-                    dataUri = part.getDataUri();
-                    if ((dataUri == null) || (dataUri.equals(uri))) {
-                        Log.w(TAG, "Can't find data for this part.");
-                        return;
-                    }
-                    // dataUri can look like:
-                    // content://com.google.android.gallery3d.provider/picasa/item/5720646660183715586
-                    if (preOpenedFiles != null && preOpenedFiles.containsKey(dataUri)) {
-                        is = preOpenedFiles.get(dataUri);
-                    }
-                    if (is == null) {
-                        is = mContentResolver.openInputStream(dataUri);
-                    }
-
-                    if (LOCAL_LOGV) {
-                        Log.v(TAG, "Saving data to: " + uri);
-                    }
-
-                    byte[] buffer = new byte[8192];
-                    for (int len = 0; (len = is.read(buffer)) != -1; ) {
-                        if (!isDrm) {
-                            os.write(buffer, 0, len);
-                        } else {
-                            byte[] convertedData = drmConvertSession.convert(buffer, len);
-                            if (convertedData != null) {
-                                os.write(convertedData, 0, convertedData.length);
-                            } else {
-                                throw new MmsException("Error converting drm data.");
-                            }
-                        }
-                    }
-                } else {
-                    if (LOCAL_LOGV) {
-                        Log.v(TAG, "Saving data to: " + uri);
-                    }
-                    if (!isDrm) {
-                        os.write(data);
-                    } else {
-                        dataUri = uri;
-                        byte[] convertedData = drmConvertSession.convert(data, data.length);
-                        if (convertedData != null) {
-                            os.write(convertedData, 0, convertedData.length);
-                        } else {
-                            throw new MmsException("Error converting drm data.");
-                        }
-                    }
-                }
-            }
-        } catch (FileNotFoundException e) {
-            Log.e(TAG, "Failed to open Input/Output stream.", e);
-            throw new MmsException(e);
-        } catch (IOException e) {
-            Log.e(TAG, "Failed to read/write data.", e);
-            throw new MmsException(e);
-        } finally {
-            if (os != null) {
-                try {
-                    os.close();
-                } catch (IOException e) {
-                    Log.e(TAG, "IOException while closing: " + os, e);
-                } // Ignore
-            }
-            if (is != null) {
-                try {
-                    is.close();
-                } catch (IOException e) {
-                    Log.e(TAG, "IOException while closing: " + is, e);
-                } // Ignore
-            }
-            if (drmConvertSession != null) {
-                drmConvertSession.close(path);
-
-                // Reset the permissions on the encrypted part file so everyone has only read
-                // permission.
-                File f = new File(path);
-                ContentValues values = new ContentValues(0);
-                SqliteWrapper.update(mContext, mContentResolver,
-                                     Uri.parse("content://mms/resetFilePerm/" + f.getName()),
-                                     values, null, null);
-            }
-        }
-    }
-
-    private void updateAddress(
-            long msgId, int type, EncodedStringValue[] array) {
-        // Delete old address information and then insert new ones.
-        SqliteWrapper.delete(mContext, mContentResolver,
-                Uri.parse("content://mms/" + msgId + "/addr"),
-                Addr.TYPE + "=" + type, null);
-
-        persistAddress(msgId, type, array);
-    }
-
-    /**
-     * Update headers of a SendReq.
-     *
-     * @param uri The PDU which need to be updated.
-     * @param pdu New headers.
-     * @throws MmsException Bad URI or updating failed.
-     */
-    public void updateHeaders(Uri uri, SendReq sendReq) {
-        synchronized(PDU_CACHE_INSTANCE) {
-            // If the cache item is getting updated, wait until it's done updating before
-            // purging it.
-            if (PDU_CACHE_INSTANCE.isUpdating(uri)) {
-                if (LOCAL_LOGV) {
-                    Log.v(TAG, "updateHeaders: " + uri + " blocked by isUpdating()");
-                }
-                try {
-                    PDU_CACHE_INSTANCE.wait();
-                } catch (InterruptedException e) {
-                    Log.e(TAG, "updateHeaders: ", e);
-                }
-            }
-        }
-        PDU_CACHE_INSTANCE.purge(uri);
-
-        ContentValues values = new ContentValues(10);
-        byte[] contentType = sendReq.getContentType();
-        if (contentType != null) {
-            values.put(Mms.CONTENT_TYPE, toIsoString(contentType));
-        }
-
-        long date = sendReq.getDate();
-        if (date != -1) {
-            values.put(Mms.DATE, date);
-        }
-
-        int deliveryReport = sendReq.getDeliveryReport();
-        if (deliveryReport != 0) {
-            values.put(Mms.DELIVERY_REPORT, deliveryReport);
-        }
-
-        long expiry = sendReq.getExpiry();
-        if (expiry != -1) {
-            values.put(Mms.EXPIRY, expiry);
-        }
-
-        byte[] msgClass = sendReq.getMessageClass();
-        if (msgClass != null) {
-            values.put(Mms.MESSAGE_CLASS, toIsoString(msgClass));
-        }
-
-        int priority = sendReq.getPriority();
-        if (priority != 0) {
-            values.put(Mms.PRIORITY, priority);
-        }
-
-        int readReport = sendReq.getReadReport();
-        if (readReport != 0) {
-            values.put(Mms.READ_REPORT, readReport);
-        }
-
-        byte[] transId = sendReq.getTransactionId();
-        if (transId != null) {
-            values.put(Mms.TRANSACTION_ID, toIsoString(transId));
-        }
-
-        EncodedStringValue subject = sendReq.getSubject();
-        if (subject != null) {
-            values.put(Mms.SUBJECT, toIsoString(subject.getTextString()));
-            values.put(Mms.SUBJECT_CHARSET, subject.getCharacterSet());
-        } else {
-            values.put(Mms.SUBJECT, "");
-        }
-
-        long messageSize = sendReq.getMessageSize();
-        if (messageSize > 0) {
-            values.put(Mms.MESSAGE_SIZE, messageSize);
-        }
-
-        PduHeaders headers = sendReq.getPduHeaders();
-        HashSet<String> recipients = new HashSet<String>();
-        for (int addrType : ADDRESS_FIELDS) {
-            EncodedStringValue[] array = null;
-            if (addrType == PduHeaders.FROM) {
-                EncodedStringValue v = headers.getEncodedStringValue(addrType);
-                if (v != null) {
-                    array = new EncodedStringValue[1];
-                    array[0] = v;
-                }
-            } else {
-                array = headers.getEncodedStringValues(addrType);
-            }
-
-            if (array != null) {
-                long msgId = ContentUris.parseId(uri);
-                updateAddress(msgId, addrType, array);
-                if (addrType == PduHeaders.TO) {
-                    for (EncodedStringValue v : array) {
-                        if (v != null) {
-                            recipients.add(v.getString());
-                        }
-                    }
-                }
-            }
-        }
-        if (!recipients.isEmpty()) {
-            long threadId = Threads.getOrCreateThreadId(mContext, recipients);
-            values.put(Mms.THREAD_ID, threadId);
-        }
-
-        SqliteWrapper.update(mContext, mContentResolver, uri, values, null, null);
-    }
-
-    private void updatePart(Uri uri, PduPart part, HashMap<Uri, InputStream> preOpenedFiles)
-            throws MmsException {
-        ContentValues values = new ContentValues(7);
-
-        int charset = part.getCharset();
-        if (charset != 0 ) {
-            values.put(Part.CHARSET, charset);
-        }
-
-        String contentType = null;
-        if (part.getContentType() != null) {
-            contentType = toIsoString(part.getContentType());
-            values.put(Part.CONTENT_TYPE, contentType);
-        } else {
-            throw new MmsException("MIME type of the part must be set.");
-        }
-
-        if (part.getFilename() != null) {
-            String fileName = new String(part.getFilename());
-            values.put(Part.FILENAME, fileName);
-        }
-
-        if (part.getName() != null) {
-            String name = new String(part.getName());
-            values.put(Part.NAME, name);
-        }
-
-        Object value = null;
-        if (part.getContentDisposition() != null) {
-            value = toIsoString(part.getContentDisposition());
-            values.put(Part.CONTENT_DISPOSITION, (String) value);
-        }
-
-        if (part.getContentId() != null) {
-            value = toIsoString(part.getContentId());
-            values.put(Part.CONTENT_ID, (String) value);
-        }
-
-        if (part.getContentLocation() != null) {
-            value = toIsoString(part.getContentLocation());
-            values.put(Part.CONTENT_LOCATION, (String) value);
-        }
-
-        SqliteWrapper.update(mContext, mContentResolver, uri, values, null, null);
-
-        // Only update the data when:
-        // 1. New binary data supplied or
-        // 2. The Uri of the part is different from the current one.
-        if ((part.getData() != null)
-                || (!uri.equals(part.getDataUri()))) {
-            persistData(part, uri, contentType, preOpenedFiles);
-        }
-    }
-
-    /**
-     * Update all parts of a PDU.
-     *
-     * @param uri The PDU which need to be updated.
-     * @param body New message body of the PDU.
-     * @param preOpenedFiles if not null, a map of preopened InputStreams for the parts.
-     * @throws MmsException Bad URI or updating failed.
-     */
-    public void updateParts(Uri uri, PduBody body, HashMap<Uri, InputStream> preOpenedFiles)
-            throws MmsException {
-        try {
-            PduCacheEntry cacheEntry;
-            synchronized(PDU_CACHE_INSTANCE) {
-                if (PDU_CACHE_INSTANCE.isUpdating(uri)) {
-                    if (LOCAL_LOGV) {
-                        Log.v(TAG, "updateParts: " + uri + " blocked by isUpdating()");
-                    }
-                    try {
-                        PDU_CACHE_INSTANCE.wait();
-                    } catch (InterruptedException e) {
-                        Log.e(TAG, "updateParts: ", e);
-                    }
-                    cacheEntry = PDU_CACHE_INSTANCE.get(uri);
-                    if (cacheEntry != null) {
-                        ((MultimediaMessagePdu) cacheEntry.getPdu()).setBody(body);
-                    }
-                }
-                // Tell the cache to indicate to other callers that this item
-                // is currently being updated.
-                PDU_CACHE_INSTANCE.setUpdating(uri, true);
-            }
-
-            ArrayList<PduPart> toBeCreated = new ArrayList<PduPart>();
-            HashMap<Uri, PduPart> toBeUpdated = new HashMap<Uri, PduPart>();
-
-            int partsNum = body.getPartsNum();
-            StringBuilder filter = new StringBuilder().append('(');
-            for (int i = 0; i < partsNum; i++) {
-                PduPart part = body.getPart(i);
-                Uri partUri = part.getDataUri();
-                if ((partUri == null) || TextUtils.isEmpty(partUri.getAuthority())
-                        || !partUri.getAuthority().startsWith("mms")) {
-                    toBeCreated.add(part);
-                } else {
-                    toBeUpdated.put(partUri, part);
-
-                    // Don't use 'i > 0' to determine whether we should append
-                    // 'AND' since 'i = 0' may be skipped in another branch.
-                    if (filter.length() > 1) {
-                        filter.append(" AND ");
-                    }
-
-                    filter.append(Part._ID);
-                    filter.append("!=");
-                    DatabaseUtils.appendEscapedSQLString(filter, partUri.getLastPathSegment());
-                }
-            }
-            filter.append(')');
-
-            long msgId = ContentUris.parseId(uri);
-
-            // Remove the parts which doesn't exist anymore.
-            SqliteWrapper.delete(mContext, mContentResolver,
-                    Uri.parse(Mms.CONTENT_URI + "/" + msgId + "/part"),
-                    filter.length() > 2 ? filter.toString() : null, null);
-
-            // Create new parts which didn't exist before.
-            for (PduPart part : toBeCreated) {
-                persistPart(part, msgId, preOpenedFiles);
-            }
-
-            // Update the modified parts.
-            for (Map.Entry<Uri, PduPart> e : toBeUpdated.entrySet()) {
-                updatePart(e.getKey(), e.getValue(), preOpenedFiles);
-            }
-        } finally {
-            synchronized(PDU_CACHE_INSTANCE) {
-                PDU_CACHE_INSTANCE.setUpdating(uri, false);
-                PDU_CACHE_INSTANCE.notifyAll();
-            }
-        }
-    }
-
-    /**
-     * Persist a PDU object to specific location in the storage.
-     *
-     * @param pdu The PDU object to be stored.
-     * @param uri Where to store the given PDU object.
-     * @param createThreadId if true, this function may create a thread id for the recipients
-     * @param groupMmsEnabled if true, all of the recipients addressed in the PDU will be used
-     *  to create the associated thread. When false, only the sender will be used in finding or
-     *  creating the appropriate thread or conversation.
-     * @param preOpenedFiles if not null, a map of preopened InputStreams for the parts.
-     * @return A Uri which can be used to access the stored PDU.
-     */
-
-    public Uri persist(GenericPdu pdu, Uri uri, boolean createThreadId, boolean groupMmsEnabled,
-            HashMap<Uri, InputStream> preOpenedFiles)
-            throws MmsException {
-        if (uri == null) {
-            throw new MmsException("Uri may not be null.");
-        }
-        long msgId = -1;
-        try {
-            msgId = ContentUris.parseId(uri);
-        } catch (NumberFormatException e) {
-            // the uri ends with "inbox" or something else like that
-        }
-        boolean existingUri = msgId != -1;
-
-        if (!existingUri && MESSAGE_BOX_MAP.get(uri) == null) {
-            throw new MmsException(
-                    "Bad destination, must be one of "
-                    + "content://mms/inbox, content://mms/sent, "
-                    + "content://mms/drafts, content://mms/outbox, "
-                    + "content://mms/temp.");
-        }
-        synchronized(PDU_CACHE_INSTANCE) {
-            // If the cache item is getting updated, wait until it's done updating before
-            // purging it.
-            if (PDU_CACHE_INSTANCE.isUpdating(uri)) {
-                if (LOCAL_LOGV) {
-                    Log.v(TAG, "persist: " + uri + " blocked by isUpdating()");
-                }
-                try {
-                    PDU_CACHE_INSTANCE.wait();
-                } catch (InterruptedException e) {
-                    Log.e(TAG, "persist1: ", e);
-                }
-            }
-        }
-        PDU_CACHE_INSTANCE.purge(uri);
-
-        PduHeaders header = pdu.getPduHeaders();
-        PduBody body = null;
-        ContentValues values = new ContentValues();
-        Set<Entry<Integer, String>> set;
-
-        set = ENCODED_STRING_COLUMN_NAME_MAP.entrySet();
-        for (Entry<Integer, String> e : set) {
-            int field = e.getKey();
-            EncodedStringValue encodedString = header.getEncodedStringValue(field);
-            if (encodedString != null) {
-                String charsetColumn = CHARSET_COLUMN_NAME_MAP.get(field);
-                values.put(e.getValue(), toIsoString(encodedString.getTextString()));
-                values.put(charsetColumn, encodedString.getCharacterSet());
-            }
-        }
-
-        set = TEXT_STRING_COLUMN_NAME_MAP.entrySet();
-        for (Entry<Integer, String> e : set){
-            byte[] text = header.getTextString(e.getKey());
-            if (text != null) {
-                values.put(e.getValue(), toIsoString(text));
-            }
-        }
-
-        set = OCTET_COLUMN_NAME_MAP.entrySet();
-        for (Entry<Integer, String> e : set){
-            int b = header.getOctet(e.getKey());
-            if (b != 0) {
-                values.put(e.getValue(), b);
-            }
-        }
-
-        set = LONG_COLUMN_NAME_MAP.entrySet();
-        for (Entry<Integer, String> e : set){
-            long l = header.getLongInteger(e.getKey());
-            if (l != -1L) {
-                values.put(e.getValue(), l);
-            }
-        }
-
-        HashMap<Integer, EncodedStringValue[]> addressMap =
-                new HashMap<Integer, EncodedStringValue[]>(ADDRESS_FIELDS.length);
-        // Save address information.
-        for (int addrType : ADDRESS_FIELDS) {
-            EncodedStringValue[] array = null;
-            if (addrType == PduHeaders.FROM) {
-                EncodedStringValue v = header.getEncodedStringValue(addrType);
-                if (v != null) {
-                    array = new EncodedStringValue[1];
-                    array[0] = v;
-                }
-            } else {
-                array = header.getEncodedStringValues(addrType);
-            }
-            addressMap.put(addrType, array);
-        }
-
-        HashSet<String> recipients = new HashSet<String>();
-        int msgType = pdu.getMessageType();
-        // Here we only allocate thread ID for M-Notification.ind,
-        // M-Retrieve.conf and M-Send.req.
-        // Some of other PDU types may be allocated a thread ID outside
-        // this scope.
-        if ((msgType == PduHeaders.MESSAGE_TYPE_NOTIFICATION_IND)
-                || (msgType == PduHeaders.MESSAGE_TYPE_RETRIEVE_CONF)
-                || (msgType == PduHeaders.MESSAGE_TYPE_SEND_REQ)) {
-            switch (msgType) {
-                case PduHeaders.MESSAGE_TYPE_NOTIFICATION_IND:
-                case PduHeaders.MESSAGE_TYPE_RETRIEVE_CONF:
-                    loadRecipients(PduHeaders.FROM, recipients, addressMap, false);
-
-                    // For received messages when group MMS is enabled, we want to associate this
-                    // message with the thread composed of all the recipients -- all but our own
-                    // number, that is. This includes the person who sent the
-                    // message or the FROM field (above) in addition to the other people the message
-                    // was addressed to or the TO field. Our own number is in that TO field and
-                    // we have to ignore it in loadRecipients.
-                    if (groupMmsEnabled) {
-                        loadRecipients(PduHeaders.TO, recipients, addressMap, true);
-
-                        // Also load any numbers in the CC field to address group messaging
-                        // compatibility issues with devices that place numbers in this field
-                        // for group messages.
-                        loadRecipients(PduHeaders.CC, recipients, addressMap, true);
-                    }
-                    break;
-                case PduHeaders.MESSAGE_TYPE_SEND_REQ:
-                    loadRecipients(PduHeaders.TO, recipients, addressMap, false);
-                    break;
-            }
-            long threadId = 0;
-            if (createThreadId && !recipients.isEmpty()) {
-                // Given all the recipients associated with this message, find (or create) the
-                // correct thread.
-                threadId = Threads.getOrCreateThreadId(mContext, recipients);
-            }
-            values.put(Mms.THREAD_ID, threadId);
-        }
-
-        // Save parts first to avoid inconsistent message is loaded
-        // while saving the parts.
-        long dummyId = System.currentTimeMillis(); // Dummy ID of the msg.
-
-        // Figure out if this PDU is a text-only message
-        boolean textOnly = true;
-
-        // Sum up the total message size
-        int messageSize = 0;
-
-        // Get body if the PDU is a RetrieveConf or SendReq.
-        if (pdu instanceof MultimediaMessagePdu) {
-            body = ((MultimediaMessagePdu) pdu).getBody();
-            // Start saving parts if necessary.
-            if (body != null) {
-                int partsNum = body.getPartsNum();
-                if (partsNum > 2) {
-                    // For a text-only message there will be two parts: 1-the SMIL, 2-the text.
-                    // Down a few lines below we're checking to make sure we've only got SMIL or
-                    // text. We also have to check then we don't have more than two parts.
-                    // Otherwise, a slideshow with two text slides would be marked as textOnly.
-                    textOnly = false;
-                }
-                for (int i = 0; i < partsNum; i++) {
-                    PduPart part = body.getPart(i);
-                    messageSize += part.getDataLength();
-                    persistPart(part, dummyId, preOpenedFiles);
-
-                    // If we've got anything besides text/plain or SMIL part, then we've got
-                    // an mms message with some other type of attachment.
-                    String contentType = getPartContentType(part);
-                    if (contentType != null && !ContentType.APP_SMIL.equals(contentType)
-                            && !ContentType.TEXT_PLAIN.equals(contentType)) {
-                        textOnly = false;
-                    }
-                }
-            }
-        }
-        // Record whether this mms message is a simple plain text or not. This is a hint for the
-        // UI.
-        values.put(Mms.TEXT_ONLY, textOnly ? 1 : 0);
-        // The message-size might already have been inserted when parsing the
-        // PDU header. If not, then we insert the message size as well.
-        if (values.getAsInteger(Mms.MESSAGE_SIZE) == null) {
-            values.put(Mms.MESSAGE_SIZE, messageSize);
-        }
-
-        Uri res = null;
-        if (existingUri) {
-            res = uri;
-            SqliteWrapper.update(mContext, mContentResolver, res, values, null, null);
-        } else {
-            res = SqliteWrapper.insert(mContext, mContentResolver, uri, values);
-            if (res == null) {
-                throw new MmsException("persist() failed: return null.");
-            }
-            // Get the real ID of the PDU and update all parts which were
-            // saved with the dummy ID.
-            msgId = ContentUris.parseId(res);
-        }
-
-        values = new ContentValues(1);
-        values.put(Part.MSG_ID, msgId);
-        SqliteWrapper.update(mContext, mContentResolver,
-                             Uri.parse("content://mms/" + dummyId + "/part"),
-                             values, null, null);
-        // We should return the longest URI of the persisted PDU, for
-        // example, if input URI is "content://mms/inbox" and the _ID of
-        // persisted PDU is '8', we should return "content://mms/inbox/8"
-        // instead of "content://mms/8".
-        // FIXME: Should the MmsProvider be responsible for this???
-        if (!existingUri) {
-            res = Uri.parse(uri + "/" + msgId);
-        }
-
-        // Save address information.
-        for (int addrType : ADDRESS_FIELDS) {
-            EncodedStringValue[] array = addressMap.get(addrType);
-            if (array != null) {
-                persistAddress(msgId, addrType, array);
-            }
-        }
-
-        return res;
-    }
-
-    /**
-     * For a given address type, extract the recipients from the headers.
-     *
-     * @param addressType can be PduHeaders.FROM, PduHeaders.TO or PduHeaders.CC
-     * @param recipients a HashSet that is loaded with the recipients from the FROM, TO or CC headers
-     * @param addressMap a HashMap of the addresses from the ADDRESS_FIELDS header
-     * @param excludeMyNumber if true, the number of this phone will be excluded from recipients
-     */
-    private void loadRecipients(int addressType, HashSet<String> recipients,
-            HashMap<Integer, EncodedStringValue[]> addressMap, boolean excludeMyNumber) {
-        EncodedStringValue[] array = addressMap.get(addressType);
-        if (array == null) {
-            return;
-        }
-        // If the TO recipients is only a single address, then we can skip loadRecipients when
-        // we're excluding our own number because we know that address is our own.
-        if (excludeMyNumber && array.length == 1) {
-            return;
-        }
-        final SubscriptionManager subscriptionManager = SubscriptionManager.from(mContext);
-        final Set<String> myPhoneNumbers = new HashSet<String>();
-        if (excludeMyNumber) {
-            // Build a list of my phone numbers from the various sims.
-            for (int subid : subscriptionManager.getActiveSubscriptionIdList()) {
-                final String myNumber = mTelephonyManager.getLine1Number(subid);
-                if (myNumber != null) {
-                    myPhoneNumbers.add(myNumber);
-                }
-            }
-        }
-
-        for (EncodedStringValue v : array) {
-            if (v != null) {
-                final String number = v.getString();
-                if (excludeMyNumber) {
-                    for (final String myNumber : myPhoneNumbers) {
-                        if (!PhoneNumberUtils.compare(number, myNumber)
-                                && !recipients.contains(number)) {
-                            // Only add numbers which aren't my own number.
-                            recipients.add(number);
-                            break;
-                        }
-                    }
-                } else if (!recipients.contains(number)){
-                    recipients.add(number);
-                }
-            }
-        }
-    }
-
-    /**
-     * Move a PDU object from one location to another.
-     *
-     * @param from Specify the PDU object to be moved.
-     * @param to The destination location, should be one of the following:
-     *        "content://mms/inbox", "content://mms/sent",
-     *        "content://mms/drafts", "content://mms/outbox",
-     *        "content://mms/trash".
-     * @return New Uri of the moved PDU.
-     * @throws MmsException Error occurred while moving the message.
-     */
-    public Uri move(Uri from, Uri to) throws MmsException {
-        // Check whether the 'msgId' has been assigned a valid value.
-        long msgId = ContentUris.parseId(from);
-        if (msgId == -1L) {
-            throw new MmsException("Error! ID of the message: -1.");
-        }
-
-        // Get corresponding int value of destination box.
-        Integer msgBox = MESSAGE_BOX_MAP.get(to);
-        if (msgBox == null) {
-            throw new MmsException(
-                    "Bad destination, must be one of "
-                    + "content://mms/inbox, content://mms/sent, "
-                    + "content://mms/drafts, content://mms/outbox, "
-                    + "content://mms/temp.");
-        }
-
-        ContentValues values = new ContentValues(1);
-        values.put(Mms.MESSAGE_BOX, msgBox);
-        SqliteWrapper.update(mContext, mContentResolver, from, values, null, null);
-        return ContentUris.withAppendedId(to, msgId);
-    }
-
-    /**
-     * Wrap a byte[] into a String.
-     */
-    public static String toIsoString(byte[] bytes) {
-        try {
-            return new String(bytes, CharacterSets.MIMENAME_ISO_8859_1);
-        } catch (UnsupportedEncodingException e) {
-            // Impossible to reach here!
-            Log.e(TAG, "ISO_8859_1 must be supported!", e);
-            return "";
-        }
-    }
-
-    /**
-     * Unpack a given String into a byte[].
-     */
-    public static byte[] getBytes(String data) {
-        try {
-            return data.getBytes(CharacterSets.MIMENAME_ISO_8859_1);
-        } catch (UnsupportedEncodingException e) {
-            // Impossible to reach here!
-            Log.e(TAG, "ISO_8859_1 must be supported!", e);
-            return new byte[0];
-        }
-    }
-
-    /**
-     * Remove all objects in the temporary path.
-     */
-    public void release() {
-        Uri uri = Uri.parse(TEMPORARY_DRM_OBJECT_URI);
-        SqliteWrapper.delete(mContext, mContentResolver, uri, null, null);
-    }
-
-    /**
-     * Find all messages to be sent or downloaded before certain time.
-     */
-    public Cursor getPendingMessages(long dueTime) {
-        Uri.Builder uriBuilder = PendingMessages.CONTENT_URI.buildUpon();
-        uriBuilder.appendQueryParameter("protocol", "mms");
-
-        String selection = PendingMessages.ERROR_TYPE + " < ?"
-                + " AND " + PendingMessages.DUE_TIME + " <= ?";
-
-        String[] selectionArgs = new String[] {
-                String.valueOf(MmsSms.ERR_TYPE_GENERIC_PERMANENT),
-                String.valueOf(dueTime)
-        };
-
-        return SqliteWrapper.query(mContext, mContentResolver,
-                uriBuilder.build(), null, selection, selectionArgs,
-                PendingMessages.DUE_TIME);
-    }
-}
diff --git a/src/java/com/google/android/mms/pdu/QuotedPrintable.java b/src/java/com/google/android/mms/pdu/QuotedPrintable.java
deleted file mode 100644
index a34ed12..0000000
--- a/src/java/com/google/android/mms/pdu/QuotedPrintable.java
+++ /dev/null
@@ -1,68 +0,0 @@
-/*
- * Copyright (C) 2007 Esmertec AG.
- * Copyright (C) 2007 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.google.android.mms.pdu;
-
-import java.io.ByteArrayOutputStream;
-
-public class QuotedPrintable {
-    private static byte ESCAPE_CHAR = '=';
-
-    /**
-     * Decodes an array quoted-printable characters into an array of original bytes.
-     * Escaped characters are converted back to their original representation.
-     *
-     * <p>
-     * This function implements a subset of
-     * quoted-printable encoding specification (rule #1 and rule #2)
-     * as defined in RFC 1521.
-     * </p>
-     *
-     * @param bytes array of quoted-printable characters
-     * @return array of original bytes,
-     *         null if quoted-printable decoding is unsuccessful.
-     */
-    public static final byte[] decodeQuotedPrintable(byte[] bytes) {
-        if (bytes == null) {
-            return null;
-        }
-        ByteArrayOutputStream buffer = new ByteArrayOutputStream();
-        for (int i = 0; i < bytes.length; i++) {
-            int b = bytes[i];
-            if (b == ESCAPE_CHAR) {
-                try {
-                    if('\r' == (char)bytes[i + 1] &&
-                            '\n' == (char)bytes[i + 2]) {
-                        i += 2;
-                        continue;
-                    }
-                    int u = Character.digit((char) bytes[++i], 16);
-                    int l = Character.digit((char) bytes[++i], 16);
-                    if (u == -1 || l == -1) {
-                        return null;
-                    }
-                    buffer.write((char) ((u << 4) + l));
-                } catch (ArrayIndexOutOfBoundsException e) {
-                    return null;
-                }
-            } else {
-                buffer.write(b);
-            }
-        }
-        return buffer.toByteArray();
-    }
-}
diff --git a/src/java/com/google/android/mms/pdu/ReadOrigInd.java b/src/java/com/google/android/mms/pdu/ReadOrigInd.java
deleted file mode 100644
index 1bfc0bb..0000000
--- a/src/java/com/google/android/mms/pdu/ReadOrigInd.java
+++ /dev/null
@@ -1,153 +0,0 @@
-/*
- * Copyright (C) 2007 Esmertec AG.
- * Copyright (C) 2007 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.google.android.mms.pdu;
-
-import com.google.android.mms.InvalidHeaderValueException;
-
-public class ReadOrigInd extends GenericPdu {
-    /**
-     * Empty constructor.
-     * Since the Pdu corresponding to this class is constructed
-     * by the Proxy-Relay server, this class is only instantiated
-     * by the Pdu Parser.
-     *
-     * @throws InvalidHeaderValueException if error occurs.
-     */
-    public ReadOrigInd() throws InvalidHeaderValueException {
-        super();
-        setMessageType(PduHeaders.MESSAGE_TYPE_READ_ORIG_IND);
-    }
-
-    /**
-     * Constructor with given headers.
-     *
-     * @param headers Headers for this PDU.
-     */
-    ReadOrigInd(PduHeaders headers) {
-        super(headers);
-    }
-
-    /**
-     * Get Date value.
-     *
-     * @return the value
-     */
-    public long getDate() {
-        return mPduHeaders.getLongInteger(PduHeaders.DATE);
-    }
-
-    /**
-     * Set Date value.
-     *
-     * @param value the value
-     */
-    public void setDate(long value) {
-        mPduHeaders.setLongInteger(value, PduHeaders.DATE);
-    }
-
-    /**
-     * Get From value.
-     * From-value = Value-length
-     *      (Address-present-token Encoded-string-value | Insert-address-token)
-     *
-     * @return the value
-     */
-    public EncodedStringValue getFrom() {
-       return mPduHeaders.getEncodedStringValue(PduHeaders.FROM);
-    }
-
-    /**
-     * Set From value.
-     *
-     * @param value the value
-     * @throws NullPointerException if the value is null.
-     */
-    public void setFrom(EncodedStringValue value) {
-        mPduHeaders.setEncodedStringValue(value, PduHeaders.FROM);
-    }
-
-    /**
-     * Get Message-ID value.
-     *
-     * @return the value
-     */
-    public byte[] getMessageId() {
-        return mPduHeaders.getTextString(PduHeaders.MESSAGE_ID);
-    }
-
-    /**
-     * Set Message-ID value.
-     *
-     * @param value the value
-     * @throws NullPointerException if the value is null.
-     */
-    public void setMessageId(byte[] value) {
-        mPduHeaders.setTextString(value, PduHeaders.MESSAGE_ID);
-    }
-
-    /**
-     * Get X-MMS-Read-status value.
-     *
-     * @return the value
-     */
-    public int getReadStatus() {
-        return mPduHeaders.getOctet(PduHeaders.READ_STATUS);
-    }
-
-    /**
-     * Set X-MMS-Read-status value.
-     *
-     * @param value the value
-     * @throws InvalidHeaderValueException if the value is invalid.
-     */
-    public void setReadStatus(int value) throws InvalidHeaderValueException {
-        mPduHeaders.setOctet(value, PduHeaders.READ_STATUS);
-    }
-
-    /**
-     * Get To value.
-     *
-     * @return the value
-     */
-    public EncodedStringValue[] getTo() {
-        return mPduHeaders.getEncodedStringValues(PduHeaders.TO);
-    }
-
-    /**
-     * Set To value.
-     *
-     * @param value the value
-     * @throws NullPointerException if the value is null.
-     */
-    public void setTo(EncodedStringValue[] value) {
-        mPduHeaders.setEncodedStringValues(value, PduHeaders.TO);
-    }
-
-    /*
-     * Optional, not supported header fields:
-     *
-     *     public byte[] getApplicId() {return null;}
-     *     public void setApplicId(byte[] value) {}
-     *
-     *     public byte[] getAuxApplicId() {return null;}
-     *     public void getAuxApplicId(byte[] value) {}
-     *
-     *     public byte[] getReplyApplicId() {return 0x00;}
-     *     public void setReplyApplicId(byte[] value) {}
-     */
-}
diff --git a/src/java/com/google/android/mms/pdu/ReadRecInd.java b/src/java/com/google/android/mms/pdu/ReadRecInd.java
deleted file mode 100644
index 880e3ac..0000000
--- a/src/java/com/google/android/mms/pdu/ReadRecInd.java
+++ /dev/null
@@ -1,144 +0,0 @@
-/*
- * Copyright (C) 2007 Esmertec AG.
- * Copyright (C) 2007 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.google.android.mms.pdu;
-
-import com.google.android.mms.InvalidHeaderValueException;
-
-public class ReadRecInd extends GenericPdu {
-    /**
-     * Constructor, used when composing a M-ReadRec.ind pdu.
-     *
-     * @param from the from value
-     * @param messageId the message ID value
-     * @param mmsVersion current viersion of mms
-     * @param readStatus the read status value
-     * @param to the to value
-     * @throws InvalidHeaderValueException if parameters are invalid.
-     *         NullPointerException if messageId or to is null.
-     */
-    public ReadRecInd(EncodedStringValue from,
-                      byte[] messageId,
-                      int mmsVersion,
-                      int readStatus,
-                      EncodedStringValue[] to) throws InvalidHeaderValueException {
-        super();
-        setMessageType(PduHeaders.MESSAGE_TYPE_READ_REC_IND);
-        setFrom(from);
-        setMessageId(messageId);
-        setMmsVersion(mmsVersion);
-        setTo(to);
-        setReadStatus(readStatus);
-    }
-
-    /**
-     * Constructor with given headers.
-     *
-     * @param headers Headers for this PDU.
-     */
-    ReadRecInd(PduHeaders headers) {
-        super(headers);
-    }
-
-    /**
-     * Get Date value.
-     *
-     * @return the value
-     */
-    public long getDate() {
-        return mPduHeaders.getLongInteger(PduHeaders.DATE);
-    }
-
-    /**
-     * Set Date value.
-     *
-     * @param value the value
-     */
-    public void setDate(long value) {
-        mPduHeaders.setLongInteger(value, PduHeaders.DATE);
-    }
-
-    /**
-     * Get Message-ID value.
-     *
-     * @return the value
-     */
-    public byte[] getMessageId() {
-        return mPduHeaders.getTextString(PduHeaders.MESSAGE_ID);
-    }
-
-    /**
-     * Set Message-ID value.
-     *
-     * @param value the value
-     * @throws NullPointerException if the value is null.
-     */
-    public void setMessageId(byte[] value) {
-        mPduHeaders.setTextString(value, PduHeaders.MESSAGE_ID);
-    }
-
-    /**
-     * Get To value.
-     *
-     * @return the value
-     */
-    public EncodedStringValue[] getTo() {
-        return mPduHeaders.getEncodedStringValues(PduHeaders.TO);
-    }
-
-    /**
-     * Set To value.
-     *
-     * @param value the value
-     * @throws NullPointerException if the value is null.
-     */
-    public void setTo(EncodedStringValue[] value) {
-        mPduHeaders.setEncodedStringValues(value, PduHeaders.TO);
-    }
-
-    /**
-     * Get X-MMS-Read-status value.
-     *
-     * @return the value
-     */
-    public int getReadStatus() {
-        return mPduHeaders.getOctet(PduHeaders.READ_STATUS);
-    }
-
-    /**
-     * Set X-MMS-Read-status value.
-     *
-     * @param value the value
-     * @throws InvalidHeaderValueException if the value is invalid.
-     */
-    public void setReadStatus(int value) throws InvalidHeaderValueException {
-        mPduHeaders.setOctet(value, PduHeaders.READ_STATUS);
-    }
-
-    /*
-     * Optional, not supported header fields:
-     *
-     *     public byte[] getApplicId() {return null;}
-     *     public void setApplicId(byte[] value) {}
-     *
-     *     public byte[] getAuxApplicId() {return null;}
-     *     public void getAuxApplicId(byte[] value) {}
-     *
-     *     public byte[] getReplyApplicId() {return 0x00;}
-     *     public void setReplyApplicId(byte[] value) {}
-     */
-}
diff --git a/src/java/com/google/android/mms/pdu/RetrieveConf.java b/src/java/com/google/android/mms/pdu/RetrieveConf.java
deleted file mode 100644
index 98e67c0..0000000
--- a/src/java/com/google/android/mms/pdu/RetrieveConf.java
+++ /dev/null
@@ -1,300 +0,0 @@
-/*
- * Copyright (C) 2007 Esmertec AG.
- * Copyright (C) 2007 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.google.android.mms.pdu;
-
-import com.google.android.mms.InvalidHeaderValueException;
-
-/**
- * M-Retrive.conf Pdu.
- */
-public class RetrieveConf extends MultimediaMessagePdu {
-    /**
-     * Empty constructor.
-     * Since the Pdu corresponding to this class is constructed
-     * by the Proxy-Relay server, this class is only instantiated
-     * by the Pdu Parser.
-     *
-     * @throws InvalidHeaderValueException if error occurs.
-     */
-    public RetrieveConf() throws InvalidHeaderValueException {
-        super();
-        setMessageType(PduHeaders.MESSAGE_TYPE_RETRIEVE_CONF);
-    }
-
-    /**
-     * Constructor with given headers.
-     *
-     * @param headers Headers for this PDU.
-     */
-    RetrieveConf(PduHeaders headers) {
-        super(headers);
-    }
-
-    /**
-     * Constructor with given headers and body
-     *
-     * @param headers Headers for this PDU.
-     * @param body Body of this PDu.
-     */
-    RetrieveConf(PduHeaders headers, PduBody body) {
-        super(headers, body);
-    }
-
-    /**
-     * Get CC value.
-     *
-     * @return the value
-     */
-    public EncodedStringValue[] getCc() {
-        return mPduHeaders.getEncodedStringValues(PduHeaders.CC);
-    }
-
-    /**
-     * Add a "CC" value.
-     *
-     * @param value the value
-     * @throws NullPointerException if the value is null.
-     */
-    public void addCc(EncodedStringValue value) {
-        mPduHeaders.appendEncodedStringValue(value, PduHeaders.CC);
-    }
-
-    /**
-     * Get Content-type value.
-     *
-     * @return the value
-     */
-    public byte[] getContentType() {
-        return mPduHeaders.getTextString(PduHeaders.CONTENT_TYPE);
-    }
-
-    /**
-     * Set Content-type value.
-     *
-     * @param value the value
-     * @throws NullPointerException if the value is null.
-     */
-    public void setContentType(byte[] value) {
-        mPduHeaders.setTextString(value, PduHeaders.CONTENT_TYPE);
-    }
-
-    /**
-     * Get X-Mms-Delivery-Report value.
-     *
-     * @return the value
-     */
-    public int getDeliveryReport() {
-        return mPduHeaders.getOctet(PduHeaders.DELIVERY_REPORT);
-    }
-
-    /**
-     * Set X-Mms-Delivery-Report value.
-     *
-     * @param value the value
-     * @throws InvalidHeaderValueException if the value is invalid.
-     */
-    public void setDeliveryReport(int value) throws InvalidHeaderValueException {
-        mPduHeaders.setOctet(value, PduHeaders.DELIVERY_REPORT);
-    }
-
-    /**
-     * Get From value.
-     * From-value = Value-length
-     *      (Address-present-token Encoded-string-value | Insert-address-token)
-     *
-     * @return the value
-     */
-    public EncodedStringValue getFrom() {
-       return mPduHeaders.getEncodedStringValue(PduHeaders.FROM);
-    }
-
-    /**
-     * Set From value.
-     *
-     * @param value the value
-     * @throws NullPointerException if the value is null.
-     */
-    public void setFrom(EncodedStringValue value) {
-        mPduHeaders.setEncodedStringValue(value, PduHeaders.FROM);
-    }
-
-    /**
-     * Get X-Mms-Message-Class value.
-     * Message-class-value = Class-identifier | Token-text
-     * Class-identifier = Personal | Advertisement | Informational | Auto
-     *
-     * @return the value
-     */
-    public byte[] getMessageClass() {
-        return mPduHeaders.getTextString(PduHeaders.MESSAGE_CLASS);
-    }
-
-    /**
-     * Set X-Mms-Message-Class value.
-     *
-     * @param value the value
-     * @throws NullPointerException if the value is null.
-     */
-    public void setMessageClass(byte[] value) {
-        mPduHeaders.setTextString(value, PduHeaders.MESSAGE_CLASS);
-    }
-
-    /**
-     * Get Message-ID value.
-     *
-     * @return the value
-     */
-    public byte[] getMessageId() {
-        return mPduHeaders.getTextString(PduHeaders.MESSAGE_ID);
-    }
-
-    /**
-     * Set Message-ID value.
-     *
-     * @param value the value
-     * @throws NullPointerException if the value is null.
-     */
-    public void setMessageId(byte[] value) {
-        mPduHeaders.setTextString(value, PduHeaders.MESSAGE_ID);
-    }
-
-    /**
-     * Get X-Mms-Read-Report value.
-     *
-     * @return the value
-     */
-    public int getReadReport() {
-        return mPduHeaders.getOctet(PduHeaders.READ_REPORT);
-    }
-
-    /**
-     * Set X-Mms-Read-Report value.
-     *
-     * @param value the value
-     * @throws InvalidHeaderValueException if the value is invalid.
-     */
-    public void setReadReport(int value) throws InvalidHeaderValueException {
-        mPduHeaders.setOctet(value, PduHeaders.READ_REPORT);
-    }
-
-    /**
-     * Get X-Mms-Retrieve-Status value.
-     *
-     * @return the value
-     */
-    public int getRetrieveStatus() {
-        return mPduHeaders.getOctet(PduHeaders.RETRIEVE_STATUS);
-    }
-
-    /**
-     * Set X-Mms-Retrieve-Status value.
-     *
-     * @param value the value
-     * @throws InvalidHeaderValueException if the value is invalid.
-     */
-    public void setRetrieveStatus(int value) throws InvalidHeaderValueException {
-        mPduHeaders.setOctet(value, PduHeaders.RETRIEVE_STATUS);
-    }
-
-    /**
-     * Get X-Mms-Retrieve-Text value.
-     *
-     * @return the value
-     */
-    public EncodedStringValue getRetrieveText() {
-        return mPduHeaders.getEncodedStringValue(PduHeaders.RETRIEVE_TEXT);
-    }
-
-    /**
-     * Set X-Mms-Retrieve-Text value.
-     *
-     * @param value the value
-     * @throws NullPointerException if the value is null.
-     */
-    public void setRetrieveText(EncodedStringValue value) {
-        mPduHeaders.setEncodedStringValue(value, PduHeaders.RETRIEVE_TEXT);
-    }
-
-    /**
-     * Get X-Mms-Transaction-Id.
-     *
-     * @return the value
-     */
-    public byte[] getTransactionId() {
-        return mPduHeaders.getTextString(PduHeaders.TRANSACTION_ID);
-    }
-
-    /**
-     * Set X-Mms-Transaction-Id.
-     *
-     * @param value the value
-     * @throws NullPointerException if the value is null.
-     */
-    public void setTransactionId(byte[] value) {
-        mPduHeaders.setTextString(value, PduHeaders.TRANSACTION_ID);
-    }
-
-    /*
-     * Optional, not supported header fields:
-     *
-     *     public byte[] getApplicId() {return null;}
-     *     public void setApplicId(byte[] value) {}
-     *
-     *     public byte[] getAuxApplicId() {return null;}
-     *     public void getAuxApplicId(byte[] value) {}
-     *
-     *     public byte getContentClass() {return 0x00;}
-     *     public void setApplicId(byte value) {}
-     *
-     *     public byte getDrmContent() {return 0x00;}
-     *     public void setDrmContent(byte value) {}
-     *
-     *     public byte getDistributionIndicator() {return 0x00;}
-     *     public void setDistributionIndicator(byte value) {}
-     *
-     *     public PreviouslySentByValue getPreviouslySentBy() {return null;}
-     *     public void setPreviouslySentBy(PreviouslySentByValue value) {}
-     *
-     *     public PreviouslySentDateValue getPreviouslySentDate() {}
-     *     public void setPreviouslySentDate(PreviouslySentDateValue value) {}
-     *
-     *     public MmFlagsValue getMmFlags() {return null;}
-     *     public void setMmFlags(MmFlagsValue value) {}
-     *
-     *     public MmStateValue getMmState() {return null;}
-     *     public void getMmState(MmStateValue value) {}
-     *
-     *     public byte[] getReplaceId() {return 0x00;}
-     *     public void setReplaceId(byte[] value) {}
-     *
-     *     public byte[] getReplyApplicId() {return 0x00;}
-     *     public void setReplyApplicId(byte[] value) {}
-     *
-     *     public byte getReplyCharging() {return 0x00;}
-     *     public void setReplyCharging(byte value) {}
-     *
-     *     public byte getReplyChargingDeadline() {return 0x00;}
-     *     public void setReplyChargingDeadline(byte value) {}
-     *
-     *     public byte[] getReplyChargingId() {return 0x00;}
-     *     public void setReplyChargingId(byte[] value) {}
-     *
-     *     public long getReplyChargingSize() {return 0;}
-     *     public void setReplyChargingSize(long value) {}
-     */
-}
diff --git a/src/java/com/google/android/mms/pdu/SendConf.java b/src/java/com/google/android/mms/pdu/SendConf.java
deleted file mode 100644
index 0568fe7..0000000
--- a/src/java/com/google/android/mms/pdu/SendConf.java
+++ /dev/null
@@ -1,117 +0,0 @@
-/*
- * Copyright (C) 2007 Esmertec AG.
- * Copyright (C) 2007 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.google.android.mms.pdu;
-
-import com.google.android.mms.InvalidHeaderValueException;
-
-public class SendConf extends GenericPdu {
-    /**
-     * Empty constructor.
-     * Since the Pdu corresponding to this class is constructed
-     * by the Proxy-Relay server, this class is only instantiated
-     * by the Pdu Parser.
-     *
-     * @throws InvalidHeaderValueException if error occurs.
-     */
-    public SendConf() throws InvalidHeaderValueException {
-        super();
-        setMessageType(PduHeaders.MESSAGE_TYPE_SEND_CONF);
-    }
-
-    /**
-     * Constructor with given headers.
-     *
-     * @param headers Headers for this PDU.
-     */
-    SendConf(PduHeaders headers) {
-        super(headers);
-    }
-
-    /**
-     * Get Message-ID value.
-     *
-     * @return the value
-     */
-    public byte[] getMessageId() {
-        return mPduHeaders.getTextString(PduHeaders.MESSAGE_ID);
-    }
-
-    /**
-     * Set Message-ID value.
-     *
-     * @param value the value
-     * @throws NullPointerException if the value is null.
-     */
-    public void setMessageId(byte[] value) {
-        mPduHeaders.setTextString(value, PduHeaders.MESSAGE_ID);
-    }
-
-    /**
-     * Get X-Mms-Response-Status.
-     *
-     * @return the value
-     */
-    public int getResponseStatus() {
-        return mPduHeaders.getOctet(PduHeaders.RESPONSE_STATUS);
-    }
-
-    /**
-     * Set X-Mms-Response-Status.
-     *
-     * @param value the values
-     * @throws InvalidHeaderValueException if the value is invalid.
-     */
-    public void setResponseStatus(int value) throws InvalidHeaderValueException {
-        mPduHeaders.setOctet(value, PduHeaders.RESPONSE_STATUS);
-    }
-
-    /**
-     * Get X-Mms-Transaction-Id field value.
-     *
-     * @return the X-Mms-Report-Allowed value
-     */
-    public byte[] getTransactionId() {
-        return mPduHeaders.getTextString(PduHeaders.TRANSACTION_ID);
-    }
-
-    /**
-     * Set X-Mms-Transaction-Id field value.
-     *
-     * @param value the value
-     * @throws NullPointerException if the value is null.
-     */
-    public void setTransactionId(byte[] value) {
-            mPduHeaders.setTextString(value, PduHeaders.TRANSACTION_ID);
-    }
-
-    /*
-     * Optional, not supported header fields:
-     *
-     *    public byte[] getContentLocation() {return null;}
-     *    public void setContentLocation(byte[] value) {}
-     *
-     *    public EncodedStringValue getResponseText() {return null;}
-     *    public void setResponseText(EncodedStringValue value) {}
-     *
-     *    public byte getStoreStatus() {return 0x00;}
-     *    public void setStoreStatus(byte value) {}
-     *
-     *    public byte[] getStoreStatusText() {return null;}
-     *    public void setStoreStatusText(byte[] value) {}
-     */
-}
diff --git a/src/java/com/google/android/mms/pdu/SendReq.java b/src/java/com/google/android/mms/pdu/SendReq.java
deleted file mode 100644
index 597cd00..0000000
--- a/src/java/com/google/android/mms/pdu/SendReq.java
+++ /dev/null
@@ -1,345 +0,0 @@
-/*
- * Copyright (C) 2007-2008 Esmertec AG.
- * Copyright (C) 2007-2008 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.google.android.mms.pdu;
-
-import android.util.Log;
-
-import com.google.android.mms.InvalidHeaderValueException;
-
-public class SendReq extends MultimediaMessagePdu {
-    private static final String TAG = "SendReq";
-
-    public SendReq() {
-        super();
-
-        try {
-            setMessageType(PduHeaders.MESSAGE_TYPE_SEND_REQ);
-            setMmsVersion(PduHeaders.CURRENT_MMS_VERSION);
-            // FIXME: Content-type must be decided according to whether
-            // SMIL part present.
-            setContentType("application/vnd.wap.multipart.related".getBytes());
-            setFrom(new EncodedStringValue(PduHeaders.FROM_INSERT_ADDRESS_TOKEN_STR.getBytes()));
-            setTransactionId(generateTransactionId());
-        } catch (InvalidHeaderValueException e) {
-            // Impossible to reach here since all headers we set above are valid.
-            Log.e(TAG, "Unexpected InvalidHeaderValueException.", e);
-            throw new RuntimeException(e);
-        }
-    }
-
-    private byte[] generateTransactionId() {
-        String transactionId = "T" + Long.toHexString(System.currentTimeMillis());
-        return transactionId.getBytes();
-    }
-
-    /**
-     * Constructor, used when composing a M-Send.req pdu.
-     *
-     * @param contentType the content type value
-     * @param from the from value
-     * @param mmsVersion current viersion of mms
-     * @param transactionId the transaction-id value
-     * @throws InvalidHeaderValueException if parameters are invalid.
-     *         NullPointerException if contentType, form or transactionId is null.
-     */
-    public SendReq(byte[] contentType,
-                   EncodedStringValue from,
-                   int mmsVersion,
-                   byte[] transactionId) throws InvalidHeaderValueException {
-        super();
-        setMessageType(PduHeaders.MESSAGE_TYPE_SEND_REQ);
-        setContentType(contentType);
-        setFrom(from);
-        setMmsVersion(mmsVersion);
-        setTransactionId(transactionId);
-    }
-
-    /**
-     * Constructor with given headers.
-     *
-     * @param headers Headers for this PDU.
-     */
-    SendReq(PduHeaders headers) {
-        super(headers);
-    }
-
-    /**
-     * Constructor with given headers and body
-     *
-     * @param headers Headers for this PDU.
-     * @param body Body of this PDu.
-     */
-    SendReq(PduHeaders headers, PduBody body) {
-        super(headers, body);
-    }
-
-    /**
-     * Get Bcc value.
-     *
-     * @return the value
-     */
-    public EncodedStringValue[] getBcc() {
-        return mPduHeaders.getEncodedStringValues(PduHeaders.BCC);
-    }
-
-    /**
-     * Add a "BCC" value.
-     *
-     * @param value the value
-     * @throws NullPointerException if the value is null.
-     */
-    public void addBcc(EncodedStringValue value) {
-        mPduHeaders.appendEncodedStringValue(value, PduHeaders.BCC);
-    }
-
-    /**
-     * Set "BCC" value.
-     *
-     * @param value the value
-     * @throws NullPointerException if the value is null.
-     */
-    public void setBcc(EncodedStringValue[] value) {
-        mPduHeaders.setEncodedStringValues(value, PduHeaders.BCC);
-    }
-
-    /**
-     * Get CC value.
-     *
-     * @return the value
-     */
-    public EncodedStringValue[] getCc() {
-        return mPduHeaders.getEncodedStringValues(PduHeaders.CC);
-    }
-
-    /**
-     * Add a "CC" value.
-     *
-     * @param value the value
-     * @throws NullPointerException if the value is null.
-     */
-    public void addCc(EncodedStringValue value) {
-        mPduHeaders.appendEncodedStringValue(value, PduHeaders.CC);
-    }
-
-    /**
-     * Set "CC" value.
-     *
-     * @param value the value
-     * @throws NullPointerException if the value is null.
-     */
-    public void setCc(EncodedStringValue[] value) {
-        mPduHeaders.setEncodedStringValues(value, PduHeaders.CC);
-    }
-
-    /**
-     * Get Content-type value.
-     *
-     * @return the value
-     */
-    public byte[] getContentType() {
-        return mPduHeaders.getTextString(PduHeaders.CONTENT_TYPE);
-    }
-
-    /**
-     * Set Content-type value.
-     *
-     * @param value the value
-     * @throws NullPointerException if the value is null.
-     */
-    public void setContentType(byte[] value) {
-        mPduHeaders.setTextString(value, PduHeaders.CONTENT_TYPE);
-    }
-
-    /**
-     * Get X-Mms-Delivery-Report value.
-     *
-     * @return the value
-     */
-    public int getDeliveryReport() {
-        return mPduHeaders.getOctet(PduHeaders.DELIVERY_REPORT);
-    }
-
-    /**
-     * Set X-Mms-Delivery-Report value.
-     *
-     * @param value the value
-     * @throws InvalidHeaderValueException if the value is invalid.
-     */
-    public void setDeliveryReport(int value) throws InvalidHeaderValueException {
-        mPduHeaders.setOctet(value, PduHeaders.DELIVERY_REPORT);
-    }
-
-    /**
-     * Get X-Mms-Expiry value.
-     *
-     * Expiry-value = Value-length
-     *      (Absolute-token Date-value | Relative-token Delta-seconds-value)
-     *
-     * @return the value
-     */
-    public long getExpiry() {
-        return mPduHeaders.getLongInteger(PduHeaders.EXPIRY);
-    }
-
-    /**
-     * Set X-Mms-Expiry value.
-     *
-     * @param value the value
-     */
-    public void setExpiry(long value) {
-        mPduHeaders.setLongInteger(value, PduHeaders.EXPIRY);
-    }
-
-    /**
-     * Get X-Mms-MessageSize value.
-     *
-     * Expiry-value = size of message
-     *
-     * @return the value
-     */
-    public long getMessageSize() {
-        return mPduHeaders.getLongInteger(PduHeaders.MESSAGE_SIZE);
-    }
-
-    /**
-     * Set X-Mms-MessageSize value.
-     *
-     * @param value the value
-     */
-    public void setMessageSize(long value) {
-        mPduHeaders.setLongInteger(value, PduHeaders.MESSAGE_SIZE);
-    }
-
-    /**
-     * Get X-Mms-Message-Class value.
-     * Message-class-value = Class-identifier | Token-text
-     * Class-identifier = Personal | Advertisement | Informational | Auto
-     *
-     * @return the value
-     */
-    public byte[] getMessageClass() {
-        return mPduHeaders.getTextString(PduHeaders.MESSAGE_CLASS);
-    }
-
-    /**
-     * Set X-Mms-Message-Class value.
-     *
-     * @param value the value
-     * @throws NullPointerException if the value is null.
-     */
-    public void setMessageClass(byte[] value) {
-        mPduHeaders.setTextString(value, PduHeaders.MESSAGE_CLASS);
-    }
-
-    /**
-     * Get X-Mms-Read-Report value.
-     *
-     * @return the value
-     */
-    public int getReadReport() {
-        return mPduHeaders.getOctet(PduHeaders.READ_REPORT);
-    }
-
-    /**
-     * Set X-Mms-Read-Report value.
-     *
-     * @param value the value
-     * @throws InvalidHeaderValueException if the value is invalid.
-     */
-    public void setReadReport(int value) throws InvalidHeaderValueException {
-        mPduHeaders.setOctet(value, PduHeaders.READ_REPORT);
-    }
-
-    /**
-     * Set "To" value.
-     *
-     * @param value the value
-     * @throws NullPointerException if the value is null.
-     */
-    public void setTo(EncodedStringValue[] value) {
-        mPduHeaders.setEncodedStringValues(value, PduHeaders.TO);
-    }
-
-    /**
-     * Get X-Mms-Transaction-Id field value.
-     *
-     * @return the X-Mms-Report-Allowed value
-     */
-    public byte[] getTransactionId() {
-        return mPduHeaders.getTextString(PduHeaders.TRANSACTION_ID);
-    }
-
-    /**
-     * Set X-Mms-Transaction-Id field value.
-     *
-     * @param value the value
-     * @throws NullPointerException if the value is null.
-     */
-    public void setTransactionId(byte[] value) {
-        mPduHeaders.setTextString(value, PduHeaders.TRANSACTION_ID);
-    }
-
-    /*
-     * Optional, not supported header fields:
-     *
-     *     public byte getAdaptationAllowed() {return 0};
-     *     public void setAdaptationAllowed(btye value) {};
-     *
-     *     public byte[] getApplicId() {return null;}
-     *     public void setApplicId(byte[] value) {}
-     *
-     *     public byte[] getAuxApplicId() {return null;}
-     *     public void getAuxApplicId(byte[] value) {}
-     *
-     *     public byte getContentClass() {return 0x00;}
-     *     public void setApplicId(byte value) {}
-     *
-     *     public long getDeliveryTime() {return 0};
-     *     public void setDeliveryTime(long value) {};
-     *
-     *     public byte getDrmContent() {return 0x00;}
-     *     public void setDrmContent(byte value) {}
-     *
-     *     public MmFlagsValue getMmFlags() {return null;}
-     *     public void setMmFlags(MmFlagsValue value) {}
-     *
-     *     public MmStateValue getMmState() {return null;}
-     *     public void getMmState(MmStateValue value) {}
-     *
-     *     public byte[] getReplyApplicId() {return 0x00;}
-     *     public void setReplyApplicId(byte[] value) {}
-     *
-     *     public byte getReplyCharging() {return 0x00;}
-     *     public void setReplyCharging(byte value) {}
-     *
-     *     public byte getReplyChargingDeadline() {return 0x00;}
-     *     public void setReplyChargingDeadline(byte value) {}
-     *
-     *     public byte[] getReplyChargingId() {return 0x00;}
-     *     public void setReplyChargingId(byte[] value) {}
-     *
-     *     public long getReplyChargingSize() {return 0;}
-     *     public void setReplyChargingSize(long value) {}
-     *
-     *     public byte[] getReplyApplicId() {return 0x00;}
-     *     public void setReplyApplicId(byte[] value) {}
-     *
-     *     public byte getStore() {return 0x00;}
-     *     public void setStore(byte value) {}
-     */
-}
diff --git a/src/java/com/google/android/mms/pdu/package.html b/src/java/com/google/android/mms/pdu/package.html
deleted file mode 100755
index c9f96a6..0000000
--- a/src/java/com/google/android/mms/pdu/package.html
+++ /dev/null
@@ -1,5 +0,0 @@
-<body>
-
-{@hide}
-
-</body>
diff --git a/src/java/com/google/android/mms/util/AbstractCache.java b/src/java/com/google/android/mms/util/AbstractCache.java
deleted file mode 100644
index 39b2abf..0000000
--- a/src/java/com/google/android/mms/util/AbstractCache.java
+++ /dev/null
@@ -1,112 +0,0 @@
-/*
- * Copyright (C) 2008 Esmertec AG.
- * Copyright (C) 2008 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.google.android.mms.util;
-
-import android.util.Log;
-
-import java.util.HashMap;
-
-public abstract class AbstractCache<K, V> {
-    private static final String TAG = "AbstractCache";
-    private static final boolean DEBUG = false;
-    private static final boolean LOCAL_LOGV = false;
-
-    private static final int MAX_CACHED_ITEMS  = 500;
-
-    private final HashMap<K, CacheEntry<V>> mCacheMap;
-
-    protected AbstractCache() {
-        mCacheMap = new HashMap<K, CacheEntry<V>>();
-    }
-
-    public boolean put(K key, V value) {
-        if (LOCAL_LOGV) {
-            Log.v(TAG, "Trying to put " + key + " into cache.");
-        }
-
-        if (mCacheMap.size() >= MAX_CACHED_ITEMS) {
-            // TODO Should remove the oldest or least hit cached entry
-            // and then cache the new one.
-            if (LOCAL_LOGV) {
-                Log.v(TAG, "Failed! size limitation reached.");
-            }
-            return false;
-        }
-
-        if (key != null) {
-            CacheEntry<V> cacheEntry = new CacheEntry<V>();
-            cacheEntry.value = value;
-            mCacheMap.put(key, cacheEntry);
-
-            if (LOCAL_LOGV) {
-                Log.v(TAG, key + " cached, " + mCacheMap.size() + " items total.");
-            }
-            return true;
-        }
-        return false;
-    }
-
-    public V get(K key) {
-        if (LOCAL_LOGV) {
-            Log.v(TAG, "Trying to get " + key + " from cache.");
-        }
-
-        if (key != null) {
-            CacheEntry<V> cacheEntry = mCacheMap.get(key);
-            if (cacheEntry != null) {
-                cacheEntry.hit++;
-                if (LOCAL_LOGV) {
-                    Log.v(TAG, key + " hit " + cacheEntry.hit + " times.");
-                }
-                return cacheEntry.value;
-            }
-        }
-        return null;
-    }
-
-    public V purge(K key) {
-        if (LOCAL_LOGV) {
-            Log.v(TAG, "Trying to purge " + key);
-        }
-
-        CacheEntry<V> v = mCacheMap.remove(key);
-
-        if (LOCAL_LOGV) {
-            Log.v(TAG, mCacheMap.size() + " items cached.");
-        }
-
-        return v != null ? v.value : null;
-    }
-
-    public void purgeAll() {
-        if (LOCAL_LOGV) {
-            Log.v(TAG, "Purging cache, " + mCacheMap.size()
-                    + " items dropped.");
-        }
-        mCacheMap.clear();
-    }
-
-    public int size() {
-        return mCacheMap.size();
-    }
-
-    private static class CacheEntry<V> {
-        int hit;
-        V value;
-    }
-}
diff --git a/src/java/com/google/android/mms/util/DownloadDrmHelper.java b/src/java/com/google/android/mms/util/DownloadDrmHelper.java
deleted file mode 100644
index 6852eca..0000000
--- a/src/java/com/google/android/mms/util/DownloadDrmHelper.java
+++ /dev/null
@@ -1,111 +0,0 @@
-/*
- * Copyright (C) 2012 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.google.android.mms.util;
-
-import android.content.Context;
-import android.drm.DrmManagerClient;
-import android.util.Log;
-
-public class DownloadDrmHelper {
-    private static final String TAG = "DownloadDrmHelper";
-
-    /** The MIME type of special DRM files */
-    public static final String MIMETYPE_DRM_MESSAGE = "application/vnd.oma.drm.message";
-
-    /** The extensions of special DRM files */
-    public static final String EXTENSION_DRM_MESSAGE = ".dm";
-
-    public static final String EXTENSION_INTERNAL_FWDL = ".fl";
-
-    /**
-     * Checks if the Media Type is a DRM Media Type
-     *
-     * @param drmManagerClient A DrmManagerClient
-     * @param mimetype Media Type to check
-     * @return True if the Media Type is DRM else false
-     */
-    public static boolean isDrmMimeType(Context context, String mimetype) {
-        boolean result = false;
-        if (context != null) {
-            try {
-                DrmManagerClient drmClient = new DrmManagerClient(context);
-                if (drmClient != null && mimetype != null && mimetype.length() > 0) {
-                    result = drmClient.canHandle("", mimetype);
-                }
-            } catch (IllegalArgumentException e) {
-                Log.w(TAG,
-                        "DrmManagerClient instance could not be created, context is Illegal.");
-            } catch (IllegalStateException e) {
-                Log.w(TAG, "DrmManagerClient didn't initialize properly.");
-            }
-        }
-        return result;
-    }
-
-    /**
-     * Checks if the Media Type needs to be DRM converted
-     *
-     * @param mimetype Media type of the content
-     * @return True if convert is needed else false
-     */
-    public static boolean isDrmConvertNeeded(String mimetype) {
-        return MIMETYPE_DRM_MESSAGE.equals(mimetype);
-    }
-
-    /**
-     * Modifies the file extension for a DRM Forward Lock file NOTE: This
-     * function shouldn't be called if the file shouldn't be DRM converted
-     */
-    public static String modifyDrmFwLockFileExtension(String filename) {
-        if (filename != null) {
-            int extensionIndex;
-            extensionIndex = filename.lastIndexOf(".");
-            if (extensionIndex != -1) {
-                filename = filename.substring(0, extensionIndex);
-            }
-            filename = filename.concat(EXTENSION_INTERNAL_FWDL);
-        }
-        return filename;
-    }
-
-    /**
-     * Gets the original mime type of DRM protected content.
-     *
-     * @param context The context
-     * @param path Path to the file
-     * @param containingMime The current mime type of of the file i.e. the
-     *            containing mime type
-     * @return The original mime type of the file if DRM protected else the
-     *         currentMime
-     */
-    public static String getOriginalMimeType(Context context, String path, String containingMime) {
-        String result = containingMime;
-        DrmManagerClient drmClient = new DrmManagerClient(context);
-        try {
-            if (drmClient.canHandle(path, null)) {
-                result = drmClient.getOriginalMimeType(path);
-            }
-        } catch (IllegalArgumentException ex) {
-            Log.w(TAG,
-                    "Can't get original mime type since path is null or empty string.");
-        } catch (IllegalStateException ex) {
-            Log.w(TAG, "DrmManagerClient didn't initialize properly.");
-        }
-        return result;
-    }
-}
diff --git a/src/java/com/google/android/mms/util/DrmConvertSession.java b/src/java/com/google/android/mms/util/DrmConvertSession.java
deleted file mode 100644
index 2d8f274..0000000
--- a/src/java/com/google/android/mms/util/DrmConvertSession.java
+++ /dev/null
@@ -1,172 +0,0 @@
-/*
- * Copyright (C) 2012 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.google.android.mms.util;
-
-import android.content.Context;
-import android.drm.DrmConvertedStatus;
-import android.drm.DrmManagerClient;
-import android.util.Log;
-import android.provider.Downloads;
-
-import java.io.FileNotFoundException;
-import java.io.IOException;
-import java.io.RandomAccessFile;
-
-
-public class DrmConvertSession {
-    private DrmManagerClient mDrmClient;
-    private int mConvertSessionId;
-    private static final String TAG = "DrmConvertSession";
-
-    private DrmConvertSession(DrmManagerClient drmClient, int convertSessionId) {
-        mDrmClient = drmClient;
-        mConvertSessionId = convertSessionId;
-    }
-
-    /**
-     * Start of converting a file.
-     *
-     * @param context The context of the application running the convert session.
-     * @param mimeType Mimetype of content that shall be converted.
-     * @return A convert session or null in case an error occurs.
-     */
-    public static DrmConvertSession open(Context context, String mimeType) {
-        DrmManagerClient drmClient = null;
-        int convertSessionId = -1;
-        if (context != null && mimeType != null && !mimeType.equals("")) {
-            try {
-                drmClient = new DrmManagerClient(context);
-                try {
-                    convertSessionId = drmClient.openConvertSession(mimeType);
-                } catch (IllegalArgumentException e) {
-                    Log.w(TAG, "Conversion of Mimetype: " + mimeType
-                            + " is not supported.", e);
-                } catch (IllegalStateException e) {
-                    Log.w(TAG, "Could not access Open DrmFramework.", e);
-                }
-            } catch (IllegalArgumentException e) {
-                Log.w(TAG,
-                        "DrmManagerClient instance could not be created, context is Illegal.");
-            } catch (IllegalStateException e) {
-                Log.w(TAG, "DrmManagerClient didn't initialize properly.");
-            }
-        }
-
-        if (drmClient == null || convertSessionId < 0) {
-            return null;
-        } else {
-            return new DrmConvertSession(drmClient, convertSessionId);
-        }
-    }
-    /**
-     * Convert a buffer of data to protected format.
-     *
-     * @param buffer Buffer filled with data to convert.
-     * @param size The number of bytes that shall be converted.
-     * @return A Buffer filled with converted data, if execution is ok, in all
-     *         other case null.
-     */
-    public byte [] convert(byte[] inBuffer, int size) {
-        byte[] result = null;
-        if (inBuffer != null) {
-            DrmConvertedStatus convertedStatus = null;
-            try {
-                if (size != inBuffer.length) {
-                    byte[] buf = new byte[size];
-                    System.arraycopy(inBuffer, 0, buf, 0, size);
-                    convertedStatus = mDrmClient.convertData(mConvertSessionId, buf);
-                } else {
-                    convertedStatus = mDrmClient.convertData(mConvertSessionId, inBuffer);
-                }
-
-                if (convertedStatus != null &&
-                        convertedStatus.statusCode == DrmConvertedStatus.STATUS_OK &&
-                        convertedStatus.convertedData != null) {
-                    result = convertedStatus.convertedData;
-                }
-            } catch (IllegalArgumentException e) {
-                Log.w(TAG, "Buffer with data to convert is illegal. Convertsession: "
-                        + mConvertSessionId, e);
-            } catch (IllegalStateException e) {
-                Log.w(TAG, "Could not convert data. Convertsession: " +
-                        mConvertSessionId, e);
-            }
-        } else {
-            throw new IllegalArgumentException("Parameter inBuffer is null");
-        }
-        return result;
-    }
-
-    /**
-     * Ends a conversion session of a file.
-     *
-     * @param fileName The filename of the converted file.
-     * @return Downloads.Impl.STATUS_SUCCESS if execution is ok.
-     *         Downloads.Impl.STATUS_FILE_ERROR in case converted file can not
-     *         be accessed. Downloads.Impl.STATUS_NOT_ACCEPTABLE if a problem
-     *         occurs when accessing drm framework.
-     *         Downloads.Impl.STATUS_UNKNOWN_ERROR if a general error occurred.
-     */
-    public int close(String filename) {
-        DrmConvertedStatus convertedStatus = null;
-        int result = Downloads.Impl.STATUS_UNKNOWN_ERROR;
-        if (mDrmClient != null && mConvertSessionId >= 0) {
-            try {
-                convertedStatus = mDrmClient.closeConvertSession(mConvertSessionId);
-                if (convertedStatus == null ||
-                        convertedStatus.statusCode != DrmConvertedStatus.STATUS_OK ||
-                        convertedStatus.convertedData == null) {
-                    result = Downloads.Impl.STATUS_NOT_ACCEPTABLE;
-                } else {
-                    RandomAccessFile rndAccessFile = null;
-                    try {
-                        rndAccessFile = new RandomAccessFile(filename, "rw");
-                        rndAccessFile.seek(convertedStatus.offset);
-                        rndAccessFile.write(convertedStatus.convertedData);
-                        result = Downloads.Impl.STATUS_SUCCESS;
-                    } catch (FileNotFoundException e) {
-                        result = Downloads.Impl.STATUS_FILE_ERROR;
-                        Log.w(TAG, "File: " + filename + " could not be found.", e);
-                    } catch (IOException e) {
-                        result = Downloads.Impl.STATUS_FILE_ERROR;
-                        Log.w(TAG, "Could not access File: " + filename + " .", e);
-                    } catch (IllegalArgumentException e) {
-                        result = Downloads.Impl.STATUS_FILE_ERROR;
-                        Log.w(TAG, "Could not open file in mode: rw", e);
-                    } catch (SecurityException e) {
-                        Log.w(TAG, "Access to File: " + filename +
-                                " was denied denied by SecurityManager.", e);
-                    } finally {
-                        if (rndAccessFile != null) {
-                            try {
-                                rndAccessFile.close();
-                            } catch (IOException e) {
-                                result = Downloads.Impl.STATUS_FILE_ERROR;
-                                Log.w(TAG, "Failed to close File:" + filename
-                                        + ".", e);
-                            }
-                        }
-                    }
-                }
-            } catch (IllegalStateException e) {
-                Log.w(TAG, "Could not close convertsession. Convertsession: " +
-                        mConvertSessionId, e);
-            }
-        }
-        return result;
-    }
-}
diff --git a/src/java/com/google/android/mms/util/PduCache.java b/src/java/com/google/android/mms/util/PduCache.java
deleted file mode 100644
index de83124..0000000
--- a/src/java/com/google/android/mms/util/PduCache.java
+++ /dev/null
@@ -1,261 +0,0 @@
-/*
- * Copyright (C) 2008 Esmertec AG.
- * Copyright (C) 2008 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.google.android.mms.util;
-
-import android.content.ContentUris;
-import android.content.UriMatcher;
-import android.net.Uri;
-import android.provider.Telephony.Mms;
-import android.util.Log;
-
-import java.util.HashMap;
-import java.util.HashSet;
-
-public final class PduCache extends AbstractCache<Uri, PduCacheEntry> {
-    private static final String TAG = "PduCache";
-    private static final boolean DEBUG = false;
-    private static final boolean LOCAL_LOGV = false;
-
-    private static final int MMS_ALL             = 0;
-    private static final int MMS_ALL_ID          = 1;
-    private static final int MMS_INBOX           = 2;
-    private static final int MMS_INBOX_ID        = 3;
-    private static final int MMS_SENT            = 4;
-    private static final int MMS_SENT_ID         = 5;
-    private static final int MMS_DRAFTS          = 6;
-    private static final int MMS_DRAFTS_ID       = 7;
-    private static final int MMS_OUTBOX          = 8;
-    private static final int MMS_OUTBOX_ID       = 9;
-    private static final int MMS_CONVERSATION    = 10;
-    private static final int MMS_CONVERSATION_ID = 11;
-
-    private static final UriMatcher URI_MATCHER;
-    private static final HashMap<Integer, Integer> MATCH_TO_MSGBOX_ID_MAP;
-
-    private static PduCache sInstance;
-
-    static {
-        URI_MATCHER = new UriMatcher(UriMatcher.NO_MATCH);
-        URI_MATCHER.addURI("mms", null,         MMS_ALL);
-        URI_MATCHER.addURI("mms", "#",          MMS_ALL_ID);
-        URI_MATCHER.addURI("mms", "inbox",      MMS_INBOX);
-        URI_MATCHER.addURI("mms", "inbox/#",    MMS_INBOX_ID);
-        URI_MATCHER.addURI("mms", "sent",       MMS_SENT);
-        URI_MATCHER.addURI("mms", "sent/#",     MMS_SENT_ID);
-        URI_MATCHER.addURI("mms", "drafts",     MMS_DRAFTS);
-        URI_MATCHER.addURI("mms", "drafts/#",   MMS_DRAFTS_ID);
-        URI_MATCHER.addURI("mms", "outbox",     MMS_OUTBOX);
-        URI_MATCHER.addURI("mms", "outbox/#",   MMS_OUTBOX_ID);
-        URI_MATCHER.addURI("mms-sms", "conversations",   MMS_CONVERSATION);
-        URI_MATCHER.addURI("mms-sms", "conversations/#", MMS_CONVERSATION_ID);
-
-        MATCH_TO_MSGBOX_ID_MAP = new HashMap<Integer, Integer>();
-        MATCH_TO_MSGBOX_ID_MAP.put(MMS_INBOX,  Mms.MESSAGE_BOX_INBOX);
-        MATCH_TO_MSGBOX_ID_MAP.put(MMS_SENT,   Mms.MESSAGE_BOX_SENT);
-        MATCH_TO_MSGBOX_ID_MAP.put(MMS_DRAFTS, Mms.MESSAGE_BOX_DRAFTS);
-        MATCH_TO_MSGBOX_ID_MAP.put(MMS_OUTBOX, Mms.MESSAGE_BOX_OUTBOX);
-    }
-
-    private final HashMap<Integer, HashSet<Uri>> mMessageBoxes;
-    private final HashMap<Long, HashSet<Uri>> mThreads;
-    private final HashSet<Uri> mUpdating;
-
-    private PduCache() {
-        mMessageBoxes = new HashMap<Integer, HashSet<Uri>>();
-        mThreads = new HashMap<Long, HashSet<Uri>>();
-        mUpdating = new HashSet<Uri>();
-    }
-
-    synchronized public static final PduCache getInstance() {
-        if (sInstance == null) {
-            if (LOCAL_LOGV) {
-                Log.v(TAG, "Constructing new PduCache instance.");
-            }
-            sInstance = new PduCache();
-        }
-        return sInstance;
-    }
-
-    @Override
-    synchronized public boolean put(Uri uri, PduCacheEntry entry) {
-        int msgBoxId = entry.getMessageBox();
-        HashSet<Uri> msgBox = mMessageBoxes.get(msgBoxId);
-        if (msgBox == null) {
-            msgBox = new HashSet<Uri>();
-            mMessageBoxes.put(msgBoxId, msgBox);
-        }
-
-        long threadId = entry.getThreadId();
-        HashSet<Uri> thread = mThreads.get(threadId);
-        if (thread == null) {
-            thread = new HashSet<Uri>();
-            mThreads.put(threadId, thread);
-        }
-
-        Uri finalKey = normalizeKey(uri);
-        boolean result = super.put(finalKey, entry);
-        if (result) {
-            msgBox.add(finalKey);
-            thread.add(finalKey);
-        }
-        setUpdating(uri, false);
-        return result;
-    }
-
-    synchronized public void setUpdating(Uri uri, boolean updating) {
-        if (updating) {
-            mUpdating.add(uri);
-        } else {
-            mUpdating.remove(uri);
-        }
-    }
-
-    synchronized public boolean isUpdating(Uri uri) {
-        return mUpdating.contains(uri);
-    }
-
-    @Override
-    synchronized public PduCacheEntry purge(Uri uri) {
-        int match = URI_MATCHER.match(uri);
-        switch (match) {
-            case MMS_ALL_ID:
-                return purgeSingleEntry(uri);
-            case MMS_INBOX_ID:
-            case MMS_SENT_ID:
-            case MMS_DRAFTS_ID:
-            case MMS_OUTBOX_ID:
-                String msgId = uri.getLastPathSegment();
-                return purgeSingleEntry(Uri.withAppendedPath(Mms.CONTENT_URI, msgId));
-            // Implicit batch of purge, return null.
-            case MMS_ALL:
-            case MMS_CONVERSATION:
-                purgeAll();
-                return null;
-            case MMS_INBOX:
-            case MMS_SENT:
-            case MMS_DRAFTS:
-            case MMS_OUTBOX:
-                purgeByMessageBox(MATCH_TO_MSGBOX_ID_MAP.get(match));
-                return null;
-            case MMS_CONVERSATION_ID:
-                purgeByThreadId(ContentUris.parseId(uri));
-                return null;
-            default:
-                return null;
-        }
-    }
-
-    private PduCacheEntry purgeSingleEntry(Uri key) {
-        mUpdating.remove(key);
-        PduCacheEntry entry = super.purge(key);
-        if (entry != null) {
-            removeFromThreads(key, entry);
-            removeFromMessageBoxes(key, entry);
-            return entry;
-        }
-        return null;
-    }
-
-    @Override
-    synchronized public void purgeAll() {
-        super.purgeAll();
-
-        mMessageBoxes.clear();
-        mThreads.clear();
-        mUpdating.clear();
-    }
-
-    /**
-     * @param uri The Uri to be normalized.
-     * @return Uri The normalized key of cached entry.
-     */
-    private Uri normalizeKey(Uri uri) {
-        int match = URI_MATCHER.match(uri);
-        Uri normalizedKey = null;
-
-        switch (match) {
-            case MMS_ALL_ID:
-                normalizedKey = uri;
-                break;
-            case MMS_INBOX_ID:
-            case MMS_SENT_ID:
-            case MMS_DRAFTS_ID:
-            case MMS_OUTBOX_ID:
-                String msgId = uri.getLastPathSegment();
-                normalizedKey = Uri.withAppendedPath(Mms.CONTENT_URI, msgId);
-                break;
-            default:
-                return null;
-        }
-
-        if (LOCAL_LOGV) {
-            Log.v(TAG, uri + " -> " + normalizedKey);
-        }
-        return normalizedKey;
-    }
-
-    private void purgeByMessageBox(Integer msgBoxId) {
-        if (LOCAL_LOGV) {
-            Log.v(TAG, "Purge cache in message box: " + msgBoxId);
-        }
-
-        if (msgBoxId != null) {
-            HashSet<Uri> msgBox = mMessageBoxes.remove(msgBoxId);
-            if (msgBox != null) {
-                for (Uri key : msgBox) {
-                    mUpdating.remove(key);
-                    PduCacheEntry entry = super.purge(key);
-                    if (entry != null) {
-                        removeFromThreads(key, entry);
-                    }
-                }
-            }
-        }
-    }
-
-    private void removeFromThreads(Uri key, PduCacheEntry entry) {
-        HashSet<Uri> thread = mThreads.get(entry.getThreadId());
-        if (thread != null) {
-            thread.remove(key);
-        }
-    }
-
-    private void purgeByThreadId(long threadId) {
-        if (LOCAL_LOGV) {
-            Log.v(TAG, "Purge cache in thread: " + threadId);
-        }
-
-        HashSet<Uri> thread = mThreads.remove(threadId);
-        if (thread != null) {
-            for (Uri key : thread) {
-                mUpdating.remove(key);
-                PduCacheEntry entry = super.purge(key);
-                if (entry != null) {
-                    removeFromMessageBoxes(key, entry);
-                }
-            }
-        }
-    }
-
-    private void removeFromMessageBoxes(Uri key, PduCacheEntry entry) {
-        HashSet<Uri> msgBox = mThreads.get(Long.valueOf(entry.getMessageBox()));
-        if (msgBox != null) {
-            msgBox.remove(key);
-        }
-    }
-}
diff --git a/src/java/com/google/android/mms/util/PduCacheEntry.java b/src/java/com/google/android/mms/util/PduCacheEntry.java
deleted file mode 100644
index 8b41386..0000000
--- a/src/java/com/google/android/mms/util/PduCacheEntry.java
+++ /dev/null
@@ -1,44 +0,0 @@
-/*
- * Copyright (C) 2008 Esmertec AG.
- * Copyright (C) 2008 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.google.android.mms.util;
-
-import com.google.android.mms.pdu.GenericPdu;
-
-public final class PduCacheEntry {
-    private final GenericPdu mPdu;
-    private final int mMessageBox;
-    private final long mThreadId;
-
-    public PduCacheEntry(GenericPdu pdu, int msgBox, long threadId) {
-        mPdu = pdu;
-        mMessageBox = msgBox;
-        mThreadId = threadId;
-    }
-
-    public GenericPdu getPdu() {
-        return mPdu;
-    }
-
-    public int getMessageBox() {
-        return mMessageBox;
-    }
-
-    public long getThreadId() {
-        return mThreadId;
-    }
-}
diff --git a/src/java/com/google/android/mms/util/SqliteWrapper.java b/src/java/com/google/android/mms/util/SqliteWrapper.java
deleted file mode 100644
index bcdac22..0000000
--- a/src/java/com/google/android/mms/util/SqliteWrapper.java
+++ /dev/null
@@ -1,120 +0,0 @@
-/*
- * Copyright (C) 2008 Esmertec AG.
- * Copyright (C) 2008 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.google.android.mms.util;
-
-import android.app.ActivityManager;
-import android.content.ContentResolver;
-import android.content.ContentValues;
-import android.content.Context;
-import android.database.Cursor;
-import android.database.sqlite.SQLiteException;
-import android.net.Uri;
-import android.util.Log;
-import android.widget.Toast;
-
-public final class SqliteWrapper {
-    private static final String TAG = "SqliteWrapper";
-    private static final String SQLITE_EXCEPTION_DETAIL_MESSAGE
-                = "unable to open database file";
-
-    private SqliteWrapper() {
-        // Forbidden being instantiated.
-    }
-
-    // FIXME: It looks like outInfo.lowMemory does not work well as we expected.
-    // after run command: adb shell fillup -p 100, outInfo.lowMemory is still false.
-    private static boolean isLowMemory(Context context) {
-        if (null == context) {
-            return false;
-        }
-
-        ActivityManager am = (ActivityManager)
-                        context.getSystemService(Context.ACTIVITY_SERVICE);
-        ActivityManager.MemoryInfo outInfo = new ActivityManager.MemoryInfo();
-        am.getMemoryInfo(outInfo);
-
-        return outInfo.lowMemory;
-    }
-
-    // FIXME: need to optimize this method.
-    private static boolean isLowMemory(SQLiteException e) {
-        return e.getMessage().equals(SQLITE_EXCEPTION_DETAIL_MESSAGE);
-    }
-
-    public static void checkSQLiteException(Context context, SQLiteException e) {
-        if (isLowMemory(e)) {
-            Toast.makeText(context, com.android.internal.R.string.low_memory,
-                    Toast.LENGTH_SHORT).show();
-        } else {
-            throw e;
-        }
-    }
-
-    public static Cursor query(Context context, ContentResolver resolver, Uri uri,
-            String[] projection, String selection, String[] selectionArgs, String sortOrder) {
-        try {
-            return resolver.query(uri, projection, selection, selectionArgs, sortOrder);
-        } catch (SQLiteException e) {
-            Log.e(TAG, "Catch a SQLiteException when query: ", e);
-            checkSQLiteException(context, e);
-            return null;
-        }
-    }
-
-    public static boolean requery(Context context, Cursor cursor) {
-        try {
-            return cursor.requery();
-        } catch (SQLiteException e) {
-            Log.e(TAG, "Catch a SQLiteException when requery: ", e);
-            checkSQLiteException(context, e);
-            return false;
-        }
-    }
-    public static int update(Context context, ContentResolver resolver, Uri uri,
-            ContentValues values, String where, String[] selectionArgs) {
-        try {
-            return resolver.update(uri, values, where, selectionArgs);
-        } catch (SQLiteException e) {
-            Log.e(TAG, "Catch a SQLiteException when update: ", e);
-            checkSQLiteException(context, e);
-            return -1;
-        }
-    }
-
-    public static int delete(Context context, ContentResolver resolver, Uri uri,
-            String where, String[] selectionArgs) {
-        try {
-            return resolver.delete(uri, where, selectionArgs);
-        } catch (SQLiteException e) {
-            Log.e(TAG, "Catch a SQLiteException when delete: ", e);
-            checkSQLiteException(context, e);
-            return -1;
-        }
-    }
-
-    public static Uri insert(Context context, ContentResolver resolver,
-            Uri uri, ContentValues values) {
-        try {
-            return resolver.insert(uri, values);
-        } catch (SQLiteException e) {
-            Log.e(TAG, "Catch a SQLiteException when insert: ", e);
-            checkSQLiteException(context, e);
-            return null;
-        }
-    }
-}
diff --git a/src/java/com/google/android/mms/util/package.html b/src/java/com/google/android/mms/util/package.html
deleted file mode 100755
index c9f96a6..0000000
--- a/src/java/com/google/android/mms/util/package.html
+++ /dev/null
@@ -1,5 +0,0 @@
-<body>
-
-{@hide}
-
-</body>
diff --git a/telephony-resources/TelephonyResource.java b/telephony-resources/TelephonyResource.java
new file mode 100644
index 0000000..d048bb0
--- /dev/null
+++ b/telephony-resources/TelephonyResource.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2020 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.telephony.resources;
+
+/**
+ * A dummy class to force pick the R.class to apk
+ */
+final class TelephonyResource {
+    TelephonyResource() {
+    }
+}
diff --git a/tests/telephonytests/Android.bp b/tests/telephonytests/Android.bp
index 0978083..52cd7c5 100644
--- a/tests/telephonytests/Android.bp
+++ b/tests/telephonytests/Android.bp
@@ -8,6 +8,7 @@
         "android.test.mock",
         "android.test.runner",
         "ims-common",
+        "unsupportedappusage",
     ],
 
     static_libs: [
@@ -15,13 +16,31 @@
         "frameworks-base-testutils",
         "guava",
         "mockito-target-minus-junit4",
+        "net-tests-utils",
         "platform-test-annotations",
         "services.core",
         "services.net",
         "telephony-common",
         "truth-prebuilt",
+        "testables",
     ],
 
     platform_apis: true,
-    test_suites: ["device-tests"],
+    jarjar_rules: ":jarjar-rules-telephony-tests",
+
+    test_suites: [
+        "device-tests",
+    ],
+}
+
+genrule {
+    name: "jarjar-rules-telephony-tests",
+    srcs: [
+        // Order matters: test rules override the base ones
+        "jarjar-rules-tests.txt",
+        ":jarjar-rules-shared",
+    ],
+    out: ["jarjar-rules-telephony-tests-combined.txt"],
+    cmd: "cat $(in) > $(out)",
+    visibility: ["//visibility:private"],
 }
diff --git a/tests/telephonytests/assets/eccdata b/tests/telephonytests/assets/eccdata
new file mode 100644
index 0000000..2bce958
--- /dev/null
+++ b/tests/telephonytests/assets/eccdata
Binary files differ
diff --git a/tests/telephonytests/jarjar-rules-tests.txt b/tests/telephonytests/jarjar-rules-tests.txt
new file mode 100644
index 0000000..91b5b32
--- /dev/null
+++ b/tests/telephonytests/jarjar-rules-tests.txt
@@ -0,0 +1,6 @@
+# NetworkFactory is included in telephony-common and services.net
+# Rename the non-jarjared, services.net version to avoid conflicts.
+# This causes two NetworkFactory classes to appear in the test package (one used
+# by services-net and one by telephony-common), similarly to what happens on a
+# real device, except that in the test they have different package names.
+rule android.net.NetworkFactory* android.net.services.NetworkFactory@1
diff --git a/tests/telephonytests/src/android/telephony/ims/ImsCallSessionListenerTests.java b/tests/telephonytests/src/android/telephony/ims/ImsCallSessionListenerTests.java
new file mode 100644
index 0000000..99461c1
--- /dev/null
+++ b/tests/telephonytests/src/android/telephony/ims/ImsCallSessionListenerTests.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2020 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 android.telephony.ims;
+
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.verify;
+
+import android.telephony.ServiceState;
+import android.telephony.TelephonyManager;
+import android.telephony.ims.aidl.IImsCallSessionListener;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@RunWith(AndroidJUnit4.class)
+public class ImsCallSessionListenerTests {
+
+    @Mock
+    IImsCallSessionListener mMockListener;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+    }
+
+    @Test
+    public void testListenerMayHandoverDeprecated() throws Exception {
+        ImsCallSessionListener mTestListener = new ImsCallSessionListener(mMockListener);
+        mTestListener.callSessionMayHandover(ServiceState.RIL_RADIO_TECHNOLOGY_LTE,
+                ServiceState.RIL_RADIO_TECHNOLOGY_IWLAN);
+        // verify we get the correct network type equivalent of this param.
+        verify(mMockListener).callSessionMayHandover(TelephonyManager.NETWORK_TYPE_LTE,
+                TelephonyManager.NETWORK_TYPE_IWLAN);
+    }
+
+    @Test
+    public void testListenerHandoverDeprecated() throws Exception {
+        ImsReasonInfo imsReasonInfo = new ImsReasonInfo();
+        ImsCallSessionListener mTestListener = new ImsCallSessionListener(mMockListener);
+        mTestListener.callSessionHandover(ServiceState.RIL_RADIO_TECHNOLOGY_LTE,
+                ServiceState.RIL_RADIO_TECHNOLOGY_IWLAN, imsReasonInfo);
+        // verify we get the correct network type equivalent of this param.
+        verify(mMockListener).callSessionHandover(eq(TelephonyManager.NETWORK_TYPE_LTE),
+                eq(TelephonyManager.NETWORK_TYPE_IWLAN), eq(imsReasonInfo));
+    }
+
+    @Test
+    public void testListenerHandoverFailedDeprecated() throws Exception {
+        ImsReasonInfo imsReasonInfo = new ImsReasonInfo(
+                ImsReasonInfo.CODE_REJECT_ONGOING_HANDOVER, 0 /*extraCode*/);
+        ImsCallSessionListener mTestListener = new ImsCallSessionListener(mMockListener);
+        mTestListener.callSessionHandoverFailed(ServiceState.RIL_RADIO_TECHNOLOGY_LTE,
+                ServiceState.RIL_RADIO_TECHNOLOGY_IWLAN, imsReasonInfo);
+        // verify we get the correct network type equivalent of this param.
+        verify(mMockListener).callSessionHandoverFailed(eq(TelephonyManager.NETWORK_TYPE_LTE),
+                eq(TelephonyManager.NETWORK_TYPE_IWLAN), eq(imsReasonInfo));
+    }
+
+}
diff --git a/tests/telephonytests/src/android/telephony/ims/ImsFeatureTest.java b/tests/telephonytests/src/android/telephony/ims/ImsFeatureTest.java
index 2695e4c..319d200 100644
--- a/tests/telephonytests/src/android/telephony/ims/ImsFeatureTest.java
+++ b/tests/telephonytests/src/android/telephony/ims/ImsFeatureTest.java
@@ -20,8 +20,10 @@
 import static junit.framework.Assert.assertTrue;
 
 import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.verify;
 
+import android.os.IBinder;
 import android.os.Parcel;
 import android.os.RemoteException;
 import android.telephony.ims.aidl.IImsCapabilityCallback;
@@ -71,12 +73,19 @@
     @Mock
     private IImsFeatureStatusCallback mTestStatusCallback;
     @Mock
+    private IBinder mTestBinder;
+    @Mock
     private IImsFeatureStatusCallback mTestStatusCallback2;
+    @Mock
+    private IBinder mTestBinder2;
 
     @Before
     public void setUp() {
         MockitoAnnotations.initMocks(this);
         mTestImsFeature = new TestImsFeature();
+        // Needed for RemoteCallbackList registration
+        doReturn(mTestBinder).when(mTestStatusCallback).asBinder();
+        doReturn(mTestBinder2).when(mTestStatusCallback2).asBinder();
     }
 
     @After
diff --git a/tests/telephonytests/src/android/telephony/ims/ImsServiceTest.java b/tests/telephonytests/src/android/telephony/ims/ImsServiceTest.java
index ec4bf33..80cae94 100644
--- a/tests/telephonytests/src/android/telephony/ims/ImsServiceTest.java
+++ b/tests/telephonytests/src/android/telephony/ims/ImsServiceTest.java
@@ -24,9 +24,11 @@
 import static org.mockito.Matchers.eq;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
 
 import android.content.Context;
 import android.content.Intent;
+import android.os.IBinder;
 import android.os.RemoteException;
 import android.telephony.ims.aidl.IImsMmTelFeature;
 import android.telephony.ims.aidl.IImsServiceController;
@@ -59,11 +61,14 @@
 
     private Context mMockContext;
     private IImsFeatureStatusCallback mTestCallback;
+    private IBinder mImsFeatureStatusCallbackBinder;
 
     @Before
     public void setUp() throws Exception {
         mMockContext = mock(Context.class);
         mTestCallback = mock(IImsFeatureStatusCallback.class);
+        mImsFeatureStatusCallbackBinder = mock(IBinder.class);
+        when(mTestCallback.asBinder()).thenReturn(mImsFeatureStatusCallbackBinder);
         mTestImsService = new TestImsService(mMockContext);
         mTestImsServiceBinder = (IImsServiceController) mTestImsService.onBind(
                 new Intent(ImsService.SERVICE_INTERFACE));
diff --git a/tests/telephonytests/src/android/telephony/ims/TestImsFeature.java b/tests/telephonytests/src/android/telephony/ims/TestImsFeature.java
index ec09b4a..5f9327e 100644
--- a/tests/telephonytests/src/android/telephony/ims/TestImsFeature.java
+++ b/tests/telephonytests/src/android/telephony/ims/TestImsFeature.java
@@ -40,6 +40,11 @@
     }
 
     @Override
+    public boolean queryCapabilityConfiguration(int capability, int radioTech) {
+        return false;
+    }
+
+    @Override
     public void changeEnabledCapabilities(CapabilityChangeRequest request,
             CapabilityCallbackProxy c) {
         lastRequest = request;
diff --git a/tests/telephonytests/src/com/android/internal/telephony/AdnRecordTest.java b/tests/telephonytests/src/com/android/internal/telephony/AdnRecordTest.java
index d647ae1..3b786b0 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/AdnRecordTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/AdnRecordTest.java
@@ -16,6 +16,8 @@
 
 package com.android.internal.telephony;
 
+import android.os.Parcel;
+import java.util.Arrays;
 import junit.framework.TestCase;
 import android.test.suitebuilder.annotation.SmallTest;
 
@@ -174,6 +176,19 @@
         assertEquals("+18885551212,12345678", adn.getNumber());
         assertFalse(adn.isEmpty());
     }
+
+    @SmallTest
+    public void testParcelUnParcel() throws Exception {
+        AdnRecord adn = new AdnRecord(0,0,"Voice Mail",
+                "+18056377243", new String[]{"adc@email.com"});
+        Parcel p = Parcel.obtain();
+        adn.writeToParcel(p, 0);
+        p.setDataPosition(0);
+        AdnRecord copy = AdnRecord.CREATOR.createFromParcel(p);
+        assertEquals(adn.getAlphaTag(), copy.getAlphaTag());
+        assertEquals(adn.getNumber(), copy.getNumber());
+        assertTrue(Arrays.equals(adn.getEmails(), copy.getEmails()));
+    }
 }
 
 
diff --git a/tests/telephonytests/src/com/android/internal/telephony/BarringInfoTest.java b/tests/telephonytests/src/com/android/internal/telephony/BarringInfoTest.java
new file mode 100644
index 0000000..6720b41
--- /dev/null
+++ b/tests/telephonytests/src/com/android/internal/telephony/BarringInfoTest.java
@@ -0,0 +1,147 @@
+/*
+ * Copyright (C) 2020 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;
+
+import static android.telephony.BarringInfo.BarringServiceInfo;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import android.os.Parcel;
+import android.telephony.BarringInfo;
+import android.util.SparseArray;
+
+import org.junit.Test;
+
+/** Unit test for {@link android.telephony.BarringInfo}. */
+public class BarringInfoTest {
+
+    public static final int CONDITIONAL_BARRING_TIME_SECONDS = 20;
+    public static final int CONDITIONAL_BARRING_FACTOR_PERCENT = 50;
+
+
+    private static final int[] sServices = new int[] {
+            BarringInfo.BARRING_SERVICE_TYPE_CS_SERVICE,
+            BarringInfo.BARRING_SERVICE_TYPE_PS_SERVICE,
+            BarringInfo.BARRING_SERVICE_TYPE_CS_VOICE,
+            BarringInfo.BARRING_SERVICE_TYPE_MO_SIGNALLING,
+            BarringInfo.BARRING_SERVICE_TYPE_MO_DATA,
+            BarringInfo.BARRING_SERVICE_TYPE_CS_FALLBACK,
+            BarringInfo.BARRING_SERVICE_TYPE_MMTEL_VOICE,
+            BarringInfo.BARRING_SERVICE_TYPE_MMTEL_VIDEO,
+            BarringInfo.BARRING_SERVICE_TYPE_EMERGENCY,
+            BarringInfo.BARRING_SERVICE_TYPE_SMS,
+    };
+
+    /** Return a dummy set of barring info */
+    private static SparseArray<BarringServiceInfo> getBarringServiceInfos() {
+        return getBarringServiceInfos(false);
+    }
+
+    /** Return a dummy set of barring info
+     *
+     * @param isConditionallyBarred set the flag for whether the conditionally barred service has
+     *        been evaluated and is actually barred based on the conditional barring parameters.
+     */
+    private static SparseArray<BarringServiceInfo>
+            getBarringServiceInfos(boolean isConditionallyBarred) {
+        SparseArray<BarringServiceInfo> serviceInfos = new SparseArray<>();
+        serviceInfos.put(BarringInfo.BARRING_SERVICE_TYPE_MO_DATA,
+                new BarringServiceInfo(BarringServiceInfo.BARRING_TYPE_UNCONDITIONAL, false, 0, 0));
+        serviceInfos.put(BarringInfo.BARRING_SERVICE_TYPE_MMTEL_VIDEO,
+                new BarringServiceInfo(
+                        BarringServiceInfo.BARRING_TYPE_CONDITIONAL, isConditionallyBarred,
+                        CONDITIONAL_BARRING_FACTOR_PERCENT, CONDITIONAL_BARRING_TIME_SECONDS));
+        return serviceInfos;
+    }
+
+    /** Test that parceling works correctly */
+    @Test
+    public void testParcel() {
+        BarringInfo info = new BarringInfo(null, getBarringServiceInfos());
+
+        Parcel parcel = Parcel.obtain();
+        info.writeToParcel(parcel, 0 /* flags */);
+        parcel.setDataPosition(0);
+
+        BarringInfo fromParcel = BarringInfo.CREATOR.createFromParcel(parcel);
+
+        assertEquals(fromParcel, info);
+    }
+
+    /** Test that an empty constructor returns valid barring service info that's all not barred */
+    @Test
+    public void testEmptyConstructor() {
+        BarringInfo b = new BarringInfo();
+        for (int service : sServices) {
+            BarringServiceInfo bsi = b.getBarringServiceInfo(service);
+            assertNotNull(bsi);
+            assertEquals(bsi.getBarringType(), BarringServiceInfo.BARRING_TYPE_UNKNOWN);
+            assertFalse(bsi.isBarred());
+        }
+    }
+
+    /** Test that barring service info is stored properly by the constructor */
+    @Test
+    public void testBarringService() {
+        BarringInfo b = new BarringInfo(null, getBarringServiceInfos());
+
+        // Check that the MO data barring info matches the info provided in getBarringServiceInfos()
+        BarringServiceInfo bsi = b.getBarringServiceInfo(BarringInfo.BARRING_SERVICE_TYPE_MO_DATA);
+        assertEquals(bsi.getBarringType(), BarringServiceInfo.BARRING_TYPE_UNCONDITIONAL);
+
+        // Check that the MMTEL barring info matches the info provided in getBarringServiceInfos()
+        bsi = b.getBarringServiceInfo(BarringInfo.BARRING_SERVICE_TYPE_MMTEL_VIDEO);
+        assertEquals(bsi.getBarringType(), BarringServiceInfo.BARRING_TYPE_CONDITIONAL);
+        assertFalse(bsi.isConditionallyBarred());
+        assertEquals(bsi.getConditionalBarringFactor(), CONDITIONAL_BARRING_FACTOR_PERCENT);
+        assertEquals(bsi.getConditionalBarringTimeSeconds(), CONDITIONAL_BARRING_TIME_SECONDS);
+
+        // Because BarringInfo is available, services that aren't reported as barred are
+        // automatically reported as unbarred.
+        bsi = b.getBarringServiceInfo(BarringInfo.BARRING_SERVICE_TYPE_SMS);
+        assertEquals(bsi.getBarringType(), BarringServiceInfo.BARRING_TYPE_NONE);
+        assertFalse(bsi.isConditionallyBarred());
+        assertEquals(bsi.getConditionalBarringFactor(), 0);
+        assertEquals(bsi.getConditionalBarringTimeSeconds(), 0);
+    }
+
+    /** Test that equality checks are correctly implemented */
+    @Test
+    public void testEquals() {
+        BarringInfo lhs = new BarringInfo(null, getBarringServiceInfos(false));
+        BarringInfo rhs = new BarringInfo(null, getBarringServiceInfos(false));
+        assertEquals(lhs, rhs);
+
+        rhs = new BarringInfo(null, getBarringServiceInfos(true));
+        assertNotEquals(lhs, rhs);
+    }
+
+    /** Test that when conditional barring is active, the service is considered barred */
+    @Test
+    public void testConditionalBarringCheck() {
+        BarringInfo condInfo = new BarringInfo(null, getBarringServiceInfos(false));
+        assertFalse(condInfo.getBarringServiceInfo(BarringInfo.BARRING_SERVICE_TYPE_MMTEL_VIDEO)
+                .isBarred());
+
+        condInfo = new BarringInfo(null, getBarringServiceInfos(true));
+        assertTrue(condInfo.getBarringServiceInfo(BarringInfo.BARRING_SERVICE_TYPE_MMTEL_VIDEO)
+                .isBarred());
+    }
+}
diff --git a/tests/telephonytests/src/com/android/internal/telephony/CallManagerTest.java b/tests/telephonytests/src/com/android/internal/telephony/CallManagerTest.java
index c05d4be..67587d4 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/CallManagerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/CallManagerTest.java
@@ -32,20 +32,24 @@
 import static org.mockito.Mockito.verify;
 
 import android.os.Handler;
-import android.os.HandlerThread;
 import android.os.Message;
 import android.telephony.PhoneNumberUtils;
 import android.telephony.ServiceState;
 import android.test.suitebuilder.annotation.SmallTest;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
 
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
+import org.junit.runner.RunWith;
 import org.mockito.ArgumentCaptor;
 import org.mockito.Mock;
 
 import java.lang.reflect.Field;
 
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper
 public class CallManagerTest extends TelephonyTest {
 
     @Mock
@@ -57,43 +61,6 @@
     @Mock
     Phone mSecondPhone;
 
-    private CallManagerHandlerThread mCallManagerHandlerThread;
-    private Handler mHandler;
-    private static final int PHONE_REGISTER_EVENT = 0;
-
-    private class CallManagerHandlerThread extends HandlerThread {
-        private CallManagerHandlerThread(String name) {
-            super(name);
-        }
-        @Override
-        public void onLooperPrepared() {
-            /* CallManager is a static object with private constructor,no need call constructor */
-            registerForPhone(mPhone);
-
-            // create a custom handler for the Handler Thread
-            mHandler = new Handler(mCallManagerHandlerThread.getLooper()) {
-                @Override
-                public void handleMessage(Message msg) {
-                    switch (msg.what) {
-                        case PHONE_REGISTER_EVENT:
-                            logd("Phone registered with CallManager");
-                            registerForPhone((Phone) msg.obj);
-                            setReady(true);
-                            break;
-                        default:
-                            logd("Unknown Event " + msg.what);
-                    }
-                }
-            };
-
-            setReady(true);
-        }
-
-        private void registerForPhone(Phone mPhone) {
-            CallManager.getInstance().registerPhone(mPhone);
-        }
-    }
-
     @Before
     public void setUp() throws Exception {
         super.setUp(this.getClass().getSimpleName());
@@ -113,15 +80,14 @@
         doReturn(true).when(mFgCall).isIdle();
         doReturn(true).when(mRingingCall).isIdle();
 
-        mCallManagerHandlerThread = new CallManagerHandlerThread(TAG);
-        mCallManagerHandlerThread.start();
-        waitUntilReady();
+        /* CallManager is a static object with private constructor; no need to call constructor */
+        CallManager.getInstance().registerPhone(mPhone);
+        processAllMessages();
     }
 
     @After
     public void tearDown() throws Exception {
         CallManager.getInstance().unregisterPhone(mPhone);
-        mCallManagerHandlerThread.quit();
         super.tearDown();
     }
 
@@ -271,12 +237,9 @@
         ServiceState mSecondServiceState = mock(ServiceState.class);
         doReturn(mSecondServiceState).when(mSecondPhone).getServiceState();
 
-        Message mRegisterPhone = mHandler.obtainMessage(PHONE_REGISTER_EVENT,
-                mSecondPhone);
-        setReady(false);
-        mRegisterPhone.sendToTarget();
-
-        waitUntilReady();
+        logd("Phone registered with CallManager");
+        CallManager.getInstance().registerPhone(mSecondPhone);
+        processAllMessages();
 
         // mPhone: STATE_IN_SERVICE > mPhoneSecond: state STATE_OUT_OF_SERVICE
         doReturn(ServiceState.STATE_OUT_OF_SERVICE).when(mSecondServiceState).getState();
diff --git a/tests/telephonytests/src/com/android/internal/telephony/CallQualityTest.java b/tests/telephonytests/src/com/android/internal/telephony/CallQualityTest.java
new file mode 100644
index 0000000..1a6bb51
--- /dev/null
+++ b/tests/telephonytests/src/com/android/internal/telephony/CallQualityTest.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2019 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;
+
+import static org.junit.Assert.assertEquals;
+
+import android.os.Parcel;
+import android.telephony.CallQuality;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Simple unit test verifying the parceling and unparceling of CallQuality.
+ */
+@RunWith(AndroidJUnit4.class)
+public class CallQualityTest {
+
+    @SmallTest
+    @Test
+    public void testParcelUnparcelCallQuality() {
+        CallQuality quality = new CallQuality(
+                1 /* downlinkCallQualityLevel */,
+                2 /* uplinkCallQualityLevel */,
+                4000 /* callDuration */,
+                500 /* numRtpPacketsTransmitted */,
+                600 /* numRtpPacketsReceived */,
+                70 /* numRtpPacketsTransmittedLost */,
+                42 /* numRtpPacketsNotReceived */,
+                30 /* averageRelativeJitter */,
+                40 /* maxRelativeJitter */,
+                100 /* averageRoundTripTime */,
+                1 /* codecType)  */);
+
+        Parcel parcel = Parcel.obtain();
+        quality.writeToParcel(parcel, 0);
+        parcel.setDataPosition(0);
+        CallQuality unparceledData = CallQuality.CREATOR.createFromParcel(parcel);
+        parcel.recycle();
+
+        assertEquals("CallQuality is not equal after parceled/unparceled", quality, unparceledData);
+    }
+}
diff --git a/tests/telephonytests/src/com/android/internal/telephony/CarrierActionAgentTest.java b/tests/telephonytests/src/com/android/internal/telephony/CarrierActionAgentTest.java
index bef29e2..6371c06 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/CarrierActionAgentTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/CarrierActionAgentTest.java
@@ -26,26 +26,28 @@
 import android.net.Uri;
 import android.os.AsyncResult;
 import android.os.Handler;
-import android.os.HandlerThread;
 import android.os.Message;
 import android.provider.Settings;
 import android.provider.Telephony;
 import android.test.mock.MockContentResolver;
 import android.test.suitebuilder.annotation.SmallTest;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
 
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
+import org.junit.runner.RunWith;
 import org.mockito.ArgumentCaptor;
 import org.mockito.Mock;
 
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper
 public class CarrierActionAgentTest extends TelephonyTest {
     private CarrierActionAgent mCarrierActionAgentUT;
     private FakeContentResolver mFakeContentResolver;
     private static int DATA_CARRIER_ACTION_EVENT = 0;
     private static int RADIO_CARRIER_ACTION_EVENT = 1;
-    private static int TEST_TIMEOUT = 5000;
-    private CarrierActionAgentHandler mCarrierActionAgentHandler;
     @Mock
     private Handler mDataActionHandler;
     @Mock
@@ -53,8 +55,8 @@
 
     private class FakeContentResolver extends MockContentResolver {
         @Override
-        public void notifyChange(Uri uri, ContentObserver observer, boolean syncToNetwork) {
-            super.notifyChange(uri, observer, syncToNetwork);
+        public void notifyChange(Uri uri, ContentObserver observer) {
+            super.notifyChange(uri, observer);
             logd("onChanged(uri=" + uri + ")" + observer);
             if (observer != null) {
                 observer.dispatchChange(false, uri);
@@ -64,34 +66,20 @@
         }
     }
 
-    private class CarrierActionAgentHandler extends HandlerThread {
-
-        private CarrierActionAgentHandler(String name) {
-            super(name);
-        }
-
-        @Override
-        public void onLooperPrepared() {
-            mCarrierActionAgentUT = new CarrierActionAgent(mPhone);
-            mCarrierActionAgentUT.registerForCarrierAction(
-                    CarrierActionAgent.CARRIER_ACTION_SET_METERED_APNS_ENABLED, mDataActionHandler,
-                    DATA_CARRIER_ACTION_EVENT, null, false);
-            mCarrierActionAgentUT.registerForCarrierAction(
-                    CarrierActionAgent.CARRIER_ACTION_SET_RADIO_ENABLED, mRadioActionHandler,
-                    RADIO_CARRIER_ACTION_EVENT, null, false);
-            setReady(true);
-        }
-    }
-
     @Before
     public void setUp() throws Exception {
         logd("CarrierActionAgentTest +Setup!");
         super.setUp(getClass().getSimpleName());
         mFakeContentResolver = new FakeContentResolver();
         doReturn(mFakeContentResolver).when(mContext).getContentResolver();
-        mCarrierActionAgentHandler = new CarrierActionAgentHandler(getClass().getSimpleName());
-        mCarrierActionAgentHandler.start();
-        waitUntilReady();
+        mCarrierActionAgentUT = new CarrierActionAgent(mPhone);
+        mCarrierActionAgentUT.registerForCarrierAction(
+                CarrierActionAgent.CARRIER_ACTION_SET_METERED_APNS_ENABLED, mDataActionHandler,
+                DATA_CARRIER_ACTION_EVENT, null, false);
+        mCarrierActionAgentUT.registerForCarrierAction(
+                CarrierActionAgent.CARRIER_ACTION_SET_RADIO_ENABLED, mRadioActionHandler,
+                RADIO_CARRIER_ACTION_EVENT, null, false);
+        processAllMessages();
         logd("CarrierActionAgentTest -Setup!");
     }
 
@@ -103,7 +91,7 @@
         intent.putExtra(IccCardConstants.INTENT_KEY_ICC_STATE,
                 IccCardConstants.INTENT_VALUE_ICC_LOADED);
         mContext.sendBroadcast(intent);
-        waitForHandlerAction(mCarrierActionAgentUT, TEST_TIMEOUT);
+        processAllMessages();
 
         // no carrier actions triggered from sim loading since there are same as the current one
         ArgumentCaptor<Message> message = ArgumentCaptor.forClass(Message.class);
@@ -113,8 +101,7 @@
         // disable metered apns and radio
         mCarrierActionAgentUT.carrierActionSetRadioEnabled(false);
         mCarrierActionAgentUT.carrierActionSetMeteredApnsEnabled(false);
-        waitForHandlerAction(mCarrierActionAgentUT, TEST_TIMEOUT);
-        waitForHandlerAction(mCarrierActionAgentUT, TEST_TIMEOUT);
+        processAllMessages();
         verify(mDataActionHandler, times(1)).sendMessageAtTime(message.capture(), anyLong());
         assertEquals(DATA_CARRIER_ACTION_EVENT, message.getValue().what);
         assertEquals(false, ((AsyncResult) message.getValue().obj).result);
@@ -126,9 +113,7 @@
         Settings.Global.putInt(mFakeContentResolver, Settings.Global.AIRPLANE_MODE_ON, 1);
         mFakeContentResolver.notifyChange(
                 Settings.Global.getUriFor(Settings.Global.AIRPLANE_MODE_ON), null);
-        waitForHandlerAction(mCarrierActionAgentUT, TEST_TIMEOUT);
-        waitForHandlerAction(mCarrierActionAgentUT, TEST_TIMEOUT);
-        waitForHandlerAction(mCarrierActionAgentUT, TEST_TIMEOUT);
+        processAllMessages();
 
         // carrier actions triggered from APM
         verify(mDataActionHandler, times(2)).sendMessageAtTime(message.capture(), anyLong());
@@ -148,7 +133,7 @@
         intent.putExtra(IccCardConstants.INTENT_KEY_ICC_STATE,
                 IccCardConstants.INTENT_VALUE_ICC_LOADED);
         mContext.sendBroadcast(intent);
-        waitForHandlerAction(mCarrierActionAgentUT, TEST_TIMEOUT);
+        processAllMessages();
 
         // no carrier actions triggered from sim loading since there are same as the current one
         ArgumentCaptor<Message> message = ArgumentCaptor.forClass(Message.class);
@@ -158,8 +143,7 @@
         // disable metered apns and radio
         mCarrierActionAgentUT.carrierActionSetRadioEnabled(false);
         mCarrierActionAgentUT.carrierActionSetMeteredApnsEnabled(false);
-        waitForHandlerAction(mCarrierActionAgentUT, TEST_TIMEOUT);
-        waitForHandlerAction(mCarrierActionAgentUT, TEST_TIMEOUT);
+        processAllMessages();
 
         verify(mDataActionHandler, times(1)).sendMessageAtTime(message.capture(), anyLong());
         assertEquals(DATA_CARRIER_ACTION_EVENT, message.getValue().what);
@@ -171,9 +155,7 @@
 
         // Simulate APN change
         mFakeContentResolver.notifyChange(Telephony.Carriers.CONTENT_URI, null);
-        waitForHandlerAction(mCarrierActionAgentUT, TEST_TIMEOUT);
-        waitForHandlerAction(mCarrierActionAgentUT, TEST_TIMEOUT);
-        waitForHandlerAction(mCarrierActionAgentUT, TEST_TIMEOUT);
+        processAllMessages();
 
         // Carrier actions triggered from APN change
         verify(mDataActionHandler, times(2)).sendMessageAtTime(message.capture(), anyLong());
@@ -188,7 +170,6 @@
     @After
     public void tearDown() throws Exception {
         Settings.Global.putInt(mFakeContentResolver, Settings.Global.AIRPLANE_MODE_ON, 0);
-        mCarrierActionAgentHandler.quit();
         super.tearDown();
     }
 }
diff --git a/tests/telephonytests/src/com/android/internal/telephony/CarrierAppUtilsTest.java b/tests/telephonytests/src/com/android/internal/telephony/CarrierAppUtilsTest.java
index 59c36bb..a0e5b33 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/CarrierAppUtilsTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/CarrierAppUtilsTest.java
@@ -16,20 +16,28 @@
 
 package com.android.internal.telephony;
 
+import android.content.Context;
 import android.content.pm.ApplicationInfo;
-import android.content.pm.IPackageManager;
 import android.content.pm.PackageManager;
+import android.os.Build;
 import android.os.Bundle;
+import android.os.CarrierAssociatedAppEntry;
+import android.os.UserHandle;
 import android.provider.Settings;
 import android.telephony.TelephonyManager;
-import android.test.InstrumentationTestCase;
 import android.test.mock.MockContentProvider;
 import android.test.mock.MockContentResolver;
 import android.test.suitebuilder.annotation.SmallTest;
 import android.util.ArrayMap;
 import android.util.ArraySet;
 
+import androidx.test.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Ignore;
 import org.junit.Test;
+import org.junit.runner.RunWith;
 import org.mockito.Mock;
 import org.mockito.Mockito;
 import org.mockito.MockitoAnnotations;
@@ -37,7 +45,8 @@
 import java.util.ArrayList;
 import java.util.List;
 
-public class CarrierAppUtilsTest extends InstrumentationTestCase {
+@RunWith(AndroidJUnit4.class)
+public class CarrierAppUtilsTest {
     private static final String CARRIER_APP = "com.example.carrier";
     private static final ArraySet<String> CARRIER_APPS = new ArraySet<>();
     static {
@@ -45,41 +54,56 @@
     }
 
     private static final String ASSOCIATED_APP = "com.example.associated";
-    private static final ArrayMap<String, List<String>> ASSOCIATED_APPS = new ArrayMap<>();
-    static {
-        List<String> associatedAppList = new ArrayList<>();
-        associatedAppList.add(ASSOCIATED_APP);
-        ASSOCIATED_APPS.put(CARRIER_APP, associatedAppList);
-    }
+    private static final ArrayMap<String, List<CarrierAssociatedAppEntry>> ASSOCIATED_APPS =
+            makeAssociatedApp(CARRIER_APP, ASSOCIATED_APP,
+                    CarrierAssociatedAppEntry.SDK_UNSPECIFIED);
     private static final int USER_ID = 12345;
     private static final String CALLING_PACKAGE = "phone";
 
-    @Mock private IPackageManager mPackageManager;
+    @Mock private Context mContext;
+    @Mock private PackageManager mPackageManager;
     @Mock private TelephonyManager mTelephonyManager;
     private SettingsMockContentProvider mContentProvider;
     private MockContentResolver mContentResolver;
 
-    @Override
+    private static ArrayMap<String, List<CarrierAssociatedAppEntry>> makeAssociatedApp(
+            String carrierAppPackage, String associatedAppPackage, int associatedAppAddedInSdk) {
+        ArrayMap<String, List<CarrierAssociatedAppEntry>> result = new ArrayMap<>();
+        List<CarrierAssociatedAppEntry> associatedAppList = new ArrayList<>();
+        associatedAppList.add(
+                new CarrierAssociatedAppEntry(associatedAppPackage, associatedAppAddedInSdk));
+        result.put(carrierAppPackage, associatedAppList);
+        return result;
+    }
+
+    @Before
     public void setUp() throws Exception {
-        super.setUp();
         System.setProperty("dexmaker.dexcache",
-                getInstrumentation().getTargetContext().getCacheDir().getPath());
+                InstrumentationRegistry.getTargetContext().getCacheDir().getPath());
         Thread.currentThread().setContextClassLoader(getClass().getClassLoader());
         MockitoAnnotations.initMocks(this);
 
+        Mockito.when(mContext.createContextAsUser(Mockito.any(UserHandle.class), Mockito.eq(0)))
+                .thenReturn(mContext);
+        Mockito.when(mContext.createPackageContextAsUser(Mockito.anyString(), Mockito.eq(0),
+                Mockito.any(UserHandle.class))).thenReturn(mContext);
+        Mockito.when(mContext.getPackageManager()).thenReturn(mPackageManager);
+        Mockito.when(mContext.getPackageName()).thenReturn(CALLING_PACKAGE);
+        // Placeholder, cannot mock final PermissionManager
+
         mContentResolver = new MockContentResolver();
         mContentProvider = new SettingsMockContentProvider();
         mContentResolver.addProvider(Settings.AUTHORITY, mContentProvider);
         Settings.Secure.putIntForUser(
                 mContentResolver, Settings.Secure.CARRIER_APPS_HANDLED, 0, USER_ID);
+        Mockito.when(mContext.getContentResolver()).thenReturn(mContentResolver);
     }
 
     /** No apps configured - should do nothing. */
     @Test @SmallTest
     public void testDisableCarrierAppsUntilPrivileged_EmptyList() {
-        CarrierAppUtils.disableCarrierAppsUntilPrivileged(CALLING_PACKAGE, mPackageManager,
-                mTelephonyManager, mContentResolver, USER_ID, new ArraySet<>(),
-                ASSOCIATED_APPS);
+        CarrierAppUtils.disableCarrierAppsUntilPrivileged(CALLING_PACKAGE, mTelephonyManager,
+                mContentResolver, USER_ID, new ArraySet<>(), ASSOCIATED_APPS, mContext);
         Mockito.verifyNoMoreInteractions(mPackageManager, mTelephonyManager);
     }
 
@@ -88,20 +112,21 @@
     public void testDisableCarrierAppsUntilPrivileged_MissingApp() throws Exception {
         Mockito.when(mPackageManager.getApplicationInfo("com.example.missing.app",
                 PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS
-                        | PackageManager.MATCH_HIDDEN_UNTIL_INSTALLED_COMPONENTS,
-                USER_ID)).thenReturn(null);
+                        | PackageManager.MATCH_HIDDEN_UNTIL_INSTALLED_COMPONENTS
+                        | PackageManager.MATCH_SYSTEM_ONLY)).thenReturn(null);
         ArraySet<String> systemCarrierAppsDisabledUntilUsed = new ArraySet<>();
         systemCarrierAppsDisabledUntilUsed.add("com.example.missing.app");
-        CarrierAppUtils.disableCarrierAppsUntilPrivileged(CALLING_PACKAGE, mPackageManager,
-                mTelephonyManager, mContentResolver, USER_ID,
-                systemCarrierAppsDisabledUntilUsed, ASSOCIATED_APPS);
-        Mockito.verify(mPackageManager, Mockito.never()).setSystemAppHiddenUntilInstalled(
-                Mockito.anyString(), Mockito.anyBoolean());
-        Mockito.verify(mPackageManager, Mockito.never()).setSystemAppInstallState(
-                Mockito.anyString(), Mockito.anyBoolean(), Mockito.anyInt());
-        Mockito.verify(mPackageManager, Mockito.never())
-                .grantDefaultPermissionsToEnabledCarrierApps(Mockito.any(String[].class),
-                        Mockito.anyInt());
+        CarrierAppUtils.disableCarrierAppsUntilPrivileged(CALLING_PACKAGE, mTelephonyManager,
+                mContentResolver, USER_ID, systemCarrierAppsDisabledUntilUsed, ASSOCIATED_APPS,
+                mContext);
+        Mockito.verify(mPackageManager, Mockito.never()).setSystemAppState(Mockito.anyString(),
+                Mockito.eq(PackageManager.SYSTEM_APP_STATE_HIDDEN_UNTIL_INSTALLED_HIDDEN));
+        Mockito.verify(mPackageManager, Mockito.never()).setSystemAppState(Mockito.anyString(),
+                Mockito.eq(PackageManager.SYSTEM_APP_STATE_HIDDEN_UNTIL_INSTALLED_VISIBLE));
+        Mockito.verify(mPackageManager, Mockito.never()).setSystemAppState(Mockito.anyString(),
+                Mockito.eq(PackageManager.SYSTEM_APP_STATE_INSTALLED));
+        Mockito.verify(mPackageManager, Mockito.never()).setSystemAppState(Mockito.anyString(),
+                Mockito.eq(PackageManager.SYSTEM_APP_STATE_UNINSTALLED));
         Mockito.verifyNoMoreInteractions(mTelephonyManager);
     }
 
@@ -111,17 +136,18 @@
         ApplicationInfo appInfo = new ApplicationInfo();
         Mockito.when(mPackageManager.getApplicationInfo(CARRIER_APP,
                 PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS
-                        | PackageManager.MATCH_HIDDEN_UNTIL_INSTALLED_COMPONENTS,
-                USER_ID)).thenReturn(appInfo);
-        CarrierAppUtils.disableCarrierAppsUntilPrivileged(CALLING_PACKAGE, mPackageManager,
-                mTelephonyManager, mContentResolver, USER_ID, CARRIER_APPS, ASSOCIATED_APPS);
-        Mockito.verify(mPackageManager, Mockito.never()).setSystemAppHiddenUntilInstalled(
-                Mockito.anyString(), Mockito.anyBoolean());
-        Mockito.verify(mPackageManager, Mockito.never()).setSystemAppInstallState(
-                Mockito.anyString(), Mockito.anyBoolean(), Mockito.anyInt());
-        Mockito.verify(mPackageManager, Mockito.never())
-                .grantDefaultPermissionsToEnabledCarrierApps(
-                        Mockito.any(String[].class), Mockito.anyInt());
+                        | PackageManager.MATCH_HIDDEN_UNTIL_INSTALLED_COMPONENTS))
+                .thenReturn(appInfo);
+        CarrierAppUtils.disableCarrierAppsUntilPrivileged(CALLING_PACKAGE, mTelephonyManager,
+                mContentResolver, USER_ID, CARRIER_APPS, ASSOCIATED_APPS, mContext);
+        Mockito.verify(mPackageManager, Mockito.never()).setSystemAppState(Mockito.anyString(),
+                Mockito.eq(PackageManager.SYSTEM_APP_STATE_HIDDEN_UNTIL_INSTALLED_HIDDEN));
+        Mockito.verify(mPackageManager, Mockito.never()).setSystemAppState(Mockito.anyString(),
+                Mockito.eq(PackageManager.SYSTEM_APP_STATE_HIDDEN_UNTIL_INSTALLED_VISIBLE));
+        Mockito.verify(mPackageManager, Mockito.never()).setSystemAppState(Mockito.anyString(),
+                Mockito.eq(PackageManager.SYSTEM_APP_STATE_INSTALLED));
+        Mockito.verify(mPackageManager, Mockito.never()).setSystemAppState(Mockito.anyString(),
+                Mockito.eq(PackageManager.SYSTEM_APP_STATE_UNINSTALLED));
         Mockito.verifyNoMoreInteractions(mTelephonyManager);
     }
 
@@ -129,249 +155,264 @@
      * Configured app has privileges, but was disabled by the user - should only grant
      * permissions.
      */
-    @Test @SmallTest
+    @Test @SmallTest @Ignore
     public void testDisableCarrierAppsUntilPrivileged_HasPrivileges_DisabledUser()
             throws Exception {
         ApplicationInfo appInfo = new ApplicationInfo();
         appInfo.packageName = CARRIER_APP;
         appInfo.flags |= ApplicationInfo.FLAG_SYSTEM | ApplicationInfo.FLAG_INSTALLED;
-        appInfo.enabledSetting = PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER;
+        Mockito.when(mPackageManager
+                .getApplicationEnabledSetting(Mockito.anyString()))
+                .thenReturn(PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER);
+
         Mockito.when(mPackageManager.getApplicationInfo(CARRIER_APP,
                 PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS
-                        | PackageManager.MATCH_HIDDEN_UNTIL_INSTALLED_COMPONENTS,
-                USER_ID)).thenReturn(appInfo);
+                        | PackageManager.MATCH_HIDDEN_UNTIL_INSTALLED_COMPONENTS
+                        | PackageManager.MATCH_SYSTEM_ONLY)).thenReturn(appInfo);
         Mockito.when(mTelephonyManager.checkCarrierPrivilegesForPackageAnyPhone(CARRIER_APP))
                 .thenReturn(TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS);
-        CarrierAppUtils.disableCarrierAppsUntilPrivileged(CALLING_PACKAGE, mPackageManager,
-                mTelephonyManager, mContentResolver, USER_ID, CARRIER_APPS, ASSOCIATED_APPS);
-        Mockito.verify(mPackageManager).setSystemAppHiddenUntilInstalled(
-                CARRIER_APP, true);
-        Mockito.verify(mPackageManager, Mockito.never()).setSystemAppInstallState(
-                Mockito.anyString(), Mockito.anyBoolean(), Mockito.anyInt());
-        Mockito.verify(mPackageManager).grantDefaultPermissionsToEnabledCarrierApps(
-                new String[] {appInfo.packageName}, USER_ID);
+        CarrierAppUtils.disableCarrierAppsUntilPrivileged(CALLING_PACKAGE, mTelephonyManager,
+                mContentResolver, USER_ID, CARRIER_APPS, ASSOCIATED_APPS, mContext);
+        Mockito.verify(mPackageManager).setSystemAppState(CARRIER_APP,
+                PackageManager.SYSTEM_APP_STATE_HIDDEN_UNTIL_INSTALLED_HIDDEN);
+        Mockito.verify(mPackageManager, Mockito.never()).setSystemAppState(Mockito.anyString(),
+                PackageManager.SYSTEM_APP_STATE_INSTALLED);
+        Mockito.verify(mPackageManager, Mockito.never()).setSystemAppState(Mockito.anyString(),
+                PackageManager.SYSTEM_APP_STATE_UNINSTALLED);
     }
 
     /** Configured app has privileges, but was disabled - should only grant permissions. */
-    @Test @SmallTest
+    @Test @SmallTest @Ignore
     public void testDisableCarrierAppsUntilPrivileged_HasPrivileges_Disabled() throws Exception {
         ApplicationInfo appInfo = new ApplicationInfo();
         appInfo.packageName = CARRIER_APP;
         appInfo.flags |= ApplicationInfo.FLAG_SYSTEM | ApplicationInfo.FLAG_INSTALLED;
-        appInfo.enabledSetting = PackageManager.COMPONENT_ENABLED_STATE_DISABLED;
+        Mockito.when(mPackageManager
+                .getApplicationEnabledSetting(Mockito.anyString()))
+                .thenReturn(PackageManager.COMPONENT_ENABLED_STATE_DISABLED);
+
         Mockito.when(mPackageManager.getApplicationInfo(CARRIER_APP,
                 PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS
-                        | PackageManager.MATCH_HIDDEN_UNTIL_INSTALLED_COMPONENTS,
-                USER_ID)).thenReturn(appInfo);
+                        | PackageManager.MATCH_HIDDEN_UNTIL_INSTALLED_COMPONENTS
+                        | PackageManager.MATCH_SYSTEM_ONLY)).thenReturn(appInfo);
         Mockito.when(mTelephonyManager.checkCarrierPrivilegesForPackageAnyPhone(CARRIER_APP))
                 .thenReturn(TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS);
-        CarrierAppUtils.disableCarrierAppsUntilPrivileged(CALLING_PACKAGE, mPackageManager,
-                mTelephonyManager, mContentResolver, USER_ID, CARRIER_APPS, ASSOCIATED_APPS);
-        Mockito.verify(mPackageManager).setSystemAppHiddenUntilInstalled(
-                CARRIER_APP, true);
-        Mockito.verify(mPackageManager, Mockito.never()).setSystemAppInstallState(
-                Mockito.anyString(), Mockito.anyBoolean(), Mockito.anyInt());
-        Mockito.verify(mPackageManager).grantDefaultPermissionsToEnabledCarrierApps(
-                new String[] {appInfo.packageName}, USER_ID);
+        CarrierAppUtils.disableCarrierAppsUntilPrivileged(CALLING_PACKAGE, mTelephonyManager,
+                mContentResolver, USER_ID, CARRIER_APPS, ASSOCIATED_APPS, mContext);
+        Mockito.verify(mPackageManager).setSystemAppState(CARRIER_APP,
+                PackageManager.SYSTEM_APP_STATE_HIDDEN_UNTIL_INSTALLED_HIDDEN);
+        Mockito.verify(mPackageManager, Mockito.never()).setSystemAppState(Mockito.anyString(),
+                Mockito.eq(PackageManager.SYSTEM_APP_STATE_INSTALLED));
+        Mockito.verify(mPackageManager, Mockito.never()).setSystemAppState(Mockito.anyString(),
+                Mockito.eq(PackageManager.SYSTEM_APP_STATE_UNINSTALLED));
     }
 
     /** Configured app has privileges, and is already installed - should only grant permissions. */
-    @Test @SmallTest
+    @Test @SmallTest @Ignore
     public void testDisableCarrierAppsUntilPrivileged_HasPrivileges_Enabled() throws Exception {
         ApplicationInfo appInfo = new ApplicationInfo();
         appInfo.packageName = CARRIER_APP;
         appInfo.flags |= ApplicationInfo.FLAG_SYSTEM | ApplicationInfo.FLAG_INSTALLED;
-        appInfo.enabledSetting = PackageManager.COMPONENT_ENABLED_STATE_ENABLED;
+        Mockito.when(mPackageManager
+                .getApplicationEnabledSetting(Mockito.anyString()))
+                .thenReturn(PackageManager.COMPONENT_ENABLED_STATE_ENABLED);
         Mockito.when(mPackageManager.getApplicationInfo(CARRIER_APP,
                 PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS
-                        | PackageManager.MATCH_HIDDEN_UNTIL_INSTALLED_COMPONENTS,
-                USER_ID)).thenReturn(appInfo);
+                        | PackageManager.MATCH_HIDDEN_UNTIL_INSTALLED_COMPONENTS
+                        | PackageManager.MATCH_SYSTEM_ONLY)).thenReturn(appInfo);
         Mockito.when(mTelephonyManager.checkCarrierPrivilegesForPackageAnyPhone(CARRIER_APP))
                 .thenReturn(TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS);
-        CarrierAppUtils.disableCarrierAppsUntilPrivileged(CALLING_PACKAGE, mPackageManager,
-                mTelephonyManager, mContentResolver, USER_ID, CARRIER_APPS, ASSOCIATED_APPS);
-        Mockito.verify(mPackageManager).setSystemAppHiddenUntilInstalled(
-                CARRIER_APP, true);
-        Mockito.verify(mPackageManager, Mockito.never()).setSystemAppInstallState(
-                Mockito.anyString(), Mockito.anyBoolean(), Mockito.anyInt());
-        Mockito.verify(mPackageManager).grantDefaultPermissionsToEnabledCarrierApps(
-                new String[] {appInfo.packageName}, USER_ID);
+        CarrierAppUtils.disableCarrierAppsUntilPrivileged(CALLING_PACKAGE, mTelephonyManager,
+                mContentResolver, USER_ID, CARRIER_APPS, ASSOCIATED_APPS, mContext);
+        Mockito.verify(mPackageManager).setSystemAppState(CARRIER_APP,
+                PackageManager.SYSTEM_APP_STATE_HIDDEN_UNTIL_INSTALLED_HIDDEN);
+        Mockito.verify(mPackageManager, Mockito.never()).setSystemAppState(Mockito.anyString(),
+                Mockito.eq(PackageManager.SYSTEM_APP_STATE_INSTALLED));
+        Mockito.verify(mPackageManager, Mockito.never()).setSystemAppState(Mockito.anyString(),
+                Mockito.eq(PackageManager.SYSTEM_APP_STATE_UNINSTALLED));
     }
 
     /** Configured /data app has privileges - should only grant permissions. */
-    @Test @SmallTest
+    @Test @SmallTest @Ignore
     public void testDisableCarrierAppsUntilPrivileged_HasPrivileges_UpdatedApp() throws Exception {
         ApplicationInfo appInfo = new ApplicationInfo();
         appInfo.packageName = CARRIER_APP;
         appInfo.flags |= ApplicationInfo.FLAG_SYSTEM | ApplicationInfo.FLAG_UPDATED_SYSTEM_APP
                 | ApplicationInfo.FLAG_INSTALLED;
-        appInfo.enabledSetting = PackageManager.COMPONENT_ENABLED_STATE_ENABLED;
         Mockito.when(mPackageManager.getApplicationInfo(CARRIER_APP,
                 PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS
-                        | PackageManager.MATCH_HIDDEN_UNTIL_INSTALLED_COMPONENTS,
-                USER_ID)).thenReturn(appInfo);
+                        | PackageManager.MATCH_HIDDEN_UNTIL_INSTALLED_COMPONENTS
+                        | PackageManager.MATCH_SYSTEM_ONLY)).thenReturn(appInfo);
+        Mockito.when(mPackageManager
+                .getApplicationEnabledSetting(Mockito.anyString()))
+                .thenReturn(PackageManager.COMPONENT_ENABLED_STATE_ENABLED);
+
         Mockito.when(mTelephonyManager.checkCarrierPrivilegesForPackageAnyPhone(CARRIER_APP))
                 .thenReturn(TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS);
-        CarrierAppUtils.disableCarrierAppsUntilPrivileged(CALLING_PACKAGE, mPackageManager,
-                mTelephonyManager, mContentResolver, USER_ID, CARRIER_APPS, ASSOCIATED_APPS);
-        Mockito.verify(mPackageManager).setSystemAppHiddenUntilInstalled(
-                CARRIER_APP, true);
-        Mockito.verify(mPackageManager, Mockito.never()).setSystemAppInstallState(
-                Mockito.anyString(), Mockito.anyBoolean(), Mockito.anyInt());
-        Mockito.verify(mPackageManager).grantDefaultPermissionsToEnabledCarrierApps(
-                new String[] {appInfo.packageName}, USER_ID);
+        CarrierAppUtils.disableCarrierAppsUntilPrivileged(CALLING_PACKAGE, mTelephonyManager,
+                mContentResolver, USER_ID, CARRIER_APPS, ASSOCIATED_APPS, mContext);
+        Mockito.verify(mPackageManager).setSystemAppState(CARRIER_APP,
+                PackageManager.SYSTEM_APP_STATE_HIDDEN_UNTIL_INSTALLED_HIDDEN);
+        Mockito.verify(mPackageManager, Mockito.never()).setSystemAppState(Mockito.anyString(),
+                Mockito.eq(PackageManager.SYSTEM_APP_STATE_INSTALLED));
+        Mockito.verify(mPackageManager, Mockito.never()).setSystemAppState(Mockito.anyString(),
+                Mockito.eq(PackageManager.SYSTEM_APP_STATE_UNINSTALLED));
     }
 
     /**
      * Configured app has privileges, and is in the default state - should install. Associated app
      * is missing and should not be touched.
      */
-    @Test @SmallTest
+    @Test @SmallTest @Ignore
     public void testDisableCarrierAppsUntilPrivileged_HasPrivileges_MissingAssociated_Default()
             throws Exception {
         ApplicationInfo appInfo = new ApplicationInfo();
         appInfo.packageName = CARRIER_APP;
         appInfo.flags |= ApplicationInfo.FLAG_SYSTEM | ApplicationInfo.FLAG_INSTALLED;
-        appInfo.enabledSetting = PackageManager.COMPONENT_ENABLED_STATE_DEFAULT;
         Mockito.when(mPackageManager.getApplicationInfo(CARRIER_APP,
                 PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS
-                        | PackageManager.MATCH_HIDDEN_UNTIL_INSTALLED_COMPONENTS, USER_ID))
-                .thenReturn(appInfo);
+                        | PackageManager.MATCH_HIDDEN_UNTIL_INSTALLED_COMPONENTS
+                        | PackageManager.MATCH_SYSTEM_ONLY)).thenReturn(appInfo);
+        Mockito.when(mPackageManager
+                .getApplicationEnabledSetting(Mockito.anyString()))
+                .thenReturn(PackageManager.COMPONENT_ENABLED_STATE_DEFAULT);
         Mockito.when(mTelephonyManager.checkCarrierPrivilegesForPackageAnyPhone(CARRIER_APP))
                 .thenReturn(TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS);
-        CarrierAppUtils.disableCarrierAppsUntilPrivileged(CALLING_PACKAGE, mPackageManager,
-                mTelephonyManager, mContentResolver, USER_ID, CARRIER_APPS, ASSOCIATED_APPS);
-        Mockito.verify(mPackageManager).setSystemAppHiddenUntilInstalled(
-                CARRIER_APP, true);
-        Mockito.verify(mPackageManager).setSystemAppInstallState(
-                CARRIER_APP, true, USER_ID);
-        Mockito.verify(mPackageManager, Mockito.never()).setSystemAppInstallState(
-                ASSOCIATED_APP, true, USER_ID);
-        Mockito.verify(mPackageManager).grantDefaultPermissionsToEnabledCarrierApps(
-                new String[] {appInfo.packageName}, USER_ID);
+        CarrierAppUtils.disableCarrierAppsUntilPrivileged(CALLING_PACKAGE, mTelephonyManager,
+                mContentResolver, USER_ID, CARRIER_APPS, ASSOCIATED_APPS, mContext);
+        Mockito.verify(mPackageManager).setSystemAppState(CARRIER_APP,
+                PackageManager.SYSTEM_APP_STATE_HIDDEN_UNTIL_INSTALLED_HIDDEN);
+        Mockito.verify(mPackageManager).setSystemAppState(CARRIER_APP,
+                PackageManager.SYSTEM_APP_STATE_INSTALLED);
+        Mockito.verify(mPackageManager, Mockito.never()).setSystemAppState(ASSOCIATED_APP,
+                PackageManager.SYSTEM_APP_STATE_INSTALLED);
     }
 
     /**
      * Configured app has privileges, and is in the default state along with associated app - should
      * install both.
      */
-    @Test @SmallTest
+    @Test @SmallTest @Ignore
     public void testDisableCarrierAppsUntilPrivileged_HasPrivileges_Associated_Default()
             throws Exception {
         // Enabling should be done even if this isn't the first run.
         Settings.Secure.putIntForUser(
-                mContentResolver, Settings.Secure.CARRIER_APPS_HANDLED, 1, USER_ID);
+                mContentResolver, Settings.Secure.CARRIER_APPS_HANDLED, Build.VERSION.SDK_INT,
+                USER_ID);
         ApplicationInfo appInfo = new ApplicationInfo();
         appInfo.packageName = CARRIER_APP;
         appInfo.flags |= ApplicationInfo.FLAG_SYSTEM | ApplicationInfo.FLAG_INSTALLED;
-        appInfo.enabledSetting = PackageManager.COMPONENT_ENABLED_STATE_DEFAULT;
+        Mockito.when(mPackageManager
+                .getApplicationEnabledSetting(Mockito.anyString()))
+                .thenReturn(PackageManager.COMPONENT_ENABLED_STATE_DEFAULT);
         Mockito.when(mPackageManager.getApplicationInfo(CARRIER_APP,
                 PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS
-                        | PackageManager.MATCH_HIDDEN_UNTIL_INSTALLED_COMPONENTS, USER_ID))
+                        | PackageManager.MATCH_HIDDEN_UNTIL_INSTALLED_COMPONENTS
+                        | PackageManager.MATCH_SYSTEM_ONLY))
                 .thenReturn(appInfo);
         ApplicationInfo associatedAppInfo = new ApplicationInfo();
         associatedAppInfo.packageName = ASSOCIATED_APP;
         associatedAppInfo.flags |= ApplicationInfo.FLAG_SYSTEM;
-        associatedAppInfo.enabledSetting = PackageManager.COMPONENT_ENABLED_STATE_DEFAULT;
         Mockito.when(mPackageManager.getApplicationInfo(ASSOCIATED_APP,
                 PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS
-                        | PackageManager.MATCH_HIDDEN_UNTIL_INSTALLED_COMPONENTS, USER_ID))
+                        | PackageManager.MATCH_HIDDEN_UNTIL_INSTALLED_COMPONENTS
+                        | PackageManager.MATCH_SYSTEM_ONLY))
                 .thenReturn(associatedAppInfo);
         Mockito.when(mTelephonyManager.checkCarrierPrivilegesForPackageAnyPhone(CARRIER_APP))
                 .thenReturn(TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS);
-        CarrierAppUtils.disableCarrierAppsUntilPrivileged(CALLING_PACKAGE, mPackageManager,
-                mTelephonyManager, mContentResolver, USER_ID, CARRIER_APPS, ASSOCIATED_APPS);
-        Mockito.verify(mPackageManager).setSystemAppHiddenUntilInstalled(
-                CARRIER_APP, true);
-        Mockito.verify(mPackageManager).setSystemAppHiddenUntilInstalled(
-                ASSOCIATED_APP, true);
-        Mockito.verify(mPackageManager).setSystemAppInstallState(
-                CARRIER_APP, true, USER_ID);
-        Mockito.verify(mPackageManager).setSystemAppInstallState(
-                ASSOCIATED_APP, true, USER_ID);
-        Mockito.verify(mPackageManager).grantDefaultPermissionsToEnabledCarrierApps(
-                new String[] {appInfo.packageName}, USER_ID);
+        CarrierAppUtils.disableCarrierAppsUntilPrivileged(CALLING_PACKAGE, mTelephonyManager,
+                mContentResolver, USER_ID, CARRIER_APPS, ASSOCIATED_APPS, mContext);
+        Mockito.verify(mPackageManager).setSystemAppState(CARRIER_APP,
+                PackageManager.SYSTEM_APP_STATE_HIDDEN_UNTIL_INSTALLED_HIDDEN);
+        Mockito.verify(mPackageManager).setSystemAppState(ASSOCIATED_APP,
+                PackageManager.SYSTEM_APP_STATE_HIDDEN_UNTIL_INSTALLED_HIDDEN);
+        Mockito.verify(mPackageManager).setSystemAppState(CARRIER_APP,
+                PackageManager.SYSTEM_APP_STATE_INSTALLED);
+        Mockito.verify(mPackageManager).setSystemAppState(ASSOCIATED_APP,
+                PackageManager.SYSTEM_APP_STATE_INSTALLED);
     }
 
     /**
      * Configured app has privileges, and is uninstalled - should install. Associated app has
      * been updated and should not be touched.
      */
-    @Test @SmallTest
+    @Test @SmallTest @Ignore
     public void testDisableCarrierAppsUntilPrivileged_HasPrivileges_UpdatedAssociated_DisabledUntilUsed()
             throws Exception {
         ApplicationInfo appInfo = new ApplicationInfo();
         appInfo.packageName = CARRIER_APP;
         appInfo.flags |= ApplicationInfo.FLAG_SYSTEM | ApplicationInfo.FLAG_INSTALLED;
-        appInfo.enabledSetting = PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED;
+        Mockito.when(mPackageManager.getApplicationEnabledSetting(CARRIER_APP))
+                .thenReturn(PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED);
         Mockito.when(mPackageManager.getApplicationInfo(CARRIER_APP,
                 PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS
-                        | PackageManager.MATCH_HIDDEN_UNTIL_INSTALLED_COMPONENTS,
-                USER_ID)).thenReturn(appInfo);
+                        | PackageManager.MATCH_HIDDEN_UNTIL_INSTALLED_COMPONENTS
+                        | PackageManager.MATCH_SYSTEM_ONLY))
+                .thenReturn(appInfo);
         ApplicationInfo associatedAppInfo = new ApplicationInfo();
         associatedAppInfo.packageName = ASSOCIATED_APP;
         associatedAppInfo.flags |=
                 ApplicationInfo.FLAG_SYSTEM | ApplicationInfo.FLAG_UPDATED_SYSTEM_APP;
-        associatedAppInfo.enabledSetting =
-                PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED;
+        Mockito.when(mPackageManager.getApplicationEnabledSetting(ASSOCIATED_APP))
+                .thenReturn(PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED);
         Mockito.when(mPackageManager.getApplicationInfo(ASSOCIATED_APP,
                 PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS
-                        | PackageManager.MATCH_HIDDEN_UNTIL_INSTALLED_COMPONENTS, USER_ID))
-                .thenReturn(associatedAppInfo);
+                        | PackageManager.MATCH_HIDDEN_UNTIL_INSTALLED_COMPONENTS
+                        | PackageManager.MATCH_SYSTEM_ONLY))
+                .thenReturn(null);
         Mockito.when(mTelephonyManager.checkCarrierPrivilegesForPackageAnyPhone(CARRIER_APP))
                 .thenReturn(TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS);
-        CarrierAppUtils.disableCarrierAppsUntilPrivileged(CALLING_PACKAGE, mPackageManager,
-                mTelephonyManager, mContentResolver, USER_ID, CARRIER_APPS, ASSOCIATED_APPS);
-        Mockito.verify(mPackageManager).setSystemAppHiddenUntilInstalled(
-                CARRIER_APP, true);
-        Mockito.verify(mPackageManager, Mockito.never()).setSystemAppHiddenUntilInstalled(
-                ASSOCIATED_APP, true);
-        Mockito.verify(mPackageManager).setSystemAppInstallState(
-                CARRIER_APP, true, USER_ID);
-        Mockito.verify(mPackageManager, Mockito.never()).setSystemAppInstallState(
-                ASSOCIATED_APP, true, USER_ID);
-        Mockito.verify(mPackageManager).grantDefaultPermissionsToEnabledCarrierApps(
-                new String[] {appInfo.packageName}, USER_ID);
+        CarrierAppUtils.disableCarrierAppsUntilPrivileged(CALLING_PACKAGE, mTelephonyManager,
+                mContentResolver, USER_ID, CARRIER_APPS, ASSOCIATED_APPS, mContext);
+        Mockito.verify(mPackageManager).setSystemAppState(CARRIER_APP,
+                PackageManager.SYSTEM_APP_STATE_HIDDEN_UNTIL_INSTALLED_HIDDEN);
+        Mockito.verify(mPackageManager, Mockito.never()).setSystemAppState(ASSOCIATED_APP,
+                PackageManager.SYSTEM_APP_STATE_HIDDEN_UNTIL_INSTALLED_HIDDEN);
+        Mockito.verify(mPackageManager).setSystemAppState(CARRIER_APP,
+                PackageManager.SYSTEM_APP_STATE_INSTALLED);
+        Mockito.verify(mPackageManager, Mockito.never()).setSystemAppState(ASSOCIATED_APP,
+                PackageManager.SYSTEM_APP_STATE_INSTALLED);
     }
 
     /**
      * Configured app has privileges, and is uninstalled until used along with associated app -
      * should install both.
      */
-    @Test @SmallTest
+    @Test @SmallTest @Ignore
     public void testDisableCarrierAppsUntilPrivileged_HasPrivileges_Associated_DisabledUntilUsed()
             throws Exception {
         ApplicationInfo appInfo = new ApplicationInfo();
         appInfo.packageName = CARRIER_APP;
         appInfo.flags |= ApplicationInfo.FLAG_SYSTEM | ApplicationInfo.FLAG_INSTALLED;
-        appInfo.enabledSetting = PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED;
+        Mockito.when(mPackageManager.getApplicationEnabledSetting(CARRIER_APP))
+                .thenReturn(PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED);
         Mockito.when(mPackageManager.getApplicationInfo(CARRIER_APP,
                 PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS
-                        | PackageManager.MATCH_HIDDEN_UNTIL_INSTALLED_COMPONENTS,
-                USER_ID)).thenReturn(appInfo);
+                        | PackageManager.MATCH_HIDDEN_UNTIL_INSTALLED_COMPONENTS
+                        | PackageManager.MATCH_SYSTEM_ONLY))
+                .thenReturn(appInfo);
         ApplicationInfo associatedAppInfo = new ApplicationInfo();
         associatedAppInfo.packageName = ASSOCIATED_APP;
         associatedAppInfo.flags |= ApplicationInfo.FLAG_SYSTEM;
-        associatedAppInfo.enabledSetting =
-                PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED;
+        Mockito.when(mPackageManager.getApplicationEnabledSetting(ASSOCIATED_APP))
+                .thenReturn(PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED);
         Mockito.when(mPackageManager.getApplicationInfo(ASSOCIATED_APP,
                 PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS
-                        | PackageManager.MATCH_HIDDEN_UNTIL_INSTALLED_COMPONENTS, USER_ID))
+                        | PackageManager.MATCH_HIDDEN_UNTIL_INSTALLED_COMPONENTS
+                        | PackageManager.MATCH_SYSTEM_ONLY))
                 .thenReturn(associatedAppInfo);
         Mockito.when(mTelephonyManager.checkCarrierPrivilegesForPackageAnyPhone(CARRIER_APP))
                 .thenReturn(TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS);
-        CarrierAppUtils.disableCarrierAppsUntilPrivileged(CALLING_PACKAGE, mPackageManager,
-                mTelephonyManager, mContentResolver, USER_ID, CARRIER_APPS, ASSOCIATED_APPS);
-        Mockito.verify(mPackageManager).setSystemAppHiddenUntilInstalled(
-                CARRIER_APP, true);
-        Mockito.verify(mPackageManager).setSystemAppHiddenUntilInstalled(
-                ASSOCIATED_APP, true);
-        Mockito.verify(mPackageManager).setSystemAppInstallState(
-                CARRIER_APP, true, USER_ID);
-        Mockito.verify(mPackageManager).setSystemAppInstallState(
-                ASSOCIATED_APP, true, USER_ID);
-        Mockito.verify(mPackageManager).grantDefaultPermissionsToEnabledCarrierApps(
-                new String[] {appInfo.packageName}, USER_ID);
+        CarrierAppUtils.disableCarrierAppsUntilPrivileged(CALLING_PACKAGE, mTelephonyManager,
+                mContentResolver, USER_ID, CARRIER_APPS, ASSOCIATED_APPS, mContext);
+        Mockito.verify(mPackageManager).setSystemAppState(CARRIER_APP,
+                PackageManager.SYSTEM_APP_STATE_HIDDEN_UNTIL_INSTALLED_HIDDEN);
+        Mockito.verify(mPackageManager).setSystemAppState(ASSOCIATED_APP,
+                PackageManager.SYSTEM_APP_STATE_HIDDEN_UNTIL_INSTALLED_HIDDEN);
+        Mockito.verify(mPackageManager).setSystemAppState(CARRIER_APP,
+                PackageManager.SYSTEM_APP_STATE_INSTALLED);
+        Mockito.verify(mPackageManager).setSystemAppState(ASSOCIATED_APP,
+                PackageManager.SYSTEM_APP_STATE_INSTALLED);
     }
 
     /** Configured app has no privileges, and was disabled by the user - should do nothing. */
@@ -380,22 +421,24 @@
         ApplicationInfo appInfo = new ApplicationInfo();
         appInfo.packageName = CARRIER_APP;
         appInfo.flags |= ApplicationInfo.FLAG_SYSTEM | ApplicationInfo.FLAG_INSTALLED;
-        appInfo.enabledSetting = PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER;
+        Mockito.when(mPackageManager
+                .getApplicationEnabledSetting(Mockito.anyString()))
+                .thenReturn(PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER);
         Mockito.when(mPackageManager.getApplicationInfo(CARRIER_APP,
                 PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS
-                        | PackageManager.MATCH_HIDDEN_UNTIL_INSTALLED_COMPONENTS,
-                USER_ID)).thenReturn(appInfo);
+                        | PackageManager.MATCH_HIDDEN_UNTIL_INSTALLED_COMPONENTS
+                        | PackageManager.MATCH_SYSTEM_ONLY))
+                .thenReturn(appInfo);
         Mockito.when(mTelephonyManager.checkCarrierPrivilegesForPackageAnyPhone(CARRIER_APP))
                 .thenReturn(TelephonyManager.CARRIER_PRIVILEGE_STATUS_NO_ACCESS);
-        CarrierAppUtils.disableCarrierAppsUntilPrivileged(CALLING_PACKAGE, mPackageManager,
-                mTelephonyManager, mContentResolver, USER_ID, CARRIER_APPS, ASSOCIATED_APPS);
-        Mockito.verify(mPackageManager).setSystemAppHiddenUntilInstalled(
-                CARRIER_APP, true);
-        Mockito.verify(mPackageManager, Mockito.never()).setSystemAppInstallState(
-                Mockito.anyString(), Mockito.anyBoolean(), Mockito.anyInt());
-        Mockito.verify(mPackageManager, Mockito.never())
-                .grantDefaultPermissionsToEnabledCarrierApps(
-                        Mockito.any(String[].class), Mockito.anyInt());
+        CarrierAppUtils.disableCarrierAppsUntilPrivileged(CALLING_PACKAGE, mTelephonyManager,
+                mContentResolver, USER_ID, CARRIER_APPS, ASSOCIATED_APPS, mContext);
+        Mockito.verify(mPackageManager).setSystemAppState(CARRIER_APP,
+                PackageManager.SYSTEM_APP_STATE_HIDDEN_UNTIL_INSTALLED_HIDDEN);
+        Mockito.verify(mPackageManager, Mockito.never()).setSystemAppState(Mockito.anyString(),
+                Mockito.eq(PackageManager.SYSTEM_APP_STATE_INSTALLED));
+        Mockito.verify(mPackageManager, Mockito.never()).setSystemAppState(Mockito.anyString(),
+                Mockito.eq(PackageManager.SYSTEM_APP_STATE_UNINSTALLED));
     }
 
     /** Telephony is not initialized, and app was disabled by the user - should do nothing. */
@@ -405,21 +448,23 @@
         ApplicationInfo appInfo = new ApplicationInfo();
         appInfo.packageName = CARRIER_APP;
         appInfo.flags |= ApplicationInfo.FLAG_SYSTEM | ApplicationInfo.FLAG_INSTALLED;
-        appInfo.enabledSetting = PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER;
+        Mockito.when(mPackageManager
+                .getApplicationEnabledSetting(Mockito.anyString()))
+                .thenReturn(PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER);
         Mockito.when(mPackageManager.getApplicationInfo(CARRIER_APP,
                 PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS
-                        | PackageManager.MATCH_HIDDEN_UNTIL_INSTALLED_COMPONENTS,
-                USER_ID)).thenReturn(appInfo);
-        CarrierAppUtils.disableCarrierAppsUntilPrivileged(CALLING_PACKAGE, mPackageManager,
+                        | PackageManager.MATCH_HIDDEN_UNTIL_INSTALLED_COMPONENTS
+                        | PackageManager.MATCH_SYSTEM_ONLY))
+                .thenReturn(appInfo);
+        CarrierAppUtils.disableCarrierAppsUntilPrivileged(CALLING_PACKAGE,
                 null /* telephonyManager */, mContentResolver, USER_ID, CARRIER_APPS,
-                ASSOCIATED_APPS);
-        Mockito.verify(mPackageManager).setSystemAppHiddenUntilInstalled(
-                CARRIER_APP, true);
-        Mockito.verify(mPackageManager, Mockito.never()).setSystemAppInstallState(
-                Mockito.anyString(), Mockito.anyBoolean(), Mockito.anyInt());
-        Mockito.verify(mPackageManager, Mockito.never())
-                .grantDefaultPermissionsToEnabledCarrierApps(
-                        Mockito.any(String[].class), Mockito.anyInt());
+                ASSOCIATED_APPS, mContext);
+        Mockito.verify(mPackageManager).setSystemAppState(CARRIER_APP,
+                PackageManager.SYSTEM_APP_STATE_HIDDEN_UNTIL_INSTALLED_HIDDEN);
+        Mockito.verify(mPackageManager, Mockito.never()).setSystemAppState(Mockito.anyString(),
+                Mockito.eq(PackageManager.SYSTEM_APP_STATE_INSTALLED));
+        Mockito.verify(mPackageManager, Mockito.never()).setSystemAppState(Mockito.anyString(),
+                Mockito.eq(PackageManager.SYSTEM_APP_STATE_UNINSTALLED));
     }
 
     /** Configured app has no privileges, and was uninstalled - should do nothing. */
@@ -428,22 +473,24 @@
         ApplicationInfo appInfo = new ApplicationInfo();
         appInfo.packageName = CARRIER_APP;
         appInfo.flags |= ApplicationInfo.FLAG_SYSTEM | ApplicationInfo.FLAG_INSTALLED;
-        appInfo.enabledSetting = PackageManager.COMPONENT_ENABLED_STATE_DISABLED;
+        Mockito.when(mPackageManager
+                .getApplicationEnabledSetting(Mockito.anyString()))
+                .thenReturn(PackageManager.COMPONENT_ENABLED_STATE_DISABLED);
         Mockito.when(mPackageManager.getApplicationInfo(CARRIER_APP,
                 PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS
-                        | PackageManager.MATCH_HIDDEN_UNTIL_INSTALLED_COMPONENTS,
-                USER_ID)).thenReturn(appInfo);
+                        | PackageManager.MATCH_HIDDEN_UNTIL_INSTALLED_COMPONENTS
+                        | PackageManager.MATCH_SYSTEM_ONLY))
+                .thenReturn(appInfo);
         Mockito.when(mTelephonyManager.checkCarrierPrivilegesForPackageAnyPhone(CARRIER_APP))
                 .thenReturn(TelephonyManager.CARRIER_PRIVILEGE_STATUS_NO_ACCESS);
-        CarrierAppUtils.disableCarrierAppsUntilPrivileged(CALLING_PACKAGE, mPackageManager,
-                mTelephonyManager, mContentResolver, USER_ID, CARRIER_APPS, ASSOCIATED_APPS);
-        Mockito.verify(mPackageManager).setSystemAppHiddenUntilInstalled(
-                CARRIER_APP, true);
-        Mockito.verify(mPackageManager, Mockito.never()).setSystemAppInstallState(
-                Mockito.anyString(), Mockito.anyBoolean(), Mockito.anyInt());
-        Mockito.verify(mPackageManager, Mockito.never())
-                .grantDefaultPermissionsToEnabledCarrierApps(
-                        Mockito.any(String[].class), Mockito.anyInt());
+        CarrierAppUtils.disableCarrierAppsUntilPrivileged(CALLING_PACKAGE, mTelephonyManager,
+                mContentResolver, USER_ID, CARRIER_APPS, ASSOCIATED_APPS, mContext);
+        Mockito.verify(mPackageManager).setSystemAppState(CARRIER_APP,
+                PackageManager.SYSTEM_APP_STATE_HIDDEN_UNTIL_INSTALLED_HIDDEN);
+        Mockito.verify(mPackageManager, Mockito.never()).setSystemAppState(Mockito.anyString(),
+                Mockito.eq(PackageManager.SYSTEM_APP_STATE_INSTALLED));
+        Mockito.verify(mPackageManager, Mockito.never()).setSystemAppState(Mockito.anyString(),
+                Mockito.eq(PackageManager.SYSTEM_APP_STATE_UNINSTALLED));
     }
 
     /** Telephony is not initialized, and app was uninstalled - should do nothing. */
@@ -452,21 +499,23 @@
         ApplicationInfo appInfo = new ApplicationInfo();
         appInfo.packageName = CARRIER_APP;
         appInfo.flags |= ApplicationInfo.FLAG_SYSTEM | ApplicationInfo.FLAG_INSTALLED;
-        appInfo.enabledSetting = PackageManager.COMPONENT_ENABLED_STATE_DISABLED;
+        Mockito.when(mPackageManager
+                .getApplicationEnabledSetting(Mockito.anyString()))
+                .thenReturn(PackageManager.COMPONENT_ENABLED_STATE_DISABLED);
         Mockito.when(mPackageManager.getApplicationInfo(CARRIER_APP,
                 PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS
-                        | PackageManager.MATCH_HIDDEN_UNTIL_INSTALLED_COMPONENTS,
-                USER_ID)).thenReturn(appInfo);
-        CarrierAppUtils.disableCarrierAppsUntilPrivileged(CALLING_PACKAGE, mPackageManager,
+                        | PackageManager.MATCH_HIDDEN_UNTIL_INSTALLED_COMPONENTS
+                        | PackageManager.MATCH_SYSTEM_ONLY))
+                .thenReturn(appInfo);
+        CarrierAppUtils.disableCarrierAppsUntilPrivileged(CALLING_PACKAGE,
                 null /* telephonyManager */, mContentResolver, USER_ID, CARRIER_APPS,
-                ASSOCIATED_APPS);
-        Mockito.verify(mPackageManager).setSystemAppHiddenUntilInstalled(
-                CARRIER_APP, true);
-        Mockito.verify(mPackageManager, Mockito.never()).setSystemAppInstallState(
-                Mockito.anyString(), Mockito.anyBoolean(), Mockito.anyInt());
-        Mockito.verify(mPackageManager, Mockito.never())
-                .grantDefaultPermissionsToEnabledCarrierApps(
-                        Mockito.any(String[].class), Mockito.anyInt());
+                ASSOCIATED_APPS, mContext);
+        Mockito.verify(mPackageManager).setSystemAppState(CARRIER_APP,
+                PackageManager.SYSTEM_APP_STATE_HIDDEN_UNTIL_INSTALLED_HIDDEN);
+        Mockito.verify(mPackageManager, Mockito.never()).setSystemAppState(Mockito.anyString(),
+                Mockito.eq(PackageManager.SYSTEM_APP_STATE_INSTALLED));
+        Mockito.verify(mPackageManager, Mockito.never()).setSystemAppState(Mockito.anyString(),
+                Mockito.eq(PackageManager.SYSTEM_APP_STATE_UNINSTALLED));
     }
 
     /** Configured app has no privileges, and is explicitly installed - should do nothing. */
@@ -475,22 +524,24 @@
         ApplicationInfo appInfo = new ApplicationInfo();
         appInfo.packageName = CARRIER_APP;
         appInfo.flags |= ApplicationInfo.FLAG_SYSTEM | ApplicationInfo.FLAG_INSTALLED;
-        appInfo.enabledSetting = PackageManager.COMPONENT_ENABLED_STATE_ENABLED;
+        Mockito.when(mPackageManager
+                .getApplicationEnabledSetting(Mockito.anyString()))
+                .thenReturn(PackageManager.COMPONENT_ENABLED_STATE_ENABLED);
         Mockito.when(mPackageManager.getApplicationInfo(CARRIER_APP,
                 PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS
-                        | PackageManager.MATCH_HIDDEN_UNTIL_INSTALLED_COMPONENTS,
-                USER_ID)).thenReturn(appInfo);
+                        | PackageManager.MATCH_HIDDEN_UNTIL_INSTALLED_COMPONENTS
+                        | PackageManager.MATCH_SYSTEM_ONLY))
+                .thenReturn(appInfo);
         Mockito.when(mTelephonyManager.checkCarrierPrivilegesForPackageAnyPhone(CARRIER_APP))
                 .thenReturn(TelephonyManager.CARRIER_PRIVILEGE_STATUS_NO_ACCESS);
-        CarrierAppUtils.disableCarrierAppsUntilPrivileged(CALLING_PACKAGE, mPackageManager,
-                mTelephonyManager, mContentResolver, USER_ID, CARRIER_APPS, ASSOCIATED_APPS);
-        Mockito.verify(mPackageManager).setSystemAppHiddenUntilInstalled(
-                CARRIER_APP, true);
-        Mockito.verify(mPackageManager, Mockito.never()).setSystemAppInstallState(
-                Mockito.anyString(), Mockito.anyBoolean(), Mockito.anyInt());
-        Mockito.verify(mPackageManager, Mockito.never())
-                .grantDefaultPermissionsToEnabledCarrierApps(
-                        Mockito.any(String[].class), Mockito.anyInt());
+        CarrierAppUtils.disableCarrierAppsUntilPrivileged(CALLING_PACKAGE, mTelephonyManager,
+                mContentResolver, USER_ID, CARRIER_APPS, ASSOCIATED_APPS, mContext);
+        Mockito.verify(mPackageManager).setSystemAppState(CARRIER_APP,
+                PackageManager.SYSTEM_APP_STATE_HIDDEN_UNTIL_INSTALLED_HIDDEN);
+        Mockito.verify(mPackageManager, Mockito.never()).setSystemAppState(Mockito.anyString(),
+                Mockito.eq(PackageManager.SYSTEM_APP_STATE_INSTALLED));
+        Mockito.verify(mPackageManager, Mockito.never()).setSystemAppState(Mockito.anyString(),
+                Mockito.eq(PackageManager.SYSTEM_APP_STATE_UNINSTALLED));
     }
 
     /** Telephony is not initialized, and app is explicitly installed - should do nothing. */
@@ -499,21 +550,23 @@
         ApplicationInfo appInfo = new ApplicationInfo();
         appInfo.packageName = CARRIER_APP;
         appInfo.flags |= ApplicationInfo.FLAG_SYSTEM | ApplicationInfo.FLAG_INSTALLED;
-        appInfo.enabledSetting = PackageManager.COMPONENT_ENABLED_STATE_ENABLED;
+        Mockito.when(mPackageManager
+                .getApplicationEnabledSetting(Mockito.anyString()))
+                .thenReturn(PackageManager.COMPONENT_ENABLED_STATE_ENABLED);
         Mockito.when(mPackageManager.getApplicationInfo(CARRIER_APP,
                 PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS
-                        | PackageManager.MATCH_HIDDEN_UNTIL_INSTALLED_COMPONENTS,
-                USER_ID)).thenReturn(appInfo);
-        CarrierAppUtils.disableCarrierAppsUntilPrivileged(CALLING_PACKAGE, mPackageManager,
+                        | PackageManager.MATCH_HIDDEN_UNTIL_INSTALLED_COMPONENTS
+                        | PackageManager.MATCH_SYSTEM_ONLY))
+                .thenReturn(appInfo);
+        CarrierAppUtils.disableCarrierAppsUntilPrivileged(CALLING_PACKAGE,
                 null /* telephonyManager */, mContentResolver, USER_ID, CARRIER_APPS,
-                ASSOCIATED_APPS);
-        Mockito.verify(mPackageManager).setSystemAppHiddenUntilInstalled(
-                CARRIER_APP, true);
-        Mockito.verify(mPackageManager, Mockito.never()).setSystemAppInstallState(
-                Mockito.anyString(), Mockito.anyBoolean(), Mockito.anyInt());
-        Mockito.verify(mPackageManager, Mockito.never())
-                .grantDefaultPermissionsToEnabledCarrierApps(
-                        Mockito.any(String[].class), Mockito.anyInt());
+                ASSOCIATED_APPS, mContext);
+        Mockito.verify(mPackageManager).setSystemAppState(CARRIER_APP,
+                PackageManager.SYSTEM_APP_STATE_HIDDEN_UNTIL_INSTALLED_HIDDEN);
+        Mockito.verify(mPackageManager, Mockito.never()).setSystemAppState(Mockito.anyString(),
+                Mockito.eq(PackageManager.SYSTEM_APP_STATE_INSTALLED));
+        Mockito.verify(mPackageManager, Mockito.never()).setSystemAppState(Mockito.anyString(),
+                Mockito.eq(PackageManager.SYSTEM_APP_STATE_UNINSTALLED));
     }
 
     /** Configured /data app has no privileges - should do nothing. */
@@ -523,22 +576,24 @@
         appInfo.packageName = CARRIER_APP;
         appInfo.flags |= ApplicationInfo.FLAG_SYSTEM | ApplicationInfo.FLAG_UPDATED_SYSTEM_APP
                 | ApplicationInfo.FLAG_INSTALLED;
-        appInfo.enabledSetting = PackageManager.COMPONENT_ENABLED_STATE_ENABLED;
+        Mockito.when(mPackageManager
+                .getApplicationEnabledSetting(Mockito.anyString()))
+                .thenReturn(PackageManager.COMPONENT_ENABLED_STATE_ENABLED);
         Mockito.when(mPackageManager.getApplicationInfo(CARRIER_APP,
                 PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS
-                        | PackageManager.MATCH_HIDDEN_UNTIL_INSTALLED_COMPONENTS,
-                USER_ID)).thenReturn(appInfo);
+                        | PackageManager.MATCH_HIDDEN_UNTIL_INSTALLED_COMPONENTS
+                        | PackageManager.MATCH_SYSTEM_ONLY))
+                .thenReturn(appInfo);
         Mockito.when(mTelephonyManager.checkCarrierPrivilegesForPackageAnyPhone(CARRIER_APP))
                 .thenReturn(TelephonyManager.CARRIER_PRIVILEGE_STATUS_NO_ACCESS);
-        CarrierAppUtils.disableCarrierAppsUntilPrivileged(CALLING_PACKAGE, mPackageManager,
-                mTelephonyManager, mContentResolver, USER_ID, CARRIER_APPS, ASSOCIATED_APPS);
-        Mockito.verify(mPackageManager).setSystemAppHiddenUntilInstalled(
-                CARRIER_APP, true);
-        Mockito.verify(mPackageManager, Mockito.never()).setSystemAppInstallState(
-                Mockito.anyString(), Mockito.anyBoolean(), Mockito.anyInt());
-        Mockito.verify(mPackageManager, Mockito.never())
-                .grantDefaultPermissionsToEnabledCarrierApps(
-                        Mockito.any(String[].class), Mockito.anyInt());
+        CarrierAppUtils.disableCarrierAppsUntilPrivileged(CALLING_PACKAGE, mTelephonyManager,
+                mContentResolver, USER_ID, CARRIER_APPS, ASSOCIATED_APPS, mContext);
+        Mockito.verify(mPackageManager).setSystemAppState(CARRIER_APP,
+                PackageManager.SYSTEM_APP_STATE_HIDDEN_UNTIL_INSTALLED_HIDDEN);
+        Mockito.verify(mPackageManager, Mockito.never()).setSystemAppState(Mockito.anyString(),
+                Mockito.eq(PackageManager.SYSTEM_APP_STATE_INSTALLED));
+        Mockito.verify(mPackageManager, Mockito.never()).setSystemAppState(Mockito.anyString(),
+                Mockito.eq(PackageManager.SYSTEM_APP_STATE_UNINSTALLED));
     }
 
     /** Telephony is not initialized and app is in /data - should do nothing. */
@@ -548,21 +603,23 @@
         appInfo.packageName = CARRIER_APP;
         appInfo.flags |= (ApplicationInfo.FLAG_SYSTEM
                 | ApplicationInfo.FLAG_UPDATED_SYSTEM_APP | ApplicationInfo.FLAG_INSTALLED);
-        appInfo.enabledSetting = PackageManager.COMPONENT_ENABLED_STATE_ENABLED;
+        Mockito.when(mPackageManager
+                .getApplicationEnabledSetting(Mockito.anyString()))
+                .thenReturn(PackageManager.COMPONENT_ENABLED_STATE_ENABLED);
         Mockito.when(mPackageManager.getApplicationInfo(CARRIER_APP,
                 PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS
-                        | PackageManager.MATCH_HIDDEN_UNTIL_INSTALLED_COMPONENTS,
-                USER_ID)).thenReturn(appInfo);
-        CarrierAppUtils.disableCarrierAppsUntilPrivileged(CALLING_PACKAGE, mPackageManager,
+                        | PackageManager.MATCH_HIDDEN_UNTIL_INSTALLED_COMPONENTS
+                        | PackageManager.MATCH_SYSTEM_ONLY))
+                .thenReturn(appInfo);
+        CarrierAppUtils.disableCarrierAppsUntilPrivileged(CALLING_PACKAGE,
                 null /* telephonyManager */, mContentResolver, USER_ID, CARRIER_APPS,
-                ASSOCIATED_APPS);
-        Mockito.verify(mPackageManager).setSystemAppHiddenUntilInstalled(
-                CARRIER_APP, true);
-        Mockito.verify(mPackageManager, Mockito.never()).setSystemAppInstallState(
-                Mockito.anyString(), Mockito.anyBoolean(), Mockito.anyInt());
-        Mockito.verify(mPackageManager, Mockito.never())
-                .grantDefaultPermissionsToEnabledCarrierApps(
-                        Mockito.any(String[].class), Mockito.anyInt());
+                ASSOCIATED_APPS, mContext);
+        Mockito.verify(mPackageManager).setSystemAppState(CARRIER_APP,
+                PackageManager.SYSTEM_APP_STATE_HIDDEN_UNTIL_INSTALLED_HIDDEN);
+        Mockito.verify(mPackageManager, Mockito.never()).setSystemAppState(Mockito.anyString(),
+                Mockito.eq(PackageManager.SYSTEM_APP_STATE_INSTALLED));
+        Mockito.verify(mPackageManager, Mockito.never()).setSystemAppState(Mockito.anyString(),
+                Mockito.eq(PackageManager.SYSTEM_APP_STATE_UNINSTALLED));
     }
 
     /**
@@ -575,34 +632,37 @@
         ApplicationInfo appInfo = new ApplicationInfo();
         appInfo.packageName = CARRIER_APP;
         appInfo.flags |= ApplicationInfo.FLAG_SYSTEM | ApplicationInfo.FLAG_INSTALLED;
-        appInfo.enabledSetting = PackageManager.COMPONENT_ENABLED_STATE_DEFAULT;
+        Mockito.when(mPackageManager.getApplicationEnabledSetting(CARRIER_APP))
+                .thenReturn(PackageManager.COMPONENT_ENABLED_STATE_DEFAULT);
         Mockito.when(mPackageManager.getApplicationInfo(CARRIER_APP,
                 PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS
-                        | PackageManager.MATCH_HIDDEN_UNTIL_INSTALLED_COMPONENTS,
-                USER_ID)).thenReturn(appInfo);
+                        | PackageManager.MATCH_HIDDEN_UNTIL_INSTALLED_COMPONENTS
+                        | PackageManager.MATCH_SYSTEM_ONLY))
+                .thenReturn(appInfo);
         ApplicationInfo associatedAppInfo = new ApplicationInfo();
         associatedAppInfo.packageName = ASSOCIATED_APP;
         associatedAppInfo.flags |= ApplicationInfo.FLAG_SYSTEM;
-        associatedAppInfo.enabledSetting = PackageManager.COMPONENT_ENABLED_STATE_ENABLED;
+        Mockito.when(mPackageManager.getApplicationEnabledSetting(ASSOCIATED_APP))
+                .thenReturn(PackageManager.COMPONENT_ENABLED_STATE_ENABLED);
         Mockito.when(mPackageManager.getApplicationInfo(ASSOCIATED_APP,
                 PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS
-                        | PackageManager.MATCH_HIDDEN_UNTIL_INSTALLED_COMPONENTS, USER_ID))
+                        | PackageManager.MATCH_HIDDEN_UNTIL_INSTALLED_COMPONENTS
+                        | PackageManager.MATCH_SYSTEM_ONLY))
                 .thenReturn(associatedAppInfo);
         Mockito.when(mTelephonyManager.checkCarrierPrivilegesForPackageAnyPhone(CARRIER_APP))
                 .thenReturn(TelephonyManager.CARRIER_PRIVILEGE_STATUS_NO_ACCESS);
-        CarrierAppUtils.disableCarrierAppsUntilPrivileged(CALLING_PACKAGE, mPackageManager,
-                mTelephonyManager, mContentResolver, USER_ID, CARRIER_APPS, ASSOCIATED_APPS);
-        Mockito.verify(mPackageManager).setSystemAppHiddenUntilInstalled(
-                CARRIER_APP, true);
-        Mockito.verify(mPackageManager).setSystemAppHiddenUntilInstalled(
-                ASSOCIATED_APP, true);
-        Mockito.verify(mPackageManager).setSystemAppInstallState(
-                CARRIER_APP, false, USER_ID);
-        Mockito.verify(mPackageManager, Mockito.never()).setSystemAppInstallState(
-                ASSOCIATED_APP, false, USER_ID);
-        Mockito.verify(mPackageManager, Mockito.never())
-                .grantDefaultPermissionsToEnabledCarrierApps(
-                        Mockito.any(String[].class), Mockito.anyInt());
+        CarrierAppUtils.disableCarrierAppsUntilPrivileged(CALLING_PACKAGE, mTelephonyManager,
+                mContentResolver, USER_ID, CARRIER_APPS, ASSOCIATED_APPS, mContext);
+        Mockito.verify(mPackageManager).setSystemAppState(CARRIER_APP,
+                PackageManager.SYSTEM_APP_STATE_HIDDEN_UNTIL_INSTALLED_HIDDEN);
+        Mockito.verify(mPackageManager).setSystemAppState(ASSOCIATED_APP,
+                PackageManager.SYSTEM_APP_STATE_HIDDEN_UNTIL_INSTALLED_HIDDEN);
+        Mockito.verify(mPackageManager).setSystemAppState(CARRIER_APP,
+                PackageManager.SYSTEM_APP_STATE_UNINSTALLED);
+        Mockito.verify(mPackageManager, Mockito.never()).setSystemAppState(ASSOCIATED_APP,
+                PackageManager.SYSTEM_APP_STATE_INSTALLED);
+        Mockito.verify(mPackageManager, Mockito.never()).setSystemAppState(ASSOCIATED_APP,
+                PackageManager.SYSTEM_APP_STATE_UNINSTALLED);
     }
 
     /**
@@ -615,34 +675,35 @@
         ApplicationInfo appInfo = new ApplicationInfo();
         appInfo.packageName = CARRIER_APP;
         appInfo.flags |= ApplicationInfo.FLAG_SYSTEM | ApplicationInfo.FLAG_INSTALLED;
-        appInfo.enabledSetting = PackageManager.COMPONENT_ENABLED_STATE_DEFAULT;
+        Mockito.when(mPackageManager.getApplicationEnabledSetting(CARRIER_APP))
+                .thenReturn(PackageManager.COMPONENT_ENABLED_STATE_DEFAULT);
         Mockito.when(mPackageManager.getApplicationInfo(CARRIER_APP,
                 PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS
-                        | PackageManager.MATCH_HIDDEN_UNTIL_INSTALLED_COMPONENTS,
-                USER_ID)).thenReturn(appInfo);
+                        | PackageManager.MATCH_HIDDEN_UNTIL_INSTALLED_COMPONENTS
+                        | PackageManager.MATCH_SYSTEM_ONLY))
+                .thenReturn(appInfo);
         ApplicationInfo associatedAppInfo = new ApplicationInfo();
         associatedAppInfo.packageName = ASSOCIATED_APP;
         associatedAppInfo.flags |= ApplicationInfo.FLAG_SYSTEM | ApplicationInfo.FLAG_INSTALLED;
-        associatedAppInfo.enabledSetting = PackageManager.COMPONENT_ENABLED_STATE_DEFAULT;
+        Mockito.when(mPackageManager.getApplicationEnabledSetting(ASSOCIATED_APP))
+                .thenReturn(PackageManager.COMPONENT_ENABLED_STATE_DEFAULT);
         Mockito.when(mPackageManager.getApplicationInfo(ASSOCIATED_APP,
                 PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS
-                        | PackageManager.MATCH_HIDDEN_UNTIL_INSTALLED_COMPONENTS, USER_ID))
+                        | PackageManager.MATCH_HIDDEN_UNTIL_INSTALLED_COMPONENTS
+                        | PackageManager.MATCH_SYSTEM_ONLY))
                 .thenReturn(associatedAppInfo);
         Mockito.when(mTelephonyManager.checkCarrierPrivilegesForPackageAnyPhone(CARRIER_APP))
                 .thenReturn(TelephonyManager.CARRIER_PRIVILEGE_STATUS_NO_ACCESS);
-        CarrierAppUtils.disableCarrierAppsUntilPrivileged(CALLING_PACKAGE, mPackageManager,
-                mTelephonyManager, mContentResolver, USER_ID, CARRIER_APPS, ASSOCIATED_APPS);
-        Mockito.verify(mPackageManager).setSystemAppHiddenUntilInstalled(
-                CARRIER_APP, true);
-        Mockito.verify(mPackageManager).setSystemAppHiddenUntilInstalled(
-                ASSOCIATED_APP, true);
-        Mockito.verify(mPackageManager).setSystemAppInstallState(
-                CARRIER_APP, false, USER_ID);
-        Mockito.verify(mPackageManager).setSystemAppInstallState(
-                ASSOCIATED_APP, false, USER_ID);
-        Mockito.verify(mPackageManager, Mockito.never())
-                .grantDefaultPermissionsToEnabledCarrierApps(
-                        Mockito.any(String[].class), Mockito.anyInt());
+        CarrierAppUtils.disableCarrierAppsUntilPrivileged(CALLING_PACKAGE, mTelephonyManager,
+                mContentResolver, USER_ID, CARRIER_APPS, ASSOCIATED_APPS, mContext);
+        Mockito.verify(mPackageManager).setSystemAppState(CARRIER_APP,
+                PackageManager.SYSTEM_APP_STATE_HIDDEN_UNTIL_INSTALLED_HIDDEN);
+        Mockito.verify(mPackageManager).setSystemAppState(ASSOCIATED_APP,
+                PackageManager.SYSTEM_APP_STATE_HIDDEN_UNTIL_INSTALLED_HIDDEN);
+        Mockito.verify(mPackageManager).setSystemAppState(CARRIER_APP,
+                PackageManager.SYSTEM_APP_STATE_UNINSTALLED);
+        Mockito.verify(mPackageManager).setSystemAppState(ASSOCIATED_APP,
+                PackageManager.SYSTEM_APP_STATE_UNINSTALLED);
     }
 
     /**
@@ -650,41 +711,388 @@
      * disabling has already occurred - should only uninstall configured app.
      */
     @Test @SmallTest
-    public void testDisableCarrierAppsUntilPrivileged_NoPrivileges_Associated_Default_AlreadyRun()
+    public void testDisableCarrierAppsUntilPrivileged_NoPrivileges_Associated_Default_HandledSdk()
             throws Exception {
         Settings.Secure.putIntForUser(
-                mContentResolver, Settings.Secure.CARRIER_APPS_HANDLED, 1, USER_ID);
+                mContentResolver, Settings.Secure.CARRIER_APPS_HANDLED, Build.VERSION.SDK_INT,
+                USER_ID);
         ApplicationInfo appInfo = new ApplicationInfo();
         appInfo.packageName = CARRIER_APP;
         appInfo.flags |= ApplicationInfo.FLAG_SYSTEM | ApplicationInfo.FLAG_INSTALLED;
-        appInfo.enabledSetting = PackageManager.COMPONENT_ENABLED_STATE_DEFAULT;
+        Mockito.when(mPackageManager
+                .getApplicationEnabledSetting(Mockito.anyString()))
+                .thenReturn(PackageManager.COMPONENT_ENABLED_STATE_DEFAULT);
         Mockito.when(mPackageManager.getApplicationInfo(CARRIER_APP,
                 PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS
-                        | PackageManager.MATCH_HIDDEN_UNTIL_INSTALLED_COMPONENTS,
-                USER_ID)).thenReturn(appInfo);
+                        | PackageManager.MATCH_HIDDEN_UNTIL_INSTALLED_COMPONENTS
+                        | PackageManager.MATCH_SYSTEM_ONLY))
+                .thenReturn(appInfo);
         ApplicationInfo associatedAppInfo = new ApplicationInfo();
         associatedAppInfo.packageName = ASSOCIATED_APP;
-        associatedAppInfo.flags |= ApplicationInfo.FLAG_SYSTEM;
+        associatedAppInfo.flags |= ApplicationInfo.FLAG_SYSTEM | ApplicationInfo.FLAG_INSTALLED;
         associatedAppInfo.enabledSetting = PackageManager.COMPONENT_ENABLED_STATE_DEFAULT;
         Mockito.when(mPackageManager.getApplicationInfo(ASSOCIATED_APP,
                 PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS
-                        | PackageManager.MATCH_HIDDEN_UNTIL_INSTALLED_COMPONENTS, USER_ID))
+                        | PackageManager.MATCH_HIDDEN_UNTIL_INSTALLED_COMPONENTS
+                        | PackageManager.MATCH_SYSTEM_ONLY))
                 .thenReturn(associatedAppInfo);
         Mockito.when(mTelephonyManager.checkCarrierPrivilegesForPackageAnyPhone(CARRIER_APP))
                 .thenReturn(TelephonyManager.CARRIER_PRIVILEGE_STATUS_NO_ACCESS);
-        CarrierAppUtils.disableCarrierAppsUntilPrivileged(CALLING_PACKAGE, mPackageManager,
-                mTelephonyManager, mContentResolver, USER_ID, CARRIER_APPS, ASSOCIATED_APPS);
-        Mockito.verify(mPackageManager).setSystemAppHiddenUntilInstalled(
-                CARRIER_APP, true);
-        Mockito.verify(mPackageManager).setSystemAppHiddenUntilInstalled(
-                ASSOCIATED_APP, true);
-        Mockito.verify(mPackageManager).setSystemAppInstallState(
-                CARRIER_APP, false, USER_ID);
-        Mockito.verify(mPackageManager, Mockito.never()).setSystemAppInstallState(
-                Mockito.eq(ASSOCIATED_APP), Mockito.anyBoolean(), Mockito.anyInt());
-        Mockito.verify(mPackageManager, Mockito.never())
-                .grantDefaultPermissionsToEnabledCarrierApps(
-                        Mockito.any(String[].class), Mockito.anyInt());
+        // Different associated app SDK than usual.
+        CarrierAppUtils.disableCarrierAppsUntilPrivileged(CALLING_PACKAGE, mTelephonyManager,
+                mContentResolver, USER_ID, CARRIER_APPS,
+                makeAssociatedApp(CARRIER_APP, ASSOCIATED_APP, Build.VERSION.SDK_INT), mContext);
+        Mockito.verify(mPackageManager).setSystemAppState(CARRIER_APP,
+                PackageManager.SYSTEM_APP_STATE_HIDDEN_UNTIL_INSTALLED_HIDDEN);
+        Mockito.verify(mPackageManager).setSystemAppState(ASSOCIATED_APP,
+                PackageManager.SYSTEM_APP_STATE_HIDDEN_UNTIL_INSTALLED_HIDDEN);
+        Mockito.verify(mPackageManager).setSystemAppState(CARRIER_APP,
+                PackageManager.SYSTEM_APP_STATE_UNINSTALLED);
+        Mockito.verify(mPackageManager, Mockito.never()).setSystemAppState(ASSOCIATED_APP,
+                PackageManager.SYSTEM_APP_STATE_INSTALLED);
+        Mockito.verify(mPackageManager, Mockito.never()).setSystemAppState(ASSOCIATED_APP,
+                PackageManager.SYSTEM_APP_STATE_UNINSTALLED);
+    }
+
+    /**
+     * Configured app has no privileges, and is in the default state along with associated app, and
+     * disabling has already occurred - should only uninstall configured app.
+     */
+    @Test @SmallTest
+    public void testDCAUP_NoPrivileges_Associated_Default_HandledSdk_AssociatedSdkUnspecified()
+            throws Exception {
+        Settings.Secure.putIntForUser(
+                mContentResolver, Settings.Secure.CARRIER_APPS_HANDLED, Build.VERSION.SDK_INT,
+                USER_ID);
+        ApplicationInfo appInfo = new ApplicationInfo();
+        appInfo.packageName = CARRIER_APP;
+        appInfo.flags |= ApplicationInfo.FLAG_SYSTEM | ApplicationInfo.FLAG_INSTALLED;
+        Mockito.when(mPackageManager
+                .getApplicationEnabledSetting(Mockito.anyString()))
+                .thenReturn(PackageManager.COMPONENT_ENABLED_STATE_DEFAULT);
+        Mockito.when(mPackageManager.getApplicationInfo(CARRIER_APP,
+                PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS
+                        | PackageManager.MATCH_HIDDEN_UNTIL_INSTALLED_COMPONENTS
+                        | PackageManager.MATCH_SYSTEM_ONLY))
+                .thenReturn(appInfo);
+        ApplicationInfo associatedAppInfo = new ApplicationInfo();
+        associatedAppInfo.packageName = ASSOCIATED_APP;
+        associatedAppInfo.flags |= ApplicationInfo.FLAG_SYSTEM | ApplicationInfo.FLAG_INSTALLED;
+        associatedAppInfo.enabledSetting = PackageManager.COMPONENT_ENABLED_STATE_DEFAULT;
+        Mockito.when(mPackageManager.getApplicationInfo(ASSOCIATED_APP,
+                PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS
+                        | PackageManager.MATCH_HIDDEN_UNTIL_INSTALLED_COMPONENTS
+                        | PackageManager.MATCH_SYSTEM_ONLY))
+                .thenReturn(associatedAppInfo);
+        Mockito.when(mTelephonyManager.checkCarrierPrivilegesForPackageAnyPhone(CARRIER_APP))
+                .thenReturn(TelephonyManager.CARRIER_PRIVILEGE_STATUS_NO_ACCESS);
+        // Using SDK_UNSPECIFIED for the associated app's addedInSdk.
+        CarrierAppUtils.disableCarrierAppsUntilPrivileged(CALLING_PACKAGE, mTelephonyManager,
+                mContentResolver, USER_ID, CARRIER_APPS, ASSOCIATED_APPS, mContext);
+        Mockito.verify(mPackageManager).setSystemAppState(CARRIER_APP,
+                PackageManager.SYSTEM_APP_STATE_HIDDEN_UNTIL_INSTALLED_HIDDEN);
+        Mockito.verify(mPackageManager).setSystemAppState(ASSOCIATED_APP,
+                PackageManager.SYSTEM_APP_STATE_HIDDEN_UNTIL_INSTALLED_HIDDEN);
+        Mockito.verify(mPackageManager).setSystemAppState(CARRIER_APP,
+                PackageManager.SYSTEM_APP_STATE_UNINSTALLED);
+        Mockito.verify(mPackageManager, Mockito.never()).setSystemAppState(ASSOCIATED_APP,
+                PackageManager.SYSTEM_APP_STATE_INSTALLED);
+        Mockito.verify(mPackageManager, Mockito.never()).setSystemAppState(ASSOCIATED_APP,
+                PackageManager.SYSTEM_APP_STATE_UNINSTALLED);
+    }
+
+    /**
+     * Configured app has no privileges, and is in the default state along with associated app, and
+     * disabling has not yet occurred on this SDK level - should uninstall both since the associated
+     * app's SDK matches.
+     */
+    @Test @SmallTest
+    public void testDCAUP_NoPrivileges_Associated_Default_NewSdk_AssociatedSdkCurrent()
+            throws Exception {
+        Settings.Secure.putIntForUser(
+                mContentResolver, Settings.Secure.CARRIER_APPS_HANDLED,
+                Build.VERSION.SDK_INT - 1, USER_ID);
+        ApplicationInfo appInfo = new ApplicationInfo();
+        appInfo.packageName = CARRIER_APP;
+        appInfo.flags |= ApplicationInfo.FLAG_SYSTEM | ApplicationInfo.FLAG_INSTALLED;
+        Mockito.when(mPackageManager
+                .getApplicationEnabledSetting(Mockito.anyString()))
+                .thenReturn(PackageManager.COMPONENT_ENABLED_STATE_DEFAULT);
+        Mockito.when(mPackageManager.getApplicationInfo(CARRIER_APP,
+                PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS
+                        | PackageManager.MATCH_HIDDEN_UNTIL_INSTALLED_COMPONENTS
+                        | PackageManager.MATCH_SYSTEM_ONLY))
+                .thenReturn(appInfo);
+        ApplicationInfo associatedAppInfo = new ApplicationInfo();
+        associatedAppInfo.packageName = ASSOCIATED_APP;
+        associatedAppInfo.flags |= ApplicationInfo.FLAG_SYSTEM | ApplicationInfo.FLAG_INSTALLED;
+        associatedAppInfo.enabledSetting = PackageManager.COMPONENT_ENABLED_STATE_DEFAULT;
+        Mockito.when(mPackageManager.getApplicationInfo(ASSOCIATED_APP,
+                PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS
+                        | PackageManager.MATCH_HIDDEN_UNTIL_INSTALLED_COMPONENTS
+                        | PackageManager.MATCH_SYSTEM_ONLY))
+                .thenReturn(associatedAppInfo);
+        Mockito.when(mTelephonyManager.checkCarrierPrivilegesForPackageAnyPhone(CARRIER_APP))
+                .thenReturn(TelephonyManager.CARRIER_PRIVILEGE_STATUS_NO_ACCESS);
+        // Different associated app SDK than usual.
+        CarrierAppUtils.disableCarrierAppsUntilPrivileged(CALLING_PACKAGE, mTelephonyManager,
+                mContentResolver, USER_ID, CARRIER_APPS,
+                makeAssociatedApp(CARRIER_APP, ASSOCIATED_APP, Build.VERSION.SDK_INT), mContext);
+        Mockito.verify(mPackageManager).setSystemAppState(CARRIER_APP,
+                PackageManager.SYSTEM_APP_STATE_HIDDEN_UNTIL_INSTALLED_HIDDEN);
+        Mockito.verify(mPackageManager).setSystemAppState(ASSOCIATED_APP,
+                PackageManager.SYSTEM_APP_STATE_HIDDEN_UNTIL_INSTALLED_HIDDEN);
+        Mockito.verify(mPackageManager).setSystemAppState(CARRIER_APP,
+                PackageManager.SYSTEM_APP_STATE_UNINSTALLED);
+        Mockito.verify(mPackageManager, Mockito.never()).setSystemAppState(ASSOCIATED_APP,
+                PackageManager.SYSTEM_APP_STATE_INSTALLED);
+        Mockito.verify(mPackageManager).setSystemAppState(ASSOCIATED_APP,
+                PackageManager.SYSTEM_APP_STATE_UNINSTALLED);
+    }
+
+    /**
+     * Configured app has no privileges, and is in the default state along with associated app, and
+     * disabling has not yet occurred on the current SDK - should only uninstall configured app
+     * since the associated app's SDK isn't specified but we've already run at least once.
+     */
+    @Test @SmallTest
+    public void testDCAUP_NoPrivileges_Associated_Default_NewSdk_AssociatedSdkUnspecified()
+            throws Exception {
+        Settings.Secure.putIntForUser(
+                mContentResolver, Settings.Secure.CARRIER_APPS_HANDLED,
+                Build.VERSION.SDK_INT - 1, USER_ID);
+        ApplicationInfo appInfo = new ApplicationInfo();
+        appInfo.packageName = CARRIER_APP;
+        appInfo.flags |= ApplicationInfo.FLAG_SYSTEM | ApplicationInfo.FLAG_INSTALLED;
+        Mockito.when(mPackageManager
+                .getApplicationEnabledSetting(Mockito.anyString()))
+                .thenReturn(PackageManager.COMPONENT_ENABLED_STATE_DEFAULT);
+        Mockito.when(mPackageManager.getApplicationInfo(CARRIER_APP,
+                PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS
+                        | PackageManager.MATCH_HIDDEN_UNTIL_INSTALLED_COMPONENTS
+                        | PackageManager.MATCH_SYSTEM_ONLY))
+                .thenReturn(appInfo);
+        ApplicationInfo associatedAppInfo = new ApplicationInfo();
+        associatedAppInfo.packageName = ASSOCIATED_APP;
+        associatedAppInfo.flags |= ApplicationInfo.FLAG_SYSTEM | ApplicationInfo.FLAG_INSTALLED;
+        associatedAppInfo.enabledSetting = PackageManager.COMPONENT_ENABLED_STATE_DEFAULT;
+        Mockito.when(mPackageManager.getApplicationInfo(ASSOCIATED_APP,
+                PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS
+                        | PackageManager.MATCH_HIDDEN_UNTIL_INSTALLED_COMPONENTS
+                        | PackageManager.MATCH_SYSTEM_ONLY))
+                .thenReturn(associatedAppInfo);
+        Mockito.when(mTelephonyManager.checkCarrierPrivilegesForPackageAnyPhone(CARRIER_APP))
+                .thenReturn(TelephonyManager.CARRIER_PRIVILEGE_STATUS_NO_ACCESS);
+        // Using SDK_UNSPECIFIED for the associated app's addedInSdk.
+        CarrierAppUtils.disableCarrierAppsUntilPrivileged(CALLING_PACKAGE, mTelephonyManager,
+                mContentResolver, USER_ID, CARRIER_APPS, ASSOCIATED_APPS, mContext);
+        Mockito.verify(mPackageManager).setSystemAppState(CARRIER_APP,
+                PackageManager.SYSTEM_APP_STATE_HIDDEN_UNTIL_INSTALLED_HIDDEN);
+        Mockito.verify(mPackageManager).setSystemAppState(ASSOCIATED_APP,
+                PackageManager.SYSTEM_APP_STATE_HIDDEN_UNTIL_INSTALLED_HIDDEN);
+        Mockito.verify(mPackageManager).setSystemAppState(CARRIER_APP,
+                PackageManager.SYSTEM_APP_STATE_UNINSTALLED);
+        Mockito.verify(mPackageManager, Mockito.never()).setSystemAppState(ASSOCIATED_APP,
+                PackageManager.SYSTEM_APP_STATE_INSTALLED);
+        Mockito.verify(mPackageManager, Mockito.never()).setSystemAppState(ASSOCIATED_APP,
+                PackageManager.SYSTEM_APP_STATE_UNINSTALLED);
+    }
+
+    /**
+     * Configured app has no privileges, and is in the default state along with associated app, and
+     * disabling has not yet occurred on the current SDK - should only uninstall configured app
+     * since the associated app's SDK doesn't match.
+     */
+    @Test @SmallTest
+    public void testDCAUP_NoPrivileges_Associated_Default_NewSdk_AssociatedSdkTooLow()
+            throws Exception {
+        Settings.Secure.putIntForUser(
+                mContentResolver, Settings.Secure.CARRIER_APPS_HANDLED,
+                Build.VERSION.SDK_INT - 1, USER_ID);
+        ApplicationInfo appInfo = new ApplicationInfo();
+        appInfo.packageName = CARRIER_APP;
+        appInfo.flags |= ApplicationInfo.FLAG_SYSTEM | ApplicationInfo.FLAG_INSTALLED;
+        Mockito.when(mPackageManager
+                .getApplicationEnabledSetting(Mockito.anyString()))
+                .thenReturn(PackageManager.COMPONENT_ENABLED_STATE_DEFAULT);
+        Mockito.when(mPackageManager.getApplicationInfo(CARRIER_APP,
+                PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS
+                        | PackageManager.MATCH_HIDDEN_UNTIL_INSTALLED_COMPONENTS
+                        | PackageManager.MATCH_SYSTEM_ONLY))
+                .thenReturn(appInfo);
+        ApplicationInfo associatedAppInfo = new ApplicationInfo();
+        associatedAppInfo.packageName = ASSOCIATED_APP;
+        associatedAppInfo.flags |= ApplicationInfo.FLAG_SYSTEM | ApplicationInfo.FLAG_INSTALLED;
+        associatedAppInfo.enabledSetting = PackageManager.COMPONENT_ENABLED_STATE_DEFAULT;
+        Mockito.when(mPackageManager.getApplicationInfo(ASSOCIATED_APP,
+                PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS
+                        | PackageManager.MATCH_HIDDEN_UNTIL_INSTALLED_COMPONENTS
+                        | PackageManager.MATCH_SYSTEM_ONLY))
+                .thenReturn(associatedAppInfo);
+        Mockito.when(mTelephonyManager.checkCarrierPrivilegesForPackageAnyPhone(CARRIER_APP))
+                .thenReturn(TelephonyManager.CARRIER_PRIVILEGE_STATUS_NO_ACCESS);
+        // Different associated app SDK than usual.
+        CarrierAppUtils.disableCarrierAppsUntilPrivileged(CALLING_PACKAGE, mTelephonyManager,
+                mContentResolver, USER_ID, CARRIER_APPS,
+                makeAssociatedApp(CARRIER_APP, ASSOCIATED_APP, Build.VERSION.SDK_INT - 1),
+                mContext);
+        Mockito.verify(mPackageManager).setSystemAppState(CARRIER_APP,
+                PackageManager.SYSTEM_APP_STATE_HIDDEN_UNTIL_INSTALLED_HIDDEN);
+        Mockito.verify(mPackageManager).setSystemAppState(ASSOCIATED_APP,
+                PackageManager.SYSTEM_APP_STATE_HIDDEN_UNTIL_INSTALLED_HIDDEN);
+        Mockito.verify(mPackageManager).setSystemAppState(CARRIER_APP,
+                PackageManager.SYSTEM_APP_STATE_UNINSTALLED);
+        Mockito.verify(mPackageManager, Mockito.never()).setSystemAppState(ASSOCIATED_APP,
+                PackageManager.SYSTEM_APP_STATE_INSTALLED);
+        Mockito.verify(mPackageManager, Mockito.never()).setSystemAppState(ASSOCIATED_APP,
+                PackageManager.SYSTEM_APP_STATE_UNINSTALLED);
+    }
+
+    /**
+     * Configured app has no privileges, and is in the default state along with associated app, and
+     * disabling has not yet occurred on this SDK level - should uninstall both since the associated
+     * app's SDK is newer than the last evaluation.
+     *
+     * While this case is expected to feel somewhat strange, it effectively simulates skipping a
+     * whole SDK level in a single OTA. For example, the device is on P. A new associated app is
+     * added on Q, but the user doesn't take the OTA. Then, they take the R OTA, at which point the
+     * associated app should still be disabled if there's no corresponding SIM, because its SDK
+     * level is newer than our last round of evaluation.
+     */
+    @Test @SmallTest
+    public void testDCAUP_NoPrivileges_Associated_Default_NewSdk_AssociatedSdkInRange()
+            throws Exception {
+        Settings.Secure.putIntForUser(
+                mContentResolver, Settings.Secure.CARRIER_APPS_HANDLED,
+                Build.VERSION.SDK_INT - 2, USER_ID);
+        ApplicationInfo appInfo = new ApplicationInfo();
+        appInfo.packageName = CARRIER_APP;
+        appInfo.flags |= ApplicationInfo.FLAG_SYSTEM | ApplicationInfo.FLAG_INSTALLED;
+        Mockito.when(mPackageManager
+                .getApplicationEnabledSetting(Mockito.anyString()))
+                .thenReturn(PackageManager.COMPONENT_ENABLED_STATE_DEFAULT);
+        Mockito.when(mPackageManager.getApplicationInfo(CARRIER_APP,
+                PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS
+                        | PackageManager.MATCH_HIDDEN_UNTIL_INSTALLED_COMPONENTS
+                        | PackageManager.MATCH_SYSTEM_ONLY))
+                .thenReturn(appInfo);
+        ApplicationInfo associatedAppInfo = new ApplicationInfo();
+        associatedAppInfo.packageName = ASSOCIATED_APP;
+        associatedAppInfo.flags |= ApplicationInfo.FLAG_SYSTEM | ApplicationInfo.FLAG_INSTALLED;
+        associatedAppInfo.enabledSetting = PackageManager.COMPONENT_ENABLED_STATE_DEFAULT;
+        Mockito.when(mPackageManager.getApplicationInfo(ASSOCIATED_APP,
+                PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS
+                        | PackageManager.MATCH_HIDDEN_UNTIL_INSTALLED_COMPONENTS
+                        | PackageManager.MATCH_SYSTEM_ONLY))
+                .thenReturn(associatedAppInfo);
+        Mockito.when(mTelephonyManager.checkCarrierPrivilegesForPackageAnyPhone(CARRIER_APP))
+                .thenReturn(TelephonyManager.CARRIER_PRIVILEGE_STATUS_NO_ACCESS);
+        // Different associated app SDK than usual.
+        CarrierAppUtils.disableCarrierAppsUntilPrivileged(CALLING_PACKAGE, mTelephonyManager,
+                mContentResolver, USER_ID, CARRIER_APPS,
+                makeAssociatedApp(CARRIER_APP, ASSOCIATED_APP, Build.VERSION.SDK_INT - 1),
+                mContext);
+        Mockito.verify(mPackageManager).setSystemAppState(CARRIER_APP,
+                PackageManager.SYSTEM_APP_STATE_HIDDEN_UNTIL_INSTALLED_HIDDEN);
+        Mockito.verify(mPackageManager).setSystemAppState(ASSOCIATED_APP,
+                PackageManager.SYSTEM_APP_STATE_HIDDEN_UNTIL_INSTALLED_HIDDEN);
+        Mockito.verify(mPackageManager).setSystemAppState(CARRIER_APP,
+                PackageManager.SYSTEM_APP_STATE_UNINSTALLED);
+        Mockito.verify(mPackageManager, Mockito.never()).setSystemAppState(ASSOCIATED_APP,
+                PackageManager.SYSTEM_APP_STATE_INSTALLED);
+        Mockito.verify(mPackageManager).setSystemAppState(ASSOCIATED_APP,
+                PackageManager.SYSTEM_APP_STATE_UNINSTALLED);
+    }
+
+    /**
+     * Configured app has no privileges, and is in the default state along with associated app, and
+     * disabling has not yet occurred ever - should uninstall both regardless of associated app's
+     * SDK.
+     */
+    @Test @SmallTest
+    public void testDCAUP_NoPrivileges_Associated_Default_FirstRun_AssociatedSdkCurrent()
+            throws Exception {
+        ApplicationInfo appInfo = new ApplicationInfo();
+        appInfo.packageName = CARRIER_APP;
+        appInfo.flags |= ApplicationInfo.FLAG_SYSTEM | ApplicationInfo.FLAG_INSTALLED;
+        Mockito.when(mPackageManager
+                .getApplicationEnabledSetting(Mockito.anyString()))
+                .thenReturn(PackageManager.COMPONENT_ENABLED_STATE_DEFAULT);
+        Mockito.when(mPackageManager.getApplicationInfo(CARRIER_APP,
+                PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS
+                        | PackageManager.MATCH_HIDDEN_UNTIL_INSTALLED_COMPONENTS
+                        | PackageManager.MATCH_SYSTEM_ONLY))
+                .thenReturn(appInfo);
+        ApplicationInfo associatedAppInfo = new ApplicationInfo();
+        associatedAppInfo.packageName = ASSOCIATED_APP;
+        associatedAppInfo.flags |= ApplicationInfo.FLAG_SYSTEM | ApplicationInfo.FLAG_INSTALLED;
+        associatedAppInfo.enabledSetting = PackageManager.COMPONENT_ENABLED_STATE_DEFAULT;
+        Mockito.when(mPackageManager.getApplicationInfo(ASSOCIATED_APP,
+                PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS
+                        | PackageManager.MATCH_HIDDEN_UNTIL_INSTALLED_COMPONENTS
+                        | PackageManager.MATCH_SYSTEM_ONLY))
+                .thenReturn(associatedAppInfo);
+        Mockito.when(mTelephonyManager.checkCarrierPrivilegesForPackageAnyPhone(CARRIER_APP))
+                .thenReturn(TelephonyManager.CARRIER_PRIVILEGE_STATUS_NO_ACCESS);
+        // Different associated app SDK than usual.
+        CarrierAppUtils.disableCarrierAppsUntilPrivileged(CALLING_PACKAGE, mTelephonyManager,
+                mContentResolver, USER_ID, CARRIER_APPS,
+                makeAssociatedApp(CARRIER_APP, ASSOCIATED_APP, Build.VERSION.SDK_INT), mContext);
+        Mockito.verify(mPackageManager).setSystemAppState(CARRIER_APP,
+                PackageManager.SYSTEM_APP_STATE_HIDDEN_UNTIL_INSTALLED_HIDDEN);
+        Mockito.verify(mPackageManager).setSystemAppState(ASSOCIATED_APP,
+                PackageManager.SYSTEM_APP_STATE_HIDDEN_UNTIL_INSTALLED_HIDDEN);
+        Mockito.verify(mPackageManager).setSystemAppState(CARRIER_APP,
+                PackageManager.SYSTEM_APP_STATE_UNINSTALLED);
+        Mockito.verify(mPackageManager, Mockito.never()).setSystemAppState(ASSOCIATED_APP,
+                PackageManager.SYSTEM_APP_STATE_INSTALLED);
+        Mockito.verify(mPackageManager).setSystemAppState(ASSOCIATED_APP,
+                PackageManager.SYSTEM_APP_STATE_UNINSTALLED);
+    }
+
+    /**
+     * Configured app has no privileges, and is in the default state along with associated app, and
+     * disabling has not yet occurred ever - should uninstall both regardless of associated app's
+     * SDK.
+     */
+    @Test @SmallTest
+    public void testDCAUP_NoPrivileges_Associated_Default_FirstRun_AssociatedSdkUnspecified()
+            throws Exception {
+        ApplicationInfo appInfo = new ApplicationInfo();
+        appInfo.packageName = CARRIER_APP;
+        appInfo.flags |= ApplicationInfo.FLAG_SYSTEM | ApplicationInfo.FLAG_INSTALLED;
+        Mockito.when(mPackageManager
+                .getApplicationEnabledSetting(Mockito.anyString()))
+                .thenReturn(PackageManager.COMPONENT_ENABLED_STATE_DEFAULT);
+        Mockito.when(mPackageManager.getApplicationInfo(CARRIER_APP,
+                PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS
+                        | PackageManager.MATCH_HIDDEN_UNTIL_INSTALLED_COMPONENTS
+                        | PackageManager.MATCH_SYSTEM_ONLY))
+                .thenReturn(appInfo);
+        ApplicationInfo associatedAppInfo = new ApplicationInfo();
+        associatedAppInfo.packageName = ASSOCIATED_APP;
+        associatedAppInfo.flags |= ApplicationInfo.FLAG_SYSTEM | ApplicationInfo.FLAG_INSTALLED;
+        associatedAppInfo.enabledSetting = PackageManager.COMPONENT_ENABLED_STATE_DEFAULT;
+        Mockito.when(mPackageManager.getApplicationInfo(ASSOCIATED_APP,
+                PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS
+                        | PackageManager.MATCH_HIDDEN_UNTIL_INSTALLED_COMPONENTS
+                        | PackageManager.MATCH_SYSTEM_ONLY))
+                .thenReturn(associatedAppInfo);
+        Mockito.when(mTelephonyManager.checkCarrierPrivilegesForPackageAnyPhone(CARRIER_APP))
+                .thenReturn(TelephonyManager.CARRIER_PRIVILEGE_STATUS_NO_ACCESS);
+        // Using SDK_UNSPECIFIED for the associated app's addedInSdk.
+        CarrierAppUtils.disableCarrierAppsUntilPrivileged(CALLING_PACKAGE, mTelephonyManager,
+                mContentResolver, USER_ID, CARRIER_APPS, ASSOCIATED_APPS, mContext);
+        Mockito.verify(mPackageManager).setSystemAppState(CARRIER_APP,
+                PackageManager.SYSTEM_APP_STATE_HIDDEN_UNTIL_INSTALLED_HIDDEN);
+        Mockito.verify(mPackageManager).setSystemAppState(ASSOCIATED_APP,
+                PackageManager.SYSTEM_APP_STATE_HIDDEN_UNTIL_INSTALLED_HIDDEN);
+        Mockito.verify(mPackageManager).setSystemAppState(CARRIER_APP,
+                PackageManager.SYSTEM_APP_STATE_UNINSTALLED);
+        Mockito.verify(mPackageManager, Mockito.never()).setSystemAppState(ASSOCIATED_APP,
+                PackageManager.SYSTEM_APP_STATE_INSTALLED);
+        Mockito.verify(mPackageManager).setSystemAppState(ASSOCIATED_APP,
+                PackageManager.SYSTEM_APP_STATE_UNINSTALLED);
     }
 
     /** Telephony is not initialized, and app is in the default state - should uninstall it. */
@@ -693,21 +1101,21 @@
         ApplicationInfo appInfo = new ApplicationInfo();
         appInfo.packageName = CARRIER_APP;
         appInfo.flags |= ApplicationInfo.FLAG_SYSTEM | ApplicationInfo.FLAG_INSTALLED;
-        appInfo.enabledSetting = PackageManager.COMPONENT_ENABLED_STATE_DEFAULT;
+        Mockito.when(mPackageManager
+                .getApplicationEnabledSetting(Mockito.anyString()))
+                .thenReturn(PackageManager.COMPONENT_ENABLED_STATE_DEFAULT);
         Mockito.when(mPackageManager.getApplicationInfo(CARRIER_APP,
                 PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS
-                        | PackageManager.MATCH_HIDDEN_UNTIL_INSTALLED_COMPONENTS,
-                USER_ID)).thenReturn(appInfo);
-        CarrierAppUtils.disableCarrierAppsUntilPrivileged(CALLING_PACKAGE, mPackageManager,
+                        | PackageManager.MATCH_HIDDEN_UNTIL_INSTALLED_COMPONENTS
+                        | PackageManager.MATCH_SYSTEM_ONLY))
+                .thenReturn(appInfo);
+        CarrierAppUtils.disableCarrierAppsUntilPrivileged(CALLING_PACKAGE,
                 null /* telephonyManager */, mContentResolver, USER_ID, CARRIER_APPS,
-                ASSOCIATED_APPS);
-        Mockito.verify(mPackageManager).setSystemAppHiddenUntilInstalled(
-                CARRIER_APP, true);
-        Mockito.verify(mPackageManager).setSystemAppInstallState(
-                CARRIER_APP, false, USER_ID);
-        Mockito.verify(mPackageManager, Mockito.never())
-                .grantDefaultPermissionsToEnabledCarrierApps(
-                        Mockito.any(String[].class), Mockito.anyInt());
+                ASSOCIATED_APPS, mContext);
+        Mockito.verify(mPackageManager).setSystemAppState(CARRIER_APP,
+                PackageManager.SYSTEM_APP_STATE_HIDDEN_UNTIL_INSTALLED_HIDDEN);
+        Mockito.verify(mPackageManager).setSystemAppState(CARRIER_APP,
+                PackageManager.SYSTEM_APP_STATE_UNINSTALLED);
     }
 
     /** Configured app has no privileges, and is disabled until used or not installed - should do
@@ -719,22 +1127,24 @@
         ApplicationInfo appInfo = new ApplicationInfo();
         appInfo.packageName = CARRIER_APP;
         appInfo.flags |= ApplicationInfo.FLAG_SYSTEM | ApplicationInfo.FLAG_INSTALLED;
-        appInfo.enabledSetting = PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED;
+        Mockito.when(mPackageManager
+                .getApplicationEnabledSetting(Mockito.anyString()))
+                .thenReturn(PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED);
         Mockito.when(mPackageManager.getApplicationInfo(CARRIER_APP,
                 PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS
-                        | PackageManager.MATCH_HIDDEN_UNTIL_INSTALLED_COMPONENTS,
-                USER_ID)).thenReturn(appInfo);
+                        | PackageManager.MATCH_HIDDEN_UNTIL_INSTALLED_COMPONENTS
+                        | PackageManager.MATCH_SYSTEM_ONLY))
+                .thenReturn(appInfo);
         Mockito.when(mTelephonyManager.checkCarrierPrivilegesForPackageAnyPhone(CARRIER_APP))
                 .thenReturn(TelephonyManager.CARRIER_PRIVILEGE_STATUS_NO_ACCESS);
-        CarrierAppUtils.disableCarrierAppsUntilPrivileged(CALLING_PACKAGE, mPackageManager,
-                mTelephonyManager, mContentResolver, USER_ID, CARRIER_APPS, ASSOCIATED_APPS);
-        Mockito.verify(mPackageManager).setSystemAppHiddenUntilInstalled(
-                CARRIER_APP, true);
-        Mockito.verify(mPackageManager, Mockito.never()).setSystemAppInstallState(
-                Mockito.anyString(), Mockito.anyBoolean(), Mockito.anyInt());
-        Mockito.verify(mPackageManager, Mockito.never())
-                .grantDefaultPermissionsToEnabledCarrierApps(
-                        Mockito.any(String[].class), Mockito.anyInt());
+        CarrierAppUtils.disableCarrierAppsUntilPrivileged(CALLING_PACKAGE, mTelephonyManager,
+                mContentResolver, USER_ID, CARRIER_APPS, ASSOCIATED_APPS, mContext);
+        Mockito.verify(mPackageManager).setSystemAppState(CARRIER_APP,
+                PackageManager.SYSTEM_APP_STATE_HIDDEN_UNTIL_INSTALLED_HIDDEN);
+        Mockito.verify(mPackageManager, Mockito.never()).setSystemAppState(Mockito.anyString(),
+                Mockito.eq(PackageManager.SYSTEM_APP_STATE_INSTALLED));
+        Mockito.verify(mPackageManager, Mockito.never()).setSystemAppState(Mockito.anyString(),
+                Mockito.eq(PackageManager.SYSTEM_APP_STATE_UNINSTALLED));
     }
 
     /** Telephony is not initialized, and app is disabled until used or not installed - should do
@@ -746,21 +1156,23 @@
         ApplicationInfo appInfo = new ApplicationInfo();
         appInfo.packageName = CARRIER_APP;
         appInfo.flags |= ApplicationInfo.FLAG_SYSTEM | ApplicationInfo.FLAG_INSTALLED;
-        appInfo.enabledSetting = PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED;
+        Mockito.when(mPackageManager
+                .getApplicationEnabledSetting(Mockito.anyString()))
+                .thenReturn(PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED);
         Mockito.when(mPackageManager.getApplicationInfo(CARRIER_APP,
                 PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS
-                        | PackageManager.MATCH_HIDDEN_UNTIL_INSTALLED_COMPONENTS,
-                USER_ID)).thenReturn(appInfo);
-        CarrierAppUtils.disableCarrierAppsUntilPrivileged(CALLING_PACKAGE, mPackageManager,
+                        | PackageManager.MATCH_HIDDEN_UNTIL_INSTALLED_COMPONENTS
+                        | PackageManager.MATCH_SYSTEM_ONLY))
+                .thenReturn(appInfo);
+        CarrierAppUtils.disableCarrierAppsUntilPrivileged(CALLING_PACKAGE,
                 null /* telephonyManager */, mContentResolver, USER_ID, CARRIER_APPS,
-                ASSOCIATED_APPS);
-        Mockito.verify(mPackageManager).setSystemAppHiddenUntilInstalled(
-                CARRIER_APP, true);
-        Mockito.verify(mPackageManager, Mockito.never()).setSystemAppInstallState(
-                Mockito.anyString(), Mockito.anyBoolean(), Mockito.anyInt());
-        Mockito.verify(mPackageManager, Mockito.never())
-                .grantDefaultPermissionsToEnabledCarrierApps(
-                        Mockito.any(String[].class), Mockito.anyInt());
+                ASSOCIATED_APPS, mContext);
+        Mockito.verify(mPackageManager).setSystemAppState(CARRIER_APP,
+                PackageManager.SYSTEM_APP_STATE_HIDDEN_UNTIL_INSTALLED_HIDDEN);
+        Mockito.verify(mPackageManager, Mockito.never()).setSystemAppState(Mockito.anyString(),
+                Mockito.eq(PackageManager.SYSTEM_APP_STATE_INSTALLED));
+        Mockito.verify(mPackageManager, Mockito.never()).setSystemAppState(Mockito.anyString(),
+                Mockito.eq(PackageManager.SYSTEM_APP_STATE_UNINSTALLED));
     }
 
     class SettingsMockContentProvider extends MockContentProvider {
diff --git a/tests/telephonytests/src/com/android/internal/telephony/CarrierKeyDownloadMgrTest.java b/tests/telephonytests/src/com/android/internal/telephony/CarrierKeyDownloadMgrTest.java
index d9acde5..5a7f688 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/CarrierKeyDownloadMgrTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/CarrierKeyDownloadMgrTest.java
@@ -17,8 +17,7 @@
 
 import static android.preference.PreferenceManager.getDefaultSharedPreferences;
 
-import static com.android.internal.telephony.TelephonyTestUtils.waitForMs;
-
+import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
@@ -32,38 +31,35 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.SharedPreferences;
-import android.os.HandlerThread;
 import android.os.PersistableBundle;
 import android.telephony.CarrierConfigManager;
 import android.telephony.ImsiEncryptionInfo;
 import android.telephony.TelephonyManager;
 import android.test.suitebuilder.annotation.SmallTest;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
 import android.util.Pair;
 
-import com.android.org.bouncycastle.util.io.pem.PemReader;
-
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
+import org.junit.runner.RunWith;
 import org.mockito.Matchers;
 import org.mockito.MockitoAnnotations;
 
-import java.io.BufferedReader;
-import java.io.ByteArrayInputStream;
-import java.io.InputStreamReader;
-import java.io.Reader;
 import java.security.PublicKey;
 import java.text.SimpleDateFormat;
 import java.util.Calendar;
 import java.util.Date;
 import java.util.GregorianCalendar;
 
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper
 public class CarrierKeyDownloadMgrTest extends TelephonyTest {
 
     private static final String LOG_TAG = "CarrierKeyDownloadManager";
 
     private CarrierKeyDownloadManager mCarrierKeyDM;
-    private CarrierActionAgentHandler mCarrierActionAgentHandler;
 
     private String mURL = "http://www.google.com";
 
@@ -76,33 +72,18 @@
     private String mJsonStr3GppSpec = "{ \"carrier-keys\": [ { \"key-identifier\": \"key1=value\", "
             + "\"public-key\": \"" + CERT + "\"}]}";
 
-    private class CarrierActionAgentHandler extends HandlerThread {
-
-        private CarrierActionAgentHandler(String name) {
-            super(name);
-        }
-
-        @Override
-        public void onLooperPrepared() {
-            mCarrierKeyDM = new CarrierKeyDownloadManager(mPhone);
-            setReady(true);
-        }
-    }
-
     @Before
     public void setUp() throws Exception {
         logd("CarrierActionAgentTest +Setup!");
         MockitoAnnotations.initMocks(this);
         super.setUp(getClass().getSimpleName());
-        mCarrierActionAgentHandler = new CarrierActionAgentHandler(getClass().getSimpleName());
-        mCarrierActionAgentHandler.start();
-        waitUntilReady();
+        mCarrierKeyDM = new CarrierKeyDownloadManager(mPhone);
+        processAllMessages();
         logd("CarrierActionAgentTest -Setup!");
     }
 
     @After
     public void tearDown() throws Exception {
-        mCarrierActionAgentHandler.quit();
         super.tearDown();
     }
 
@@ -162,12 +143,9 @@
     @Test
     @SmallTest
     public void testParseJson() {
-        ByteArrayInputStream certBytes = new ByteArrayInputStream(CERT.getBytes());
-        Reader fRd = new BufferedReader(new InputStreamReader(certBytes));
-        PemReader reader = new PemReader(fRd);
         Pair<PublicKey, Long> keyInfo = null;
         try {
-            keyInfo = mCarrierKeyDM.getKeyInformation(reader.readPemObject().getContent());
+            keyInfo = mCarrierKeyDM.getKeyInformation(CERT.getBytes());
         } catch (Exception e) {
             fail(LOG_TAG + "exception creating public key");
         }
@@ -186,12 +164,9 @@
     @Test
     @SmallTest
     public void testParseJsonPublicKey() {
-        ByteArrayInputStream certBytes = new ByteArrayInputStream(CERT.getBytes());
-        Reader fRd = new BufferedReader(new InputStreamReader(certBytes));
-        PemReader reader = new PemReader(fRd);
         Pair<PublicKey, Long> keyInfo = null;
         try {
-            keyInfo = mCarrierKeyDM.getKeyInformation(reader.readPemObject().getContent());
+            keyInfo = mCarrierKeyDM.getKeyInformation(CERT.getBytes());
         } catch (Exception e) {
             fail(LOG_TAG + "exception creating public key");
         }
@@ -279,7 +254,7 @@
         when(mTelephonyManager.getSimOperator(anyInt())).thenReturn("310260");
         Intent mIntent = new Intent(DownloadManager.ACTION_DOWNLOAD_COMPLETE);
         mContext.sendBroadcast(mIntent);
-        waitForMs(200);
+        processAllMessages();
         Date expirationDate = new Date(mCarrierKeyDM.getExpirationDate());
         assertTrue(dt.format(expirationDate).equals(dateExpected));
     }
@@ -302,7 +277,7 @@
         Intent mIntent = new Intent(CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED);
         mIntent.putExtra(PhoneConstants.PHONE_KEY, 0);
         mContext.sendBroadcast(mIntent);
-        waitForMs(200);
+        processAllMessages();
         SharedPreferences preferences = getDefaultSharedPreferences(mContext);
         String mccMnc = preferences.getString("CARRIER_KEY_DM_MCC_MNC" + slotId, null);
         assertTrue(mccMnc.equals("310:260"));
@@ -326,7 +301,7 @@
         Intent mIntent = new Intent("com.android.internal.telephony.carrier_key_download_alarm"
                 + slotId);
         mContext.sendBroadcast(mIntent);
-        waitForMs(200);
+        processAllMessages();
         SharedPreferences preferences = getDefaultSharedPreferences(mContext);
         String mccMnc = preferences.getString("CARRIER_KEY_DM_MCC_MNC" + slotId, null);
         assertTrue(mccMnc.equals("310:260"));
@@ -338,12 +313,9 @@
     @Test
     @SmallTest
     public void testParseJson3GppFormat() {
-        ByteArrayInputStream certBytes = new ByteArrayInputStream(CERT.getBytes());
-        Reader fRd = new BufferedReader(new InputStreamReader(certBytes));
-        PemReader reader = new PemReader(fRd);
         Pair<PublicKey, Long> keyInfo = null;
         try {
-            keyInfo = mCarrierKeyDM.getKeyInformation(reader.readPemObject().getContent());
+            keyInfo = mCarrierKeyDM.getKeyInformation(CERT.getBytes());
         } catch (Exception e) {
             fail(LOG_TAG + "exception creating public key");
         }
@@ -356,4 +328,13 @@
                 (Matchers.refEq(imsiEncryptionInfo)));
     }
 
+    /**
+     * Checks if certificate string cleaning is working correctly
+     */
+    @Test
+    @SmallTest
+    public void testCleanCertString() {
+        assertEquals(CarrierKeyDownloadManager
+                .cleanCertString("Comments before" + CERT + "Comments after"), CERT);
+    }
 }
diff --git a/tests/telephonytests/src/com/android/internal/telephony/CarrierPrivilegesTrackerTest.java b/tests/telephonytests/src/com/android/internal/telephony/CarrierPrivilegesTrackerTest.java
new file mode 100644
index 0000000..79c8042
--- /dev/null
+++ b/tests/telephonytests/src/com/android/internal/telephony/CarrierPrivilegesTrackerTest.java
@@ -0,0 +1,556 @@
+/*
+ * Copyright (C) 2020 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;
+
+import static android.content.pm.PackageManager.GET_SIGNING_CERTIFICATES;
+import static android.os.UserHandle.SYSTEM;
+import static android.telephony.CarrierConfigManager.EXTRA_SLOT_INDEX;
+import static android.telephony.CarrierConfigManager.EXTRA_SUBSCRIPTION_INDEX;
+import static android.telephony.CarrierConfigManager.KEY_CARRIER_CERTIFICATE_STRING_ARRAY;
+import static android.telephony.CarrierConfigManager.KEY_CARRIER_CONFIG_APPLIED_BOOL;
+import static android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID;
+import static android.telephony.TelephonyManager.EXTRA_SIM_STATE;
+import static android.telephony.TelephonyManager.SIM_STATE_LOADED;
+import static android.telephony.TelephonyManager.SIM_STATE_NOT_READY;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.when;
+
+import android.annotation.Nullable;
+import android.content.Intent;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.Signature;
+import android.content.pm.UserInfo;
+import android.net.Uri;
+import android.os.AsyncResult;
+import android.os.Handler;
+import android.os.Message;
+import android.os.PersistableBundle;
+import android.telephony.CarrierConfigManager;
+import android.telephony.TelephonyManager;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+import android.util.ArraySet;
+
+import com.android.internal.telephony.uicc.IccUtils;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+
+import java.security.MessageDigest;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Set;
+
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper
+public class CarrierPrivilegesTrackerTest extends TelephonyTest {
+    private static final int REGISTRANT_WHAT = 1;
+    private static final int PHONE_ID = 2;
+    private static final int PHONE_ID_INCORRECT = 3;
+    private static final int SUB_ID = 4;
+
+    private static final int USER_ID_1 = 5;
+    private static final int USER_ID_2 = 6;
+
+    private static final UserInfo USER_1 = new UserInfo(USER_ID_1, "" /* name */, 0 /* flags */);
+    private static final UserInfo USER_2 = new UserInfo(USER_ID_2, "" /* name */, 0 /* flags */);
+
+    private static final String PACKAGE_1 = "android.test.package1";
+    private static final String PACKAGE_2 = "android.test.package2";
+
+    private static final String CERT_1 = "11223344";
+    private static final String CERT_2 = "AABBCCDD";
+    private static final String CERT_3 = "FFFFFFFF";
+
+    private static final String SHA_1 = "SHA-1";
+
+    private static final int UID_1 = 10000001;
+    private static final int UID_2 = 10000002;
+    private static final int[] PRIVILEGED_UIDS = {UID_1, UID_2};
+
+    private static final int PM_FLAGS =
+            PackageManager.MATCH_DISABLED_COMPONENTS
+                    | PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS
+                    | PackageManager.GET_SIGNING_CERTIFICATES;
+
+    @Mock private Signature mSignature;
+
+    private PersistableBundle mCarrierConfigs;
+    private CarrierPrivilegesTrackerTestHandler mHandler;
+    private CarrierPrivilegesTracker mCarrierPrivilegesTracker;
+
+    @Before
+    public void setUp() throws Exception {
+        logd("CarrierPrivilegesTrackerTest +Setup!");
+        super.setUp(getClass().getSimpleName());
+
+        when(mPhone.getPhoneId()).thenReturn(PHONE_ID);
+        when(mPhone.getSubId()).thenReturn(SUB_ID);
+
+        mCarrierConfigs = new PersistableBundle();
+        mHandler = new CarrierPrivilegesTrackerTestHandler();
+
+        // set mock behavior so CarrierPrivilegeTracker initializes with no privileged UIDs
+        setupCarrierConfigCerts();
+        setupSimLoadedCerts();
+        setupInstalledPackages();
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        super.tearDown();
+    }
+
+    private void setupCarrierConfigCerts(String... certHashes) {
+        mCarrierConfigs.putStringArray(KEY_CARRIER_CERTIFICATE_STRING_ARRAY, certHashes);
+        mCarrierConfigs.putBoolean(KEY_CARRIER_CONFIG_APPLIED_BOOL, true);
+        when(mCarrierConfigManager.getConfigForSubId(SUB_ID)).thenReturn(mCarrierConfigs);
+    }
+
+    private void setupSimLoadedCerts(String... certHashes) {
+        when(mTelephonyManager.hasIccCard(PHONE_ID)).thenReturn(true);
+        when(mTelephonyManager.getCertsFromCarrierPrivilegeAccessRules())
+                .thenReturn(Arrays.asList(certHashes));
+    }
+
+    private void setupInstalledPackages(PackageCertInfo... pkgCertInfos) throws Exception {
+        Set<UserInfo> users = new ArraySet<>();
+        List<PackageInfo> installedPackages = new ArrayList<>();
+        for (PackageCertInfo pkgCertInfo : pkgCertInfos) {
+            users.add(pkgCertInfo.userInfo);
+
+            PackageInfo pkg = new PackageInfo();
+            pkg.packageName = pkgCertInfo.pkgName;
+            pkg.signatures = new Signature[] {new Signature(pkgCertInfo.cert)};
+
+            when(mPackageManager.getPackageInfo(
+                            eq(pkgCertInfo.pkgName), eq(GET_SIGNING_CERTIFICATES)))
+                    .thenReturn(pkg);
+            when(mPackageManager.getPackageUidAsUser(
+                            eq(pkgCertInfo.pkgName), eq(pkgCertInfo.userInfo.id)))
+                    .thenReturn(pkgCertInfo.uid);
+            installedPackages.add(pkg);
+        }
+        when(mUserManager.getUsers()).thenReturn(new ArrayList<>(users));
+        when(mPackageManager.getInstalledPackagesAsUser(eq(PM_FLAGS), eq(SYSTEM.getIdentifier())))
+                .thenReturn(installedPackages);
+    }
+
+    /**
+     * Creates and returns a CarrierPrivilegesTracker instance.
+     *
+     * <p>The initial configuration of the CarrierPrivilegesTracker will be based on the current
+     * state of certificate hashes and installed packages.
+     *
+     * See #setupCarrierConfigCerts, #setupSimLoadedCerts, #setupInstalledPackages.
+     */
+    private CarrierPrivilegesTracker createCarrierPrivilegesTracker() throws Exception {
+        CarrierPrivilegesTracker cpt =
+                new CarrierPrivilegesTracker(mTestableLooper.getLooper(), mPhone, mContext);
+        mTestableLooper.processAllMessages();
+
+        cpt.registerCarrierPrivilegesListener(mHandler, REGISTRANT_WHAT, null);
+        mTestableLooper.processAllMessages();
+        mHandler.reset();
+
+        return cpt;
+    }
+
+    private void setupCarrierPrivilegesTrackerWithCarrierConfigUids() throws Exception {
+        setupCarrierConfigCerts(getHash(CERT_1), getHash(CERT_2));
+        setupInstalledPackages(
+                new PackageCertInfo(PACKAGE_1, CERT_1, USER_1, UID_1),
+                new PackageCertInfo(PACKAGE_2, CERT_2, USER_1, UID_2));
+        mCarrierPrivilegesTracker = createCarrierPrivilegesTracker();
+    }
+
+    private void setupCarrierPrivilegesTrackerWithSimLoadedUids() throws Exception {
+        setupSimLoadedCerts(getHash(CERT_1), getHash(CERT_2));
+        setupInstalledPackages(
+                new PackageCertInfo(PACKAGE_1, CERT_1, USER_1, UID_1),
+                new PackageCertInfo(PACKAGE_2, CERT_2, USER_1, UID_2));
+        mCarrierPrivilegesTracker = createCarrierPrivilegesTracker();
+    }
+
+    private class CarrierPrivilegesTrackerTestHandler extends Handler {
+        public int[] privilegedUids;
+        public int numUidUpdates;
+
+        @Override
+        public void handleMessage(Message msg) {
+            switch (msg.what) {
+                case REGISTRANT_WHAT: {
+                    AsyncResult asyncResult = (AsyncResult) msg.obj;
+                    privilegedUids = (int[]) asyncResult.result;
+                    numUidUpdates++;
+                    break;
+                }
+                default: {
+                    fail("Unexpected msg received. what=" + msg.what);
+                }
+            }
+        }
+
+        void reset() {
+            privilegedUids = null;
+            numUidUpdates = 0;
+        }
+    }
+
+    private void verifyPrivilegedUids(@Nullable int[] expectedUids, int expectedUidUpdates) {
+        assertArrayEquals(expectedUids, mHandler.privilegedUids);
+        assertEquals(expectedUidUpdates, mHandler.numUidUpdates);
+    }
+
+    private void verifyRegisterListener(int[] expectedUids, int expectedUidUpdates)
+            throws Exception {
+        // mHandler registered in createCarrierPrivilegesTracker(), so reset it
+        mHandler = new CarrierPrivilegesTrackerTestHandler();
+
+        mCarrierPrivilegesTracker.registerCarrierPrivilegesListener(
+                mHandler, REGISTRANT_WHAT, null);
+        mTestableLooper.processAllMessages();
+
+        verifyPrivilegedUids(expectedUids, expectedUidUpdates);
+    }
+
+    @Test
+    public void testRegisterListener() throws Exception {
+        mCarrierPrivilegesTracker = createCarrierPrivilegesTracker();
+
+        verifyRegisterListener(new int[0] /* expectedUids */, 1 /* expectedUidUpdates */);
+    }
+
+    @Test
+    public void testUnregisterListener() throws Exception {
+        // Start with privileges. Verify no updates received after clearing UIDs.
+        setupCarrierPrivilegesTrackerWithCarrierConfigUids();
+
+        verifyRegisterListener(PRIVILEGED_UIDS, 1 /* expectedUidUpdates */);
+        mHandler.reset();
+
+        mCarrierPrivilegesTracker.unregisterCarrierPrivilegesListener(mHandler);
+        mTestableLooper.processAllMessages();
+
+        verifyPrivilegedUids(null /* expectedUids */, 0 /* expectedUidUpdates */);
+
+        // Clear UIDs
+        sendCarrierConfigChangedIntent(INVALID_SUBSCRIPTION_ID, PHONE_ID);
+        mTestableLooper.processAllMessages();
+
+        verifyPrivilegedUids(null /* expectedUids */, 0 /* expectedUidUpdates */);
+    }
+
+    @Test
+    public void testCarrierConfigUpdated() throws Exception {
+        // Start with packages installed and no certs
+        setupInstalledPackages(
+                new PackageCertInfo(PACKAGE_1, CERT_1, USER_1, UID_1),
+                new PackageCertInfo(PACKAGE_2, CERT_2, USER_1, UID_2));
+        mCarrierPrivilegesTracker = createCarrierPrivilegesTracker();
+        mCarrierConfigs.putStringArray(
+                KEY_CARRIER_CERTIFICATE_STRING_ARRAY,
+                new String[] {getHash(CERT_1), getHash(CERT_2)});
+        when(mCarrierConfigManager.getConfigForSubId(SUB_ID)).thenReturn(mCarrierConfigs);
+
+        sendCarrierConfigChangedIntent(SUB_ID, PHONE_ID);
+        mTestableLooper.processAllMessages();
+
+        verifyPrivilegedUids(PRIVILEGED_UIDS, 1 /* expectedUidUpdates */);
+    }
+
+    @Test
+    public void testCarrierConfigUpdatedMismatchedSlotIndex() throws Exception {
+        // Start with privileges. Incorrect phoneId shouldn't affect certs
+        setupCarrierPrivilegesTrackerWithCarrierConfigUids();
+
+        sendCarrierConfigChangedIntent(SUB_ID, PHONE_ID_INCORRECT);
+        mTestableLooper.processAllMessages();
+
+        verifyPrivilegedUids(null /* expectedUids */, 0 /* expectedUidUpdates */);
+    }
+
+    @Test
+    public void testCarrierConfigUpdatedInvalidSubId() throws Exception {
+        // Start with privileges, verify clearing certs clears UIDs
+        setupCarrierPrivilegesTrackerWithCarrierConfigUids();
+
+        sendCarrierConfigChangedIntent(INVALID_SUBSCRIPTION_ID, PHONE_ID);
+        mTestableLooper.processAllMessages();
+
+        verifyPrivilegedUids(new int[0] /* expectedUids */, 1 /* expectedUidUpdates */);
+    }
+
+    @Test
+    public void testCarrierConfigUpdatedNotIdentifiedCarrier() throws Exception {
+        // Start with privileges, verify clearing certs clears UIDs
+        setupCarrierPrivilegesTrackerWithCarrierConfigUids();
+
+        mCarrierConfigs.putBoolean(KEY_CARRIER_CONFIG_APPLIED_BOOL, false);
+        when(mCarrierConfigManager.getConfigForSubId(SUB_ID)).thenReturn(mCarrierConfigs);
+
+        sendCarrierConfigChangedIntent(SUB_ID, PHONE_ID);
+        mTestableLooper.processAllMessages();
+
+        verifyPrivilegedUids(new int[0] /* expectedUids */, 1 /* expectedUidUpdates */);
+    }
+
+    @Test
+    public void testSimCardStateChanged() throws Exception {
+        // Start with packages installed and no certs
+        setupInstalledPackages(
+                new PackageCertInfo(PACKAGE_1, CERT_1, USER_1, UID_1),
+                new PackageCertInfo(PACKAGE_2, CERT_2, USER_1, UID_2));
+        mCarrierPrivilegesTracker = createCarrierPrivilegesTracker();
+
+        when(mTelephonyManager.getCertsFromCarrierPrivilegeAccessRules())
+                .thenReturn(Arrays.asList(getHash(CERT_1), getHash(CERT_2)));
+        when(mTelephonyManager.hasIccCard(PHONE_ID)).thenReturn(true);
+
+        sendSimCardStateChangedIntent(PHONE_ID, SIM_STATE_LOADED);
+        mTestableLooper.processAllMessages();
+
+        verifyPrivilegedUids(PRIVILEGED_UIDS, 1 /* expectedUidUpdates */);
+    }
+
+    @Test
+    public void testSimApplicationStateChanged() throws Exception {
+        // Start with packages installed and no certs
+        setupInstalledPackages(
+                new PackageCertInfo(PACKAGE_1, CERT_1, USER_1, UID_1),
+                new PackageCertInfo(PACKAGE_2, CERT_2, USER_1, UID_2));
+        mCarrierPrivilegesTracker = createCarrierPrivilegesTracker();
+
+        when(mTelephonyManager.getCertsFromCarrierPrivilegeAccessRules())
+                .thenReturn(Arrays.asList(getHash(CERT_1), getHash(CERT_2)));
+        when(mTelephonyManager.hasIccCard(PHONE_ID)).thenReturn(true);
+
+        sendSimApplicationStateChangedIntent(PHONE_ID, SIM_STATE_LOADED);
+        mTestableLooper.processAllMessages();
+
+        verifyPrivilegedUids(PRIVILEGED_UIDS, 1 /* expectedUidUpdates */);
+    }
+
+    @Test
+    public void testSimStateChangedWithoutIccCard() throws Exception {
+        // Start with privileges, verify no Icc Card clears UIDs
+        setupCarrierPrivilegesTrackerWithSimLoadedUids();
+
+        when(mTelephonyManager.hasIccCard(PHONE_ID)).thenReturn(false);
+
+        sendSimCardStateChangedIntent(PHONE_ID, SIM_STATE_LOADED);
+        mTestableLooper.processAllMessages();
+
+        verifyPrivilegedUids(new int[0] /* expectedUids */, 1 /* expectedUidUpdates */);
+    }
+
+    @Test
+    public void testSimStateChangedMismatchedSlotIndex() throws Exception {
+        // Start with privileges. Incorrect phoneId shouldn't affect certs
+        setupCarrierPrivilegesTrackerWithSimLoadedUids();
+
+        when(mTelephonyManager.hasIccCard(PHONE_ID)).thenReturn(false);
+
+        sendSimCardStateChangedIntent(PHONE_ID_INCORRECT, SIM_STATE_LOADED);
+        mTestableLooper.processAllMessages();
+
+        verifyPrivilegedUids(null /* expectedUids */, 0 /* expectedUidUpdates */);
+    }
+
+    @Test
+    public void testSimStateChangedSimStateNotReady() throws Exception {
+        // Start with privileges, verify clearing certs clears UIDs
+        setupCarrierPrivilegesTrackerWithSimLoadedUids();
+
+        sendSimCardStateChangedIntent(PHONE_ID, SIM_STATE_NOT_READY);
+        mTestableLooper.processAllMessages();
+
+        verifyPrivilegedUids(new int[0] /* expectedUids */, 1 /* expectedUidUpdates */);
+    }
+
+    @Test
+    public void testPackageAdded() throws Exception {
+        // Start with certs and no packages installed
+        setupCarrierConfigCerts(getHash(CERT_1));
+        mCarrierPrivilegesTracker = createCarrierPrivilegesTracker();
+
+        setupInstalledPackages(new PackageCertInfo(PACKAGE_1, CERT_1, USER_1, UID_1));
+
+        sendPackageChangedIntent(Intent.ACTION_PACKAGE_ADDED, PACKAGE_1);
+        mTestableLooper.processAllMessages();
+
+        verifyPrivilegedUids(new int[] {UID_1}, 1 /* expectedUidUpdates */);
+    }
+
+    @Test
+    public void testPackageAddedMultipleUsers() throws Exception {
+        // Start with certs and no packages installed
+        setupCarrierConfigCerts(getHash(CERT_1), getHash(CERT_2));
+        mCarrierPrivilegesTracker = createCarrierPrivilegesTracker();
+
+        setupInstalledPackages(
+                new PackageCertInfo(PACKAGE_1, CERT_1, USER_1, UID_1),
+                new PackageCertInfo(PACKAGE_1, CERT_2, USER_2, UID_2));
+
+        sendPackageChangedIntent(Intent.ACTION_PACKAGE_ADDED, PACKAGE_1);
+        mTestableLooper.processAllMessages();
+
+        verifyPrivilegedUids(PRIVILEGED_UIDS, 1 /* expectedUidUpdates */);
+    }
+
+    @Test
+    public void testPackageReplaced() throws Exception {
+        // Start with certs and an unmatched package
+        setupCarrierConfigCerts(getHash(CERT_1), getHash(CERT_2));
+        setupInstalledPackages(new PackageCertInfo(PACKAGE_1, CERT_3, USER_1, UID_1));
+        mCarrierPrivilegesTracker = createCarrierPrivilegesTracker();
+
+        setupInstalledPackages(
+                new PackageCertInfo(PACKAGE_1, CERT_1, USER_1, UID_1),
+                new PackageCertInfo(PACKAGE_1, CERT_2, USER_2, UID_2));
+
+        sendPackageChangedIntent(Intent.ACTION_PACKAGE_REPLACED, PACKAGE_1);
+        mTestableLooper.processAllMessages();
+
+        verifyPrivilegedUids(PRIVILEGED_UIDS, 1 /* expectedUidUpdates */);
+    }
+
+    @Test
+    public void testPackageAddedOrReplacedNoSignatures() throws Exception {
+        // Start with certs and packages installed
+        setupCarrierConfigCerts(getHash(CERT_1), getHash(CERT_2));
+        setupInstalledPackages(
+                new PackageCertInfo(PACKAGE_1, CERT_1, USER_1, UID_1),
+                new PackageCertInfo(PACKAGE_2, CERT_2, USER_1, UID_2));
+        mCarrierPrivilegesTracker = createCarrierPrivilegesTracker();
+
+        // Update PACKAGE_1 to have no signatures
+        PackageInfo pkg = new PackageInfo();
+        pkg.packageName = PACKAGE_1;
+        when(mPackageManager.getPackageInfo(eq(PACKAGE_1), eq(GET_SIGNING_CERTIFICATES)))
+                .thenReturn(pkg);
+
+        sendPackageChangedIntent(Intent.ACTION_PACKAGE_ADDED, PACKAGE_1);
+        mTestableLooper.processAllMessages();
+
+        verifyPrivilegedUids(new int[] {UID_2} /* expectedUids */, 1 /* expectedUidUpdates */);
+    }
+
+    @Test
+    public void testPackageAddedOrReplacedSignatureChanged() throws Exception {
+        // Start with certs and packages installed
+        setupCarrierConfigCerts(getHash(CERT_1), getHash(CERT_2));
+        setupInstalledPackages(
+                new PackageCertInfo(PACKAGE_1, CERT_1, USER_1, UID_1),
+                new PackageCertInfo(PACKAGE_2, CERT_2, USER_1, UID_2));
+        mCarrierPrivilegesTracker = createCarrierPrivilegesTracker();
+
+        // Update PACKAGE_1 to have a different signature
+        setupInstalledPackages(
+                new PackageCertInfo(PACKAGE_1, CERT_3, USER_1, UID_1),
+                new PackageCertInfo(PACKAGE_2, CERT_2, USER_1, UID_2));
+
+        sendPackageChangedIntent(Intent.ACTION_PACKAGE_ADDED, PACKAGE_1);
+        mTestableLooper.processAllMessages();
+
+        verifyPrivilegedUids(new int[] {UID_2} /* expectedUids */, 1 /* expectedUidUpdates */);
+    }
+
+    @Test
+    public void testPackageRemoved() throws Exception {
+        // Start with certs and packages installed
+        setupCarrierConfigCerts(getHash(CERT_1), getHash(CERT_2));
+        setupInstalledPackages(
+                new PackageCertInfo(PACKAGE_1, CERT_1, USER_1, UID_1),
+                new PackageCertInfo(PACKAGE_2, CERT_2, USER_1, UID_2));
+        mCarrierPrivilegesTracker = createCarrierPrivilegesTracker();
+
+        sendPackageChangedIntent(Intent.ACTION_PACKAGE_REMOVED, PACKAGE_1);
+        mTestableLooper.processAllMessages();
+
+        verifyPrivilegedUids(new int[] {UID_2} /* expectedUids */, 1 /* expectedUidUpdates */);
+    }
+
+    @Test
+    public void testPackageRemovedNoChanges() throws Exception {
+        // Start with packages installed and no certs
+        setupInstalledPackages(new PackageCertInfo(PACKAGE_1, CERT_1, USER_1, UID_1));
+        mCarrierPrivilegesTracker = createCarrierPrivilegesTracker();
+
+        sendPackageChangedIntent(Intent.ACTION_PACKAGE_REMOVED, PACKAGE_1);
+        mTestableLooper.processAllMessages();
+
+        verifyPrivilegedUids(null /* expectedUids */, 0 /* expectedUidUpdates */);
+    }
+
+    private void sendCarrierConfigChangedIntent(int subId, int phoneId) {
+        mContext.sendBroadcast(
+                new Intent(CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED)
+                        .putExtra(EXTRA_SUBSCRIPTION_INDEX, subId)
+                        .putExtra(EXTRA_SLOT_INDEX, phoneId));
+    }
+
+    private void sendSimCardStateChangedIntent(int phoneId, int simState) {
+        mContext.sendBroadcast(
+                new Intent(TelephonyManager.ACTION_SIM_CARD_STATE_CHANGED)
+                        .putExtra(EXTRA_SIM_STATE, simState)
+                        .putExtra(PhoneConstants.PHONE_KEY, phoneId));
+    }
+
+    private void sendSimApplicationStateChangedIntent(int phoneId, int simState) {
+        mContext.sendBroadcast(
+                new Intent(TelephonyManager.ACTION_SIM_APPLICATION_STATE_CHANGED)
+                        .putExtra(EXTRA_SIM_STATE, simState)
+                        .putExtra(PhoneConstants.PHONE_KEY, phoneId));
+    }
+
+    private void sendPackageChangedIntent(String action, String pkgName) {
+        mContext.sendBroadcast(new Intent(action, new Uri.Builder().path(pkgName).build()));
+    }
+
+    /**
+     * Returns the SHA-1 hash (as a hex String) for the given hex String.
+     */
+    private static String getHash(String hexString) throws Exception {
+        MessageDigest sha1 = MessageDigest.getInstance(SHA_1);
+        byte[] result = sha1.digest(IccUtils.hexStringToBytes(hexString));
+        return IccUtils.bytesToHexString(result);
+    }
+
+    private class PackageCertInfo {
+        public final String pkgName;
+        public final String cert;
+        public final UserInfo userInfo;
+        public final int uid;
+
+        PackageCertInfo(String pkgName, String cert, UserInfo userInfo, int uid) {
+            this.pkgName = pkgName;
+            this.cert = cert;
+            this.userInfo = userInfo;
+            this.uid = uid;
+        }
+    }
+}
diff --git a/tests/telephonytests/src/com/android/internal/telephony/CarrierResolverTest.java b/tests/telephonytests/src/com/android/internal/telephony/CarrierResolverTest.java
index 2604959..f72ee2f 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/CarrierResolverTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/CarrierResolverTest.java
@@ -16,8 +16,6 @@
 
 package com.android.internal.telephony;
 
-import static com.android.internal.telephony.TelephonyTestUtils.waitForMs;
-
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNull;
 import static org.mockito.Matchers.eq;
@@ -26,7 +24,6 @@
 import android.database.Cursor;
 import android.database.MatrixCursor;
 import android.net.Uri;
-import android.os.HandlerThread;
 import android.provider.Telephony.CarrierId;
 import android.provider.Telephony.Carriers;
 import android.service.carrier.CarrierIdentifier;
@@ -34,13 +31,18 @@
 import android.test.mock.MockContentProvider;
 import android.test.mock.MockContentResolver;
 import android.test.suitebuilder.annotation.SmallTest;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
 
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
+import org.junit.runner.RunWith;
 
 import java.util.Arrays;
 
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper
 public class CarrierResolverTest extends TelephonyTest {
     private static final String MCCMNC = "311480";
     private static final String NAME = "VZW";
@@ -87,19 +89,6 @@
     private static final int PREFER_APN_SET_EVENT = 3;
 
     private CarrierResolver mCarrierResolver;
-    private CarrierResolverHandler mCarrierCarrierResolverHandler;
-
-    private class CarrierResolverHandler extends HandlerThread {
-        private CarrierResolverHandler(String name) {
-            super(name);
-        }
-
-        @Override
-        public void onLooperPrepared() {
-            mCarrierResolver = new CarrierResolver(mPhone);
-            setReady(true);
-        }
-    }
 
     @Before
     public void setUp() throws Exception {
@@ -107,11 +96,9 @@
         super.setUp(getClass().getSimpleName());
         ((MockContentResolver) mContext.getContentResolver()).addProvider(
                 CarrierId.AUTHORITY, new CarrierIdContentProvider());
-        // start handler thread
-        mCarrierCarrierResolverHandler = new CarrierResolverHandler(getClass().getSimpleName());
-        mCarrierCarrierResolverHandler.start();
-        waitUntilReady();
+        mCarrierResolver = new CarrierResolver(mPhone);
         mCarrierResolver.sendEmptyMessage(ICC_CHANGED_EVENT);
+        processAllMessages();
         logd("CarrierResolverTest -Setup!");
     }
 
@@ -120,8 +107,6 @@
         logd("CarrierResolver -tearDown");
         mCarrierResolver.removeCallbacksAndMessages(null);
         mCarrierResolver = null;
-        mCarrierCarrierResolverHandler.quit();
-        mCarrierCarrierResolverHandler.join();
         super.tearDown();
     }
 
@@ -132,13 +117,13 @@
         doReturn(MCCMNC).when(mTelephonyManager).getSimOperatorNumericForPhone(eq(phoneId));
         // trigger sim loading event
         mCarrierResolver.sendEmptyMessage(SIM_LOAD_EVENT);
-        waitForMs(200);
+        processAllMessages();
         assertEquals(CID_VZW, mCarrierResolver.getCarrierId());
         assertEquals(NAME, mCarrierResolver.getCarrierName());
 
         doReturn(SPN_FI).when(mSimRecords).getServiceProviderName();
         mCarrierResolver.sendEmptyMessage(SIM_LOAD_EVENT);
-        waitForMs(200);
+        processAllMessages();
         assertEquals(CID_FI, mCarrierResolver.getCarrierId());
         assertEquals(NAME_FI, mCarrierResolver.getCarrierName());
     }
@@ -151,7 +136,7 @@
         doReturn(SPN_FI).when(mSimRecords).getServiceProviderName();
 
         mCarrierResolver.sendEmptyMessage(SIM_LOAD_EVENT);
-        waitForMs(200);
+        processAllMessages();
 
         assertEquals(CID_FI, mCarrierResolver.getCarrierId());
         assertEquals(NAME_FI, mCarrierResolver.getCarrierName());
@@ -161,7 +146,7 @@
                 .getSimOperatorNumericForPhone(eq(phoneId));
         doReturn(SPN_VODAFONE).when(mSimRecords).getServiceProviderName();
         mCarrierResolver.sendEmptyMessage(SIM_LOAD_EVENT);
-        waitForMs(200);
+        processAllMessages();
         assertEquals(CID_VODAFONE, mCarrierResolver.getCarrierId());
         assertEquals(NAME_VODAFONE, mCarrierResolver.getCarrierName());
         assertEquals(CID_VODAFONE, mCarrierResolver.getMnoCarrierId());
@@ -175,27 +160,27 @@
                 .getSimOperatorNumericForPhone(eq(phoneId));
 
         mCarrierResolver.sendEmptyMessage(SIM_LOAD_EVENT);
-        waitForMs(200);
+        processAllMessages();
         assertEquals(CID_ATT, mCarrierResolver.getCarrierId());
         assertEquals(CID_ATT, mCarrierResolver.getSpecificCarrierId());
 
         doReturn(GID_TRACFONE).when(mPhone).getGroupIdLevel1();
         mCarrierResolver.sendEmptyMessage(SIM_LOAD_EVENT);
-        waitForMs(200);
+        processAllMessages();
         assertEquals(CID_TRACFONE, mCarrierResolver.getCarrierId());
         assertEquals(CID_TRACFONE_ATT, mCarrierResolver.getSpecificCarrierId());
 
         doReturn(MCCMNC_TRACFONE_TMO).when(mTelephonyManager)
                 .getSimOperatorNumericForPhone(eq(phoneId));
         mCarrierResolver.sendEmptyMessage(SIM_LOAD_EVENT);
-        waitForMs(200);
+        processAllMessages();
         assertEquals(CID_TRACFONE, mCarrierResolver.getCarrierId());
         assertEquals(CID_TRACFONE_TMO, mCarrierResolver.getSpecificCarrierId());
 
         doReturn(MCCMNC_O2).when(mTelephonyManager)
                 .getSimOperatorNumericForPhone(eq(phoneId));
         mCarrierResolver.sendEmptyMessage(SIM_LOAD_EVENT);
-        waitForMs(200);
+        processAllMessages();
         assertEquals(CID_O2, mCarrierResolver.getCarrierId());
         assertEquals(CID_O2, mCarrierResolver.getSpecificCarrierId());
 
@@ -203,7 +188,7 @@
                 .getSimOperatorNumericForPhone(eq(phoneId));
         doReturn(GID_O2_PREPAID).when(mPhone).getGroupIdLevel1();
         mCarrierResolver.sendEmptyMessage(SIM_LOAD_EVENT);
-        waitForMs(200);
+        processAllMessages();
         assertEquals(CID_O2, mCarrierResolver.getCarrierId());
         assertEquals(CID_O2_PREPAID, mCarrierResolver.getSpecificCarrierId());
     }
@@ -215,12 +200,12 @@
         doReturn(MCCMNC).when(mTelephonyManager).getSimOperatorNumericForPhone(eq(phoneId));
         // trigger sim loading event
         mCarrierResolver.sendEmptyMessage(SIM_LOAD_EVENT);
-        waitForMs(200);
+        processAllMessages();
         assertEquals(CID_VZW, mCarrierResolver.getCarrierId());
         assertEquals(NAME, mCarrierResolver.getCarrierName());
         // trigger sim absent event
         mCarrierResolver.resolveSubscriptionCarrierId(IccCardConstants.INTENT_VALUE_ICC_ABSENT);
-        waitForMs(200);
+        processAllMessages();
         assertEquals(CID_UNKNOWN, mCarrierResolver.getCarrierId());
         assertNull(mCarrierResolver.getCarrierName());
         assertNull(mCarrierResolver.getCarrierName());
@@ -234,7 +219,7 @@
         doReturn("12345").when(mTelephonyManager).getSimOperatorNumericForPhone(eq(phoneId));
         // trigger sim loading event
         mCarrierResolver.sendEmptyMessage(SIM_LOAD_EVENT);
-        waitForMs(200);
+        processAllMessages();
         assertEquals(CID_UNKNOWN, mCarrierResolver.getCarrierId());
         assertNull(mCarrierResolver.getCarrierName());
     }
@@ -244,7 +229,7 @@
     public void testGetCarrierIdFromIdentifier() {
         // trigger sim loading event
         mCarrierResolver.sendEmptyMessage(SIM_LOAD_EVENT);
-        waitForMs(200);
+        processAllMessages();
 
         CarrierIdentifier identifier = new CarrierIdentifier(null, null, null, null, null, null);
         int carrierid = mCarrierResolver.getCarrierIdFromIdentifier(mContext, identifier);
@@ -268,14 +253,14 @@
         doReturn(MCCMNC).when(mTelephonyManager).getSimOperatorNumericForPhone(eq(phoneId));
         // trigger sim loading event
         mCarrierResolver.sendEmptyMessage(SIM_LOAD_EVENT);
-        waitForMs(200);
+        processAllMessages();
         assertEquals(CID_VZW, mCarrierResolver.getCarrierId());
         assertEquals(NAME, mCarrierResolver.getCarrierName());
         // mock apn
         ((MockContentResolver) mContext.getContentResolver()).addProvider(
                 Carriers.CONTENT_URI.getAuthority(), new CarrierIdContentProvider());
         mCarrierResolver.sendEmptyMessage(PREFER_APN_SET_EVENT);
-        waitForMs(200);
+        processAllMessages();
         assertEquals(CID_DOCOMO, mCarrierResolver.getCarrierId());
         assertEquals(NAME_DOCOMO, mCarrierResolver.getCarrierName());
     }
diff --git a/tests/telephonytests/src/com/android/internal/telephony/CarrierServiceBindHelperTest.java b/tests/telephonytests/src/com/android/internal/telephony/CarrierServiceBindHelperTest.java
new file mode 100644
index 0000000..a35f64b
--- /dev/null
+++ b/tests/telephonytests/src/com/android/internal/telephony/CarrierServiceBindHelperTest.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2019 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;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.mockito.Mockito.clearInvocations;
+import static org.mockito.Mockito.doReturn;
+
+import android.os.Message;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper
+public class CarrierServiceBindHelperTest extends TelephonyTest {
+    CarrierServiceBindHelper mCarrierServiceBindHelper;
+    @Before
+    public void setUp() throws Exception {
+        super.setUp(getClass().getSimpleName());
+        doReturn(1).when(mTelephonyManager).getActiveModemCount();
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        // Restore system properties.
+        super.tearDown();
+    }
+
+    @Test
+    @SmallTest
+    public void testMultiSimConfigChanged() throws Exception {
+        clearInvocations(mPhoneConfigurationManager);
+        mCarrierServiceBindHelper = new CarrierServiceBindHelper(mContext);
+        assertEquals(1, mCarrierServiceBindHelper.mBindings.size());
+        assertEquals(1, mCarrierServiceBindHelper.mLastSimState.size());
+        assertNotNull(mCarrierServiceBindHelper.mBindings.get(0));
+        assertNotNull(mCarrierServiceBindHelper.mLastSimState.get(0));
+
+        // Verify registration of EVENT_MULTI_SIM_CONFIG_CHANGED.
+        doReturn(2).when(mTelephonyManager).getActiveModemCount();
+        PhoneConfigurationManager.notifyMultiSimConfigChange(2);
+        processAllMessages();
+
+        assertEquals(2, mCarrierServiceBindHelper.mBindings.size());
+        assertEquals(2, mCarrierServiceBindHelper.mLastSimState.size());
+        assertNotNull(mCarrierServiceBindHelper.mBindings.get(0));
+        assertNotNull(mCarrierServiceBindHelper.mBindings.get(1));
+        assertNotNull(mCarrierServiceBindHelper.mLastSimState.get(0));
+        assertNotNull(mCarrierServiceBindHelper.mLastSimState.get(1));
+
+        // Switch back to single SIM.
+        doReturn(1).when(mTelephonyManager).getActiveModemCount();
+        PhoneConfigurationManager.notifyMultiSimConfigChange(1);
+        processAllMessages();
+
+        assertEquals(1, mCarrierServiceBindHelper.mBindings.size());
+        assertEquals(1, mCarrierServiceBindHelper.mLastSimState.size());
+        assertNotNull(mCarrierServiceBindHelper.mBindings.get(0));
+        assertNotNull(mCarrierServiceBindHelper.mLastSimState.get(0));
+    }
+
+    @Test
+    public void testUnbindWhenNotBound() throws Exception {
+        mCarrierServiceBindHelper = new CarrierServiceBindHelper(mContext);
+
+        // Try unbinding without binding and make sure we don't throw an Exception
+        mCarrierServiceBindHelper.mHandler.handleMessage(
+                Message.obtain(mCarrierServiceBindHelper.mHandler,
+                        CarrierServiceBindHelper.EVENT_PERFORM_IMMEDIATE_UNBIND,
+                        new Integer(0)));
+    }
+}
diff --git a/tests/telephonytests/src/com/android/internal/telephony/CarrierServiceStateTrackerTest.java b/tests/telephonytests/src/com/android/internal/telephony/CarrierServiceStateTrackerTest.java
index 2dc5c66..27ffeef 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/CarrierServiceStateTrackerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/CarrierServiceStateTrackerTest.java
@@ -16,8 +16,6 @@
 
 package com.android.internal.telephony;
 
-import static com.android.internal.telephony.TelephonyTestUtils.waitForMs;
-
 import static org.mockito.Mockito.any;
 import static org.mockito.Mockito.atLeast;
 import static org.mockito.Mockito.doReturn;
@@ -31,17 +29,19 @@
 import android.app.NotificationManager;
 import android.content.Context;
 import android.content.Intent;
-import android.os.HandlerThread;
 import android.os.Message;
 import android.os.PersistableBundle;
 import android.provider.Settings;
 import android.telephony.CarrierConfigManager;
 import android.telephony.ServiceState;
 import android.test.suitebuilder.annotation.SmallTest;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
 
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
+import org.junit.runner.RunWith;
 import org.mockito.MockitoAnnotations;
 
 import java.util.Map;
@@ -49,58 +49,43 @@
 /**
  * Unit tests for {@link com.android.internal.telephony.CarrierServiceStateTracker}.
  */
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper
 public class CarrierServiceStateTrackerTest extends TelephonyTest {
     public static final String LOG_TAG = "CSST";
-    public static final int TEST_TIMEOUT = 5000;
 
     private CarrierServiceStateTracker mSpyCarrierSST;
     private CarrierServiceStateTracker mCarrierSST;
-    private CarrierServiceStateTrackerTestHandler mCarrierServiceStateTrackerTestHandler;
+
+    private static final int SUB_ID = 1;
 
     NotificationManager mNotificationManager;
     PersistableBundle mBundle;
 
-    private class CarrierServiceStateTrackerTestHandler extends HandlerThread {
-        private CarrierServiceStateTrackerTestHandler(String name) {
-            super(name);
-        }
-
-        @Override
-        public void onLooperPrepared() {
-            mCarrierSST = new CarrierServiceStateTracker(mPhone, mSST);
-            mSpyCarrierSST = spy(mCarrierSST);
-            setReady(true);
-        }
-    }
-
     @Before
     public void setUp() throws Exception {
         MockitoAnnotations.initMocks(this);
         logd(LOG_TAG + "Setup!");
         super.setUp(getClass().getSimpleName());
         mBundle = mContextFixture.getCarrierConfigBundle();
-        when(mPhone.getSubId()).thenReturn(1);
-        mCarrierServiceStateTrackerTestHandler =
-                new CarrierServiceStateTrackerTestHandler(getClass().getSimpleName());
-        mCarrierServiceStateTrackerTestHandler.start();
+        when(mPhone.getSubId()).thenReturn(SUB_ID);
+        mCarrierSST = new CarrierServiceStateTracker(mPhone, mSST);
+        mSpyCarrierSST = spy(mCarrierSST);
 
         mNotificationManager = (NotificationManager) mContext.getSystemService(
                 Context.NOTIFICATION_SERVICE);
 
         setDefaultValues();
-        waitUntilReady();
+        processAllMessages();
     }
 
     private void setDefaultValues() {
-        mBundle.putInt(CarrierConfigManager.KEY_PREF_NETWORK_NOTIFICATION_DELAY_INT,
-                0);
-        mBundle.putInt(CarrierConfigManager.KEY_EMERGENCY_NOTIFICATION_DELAY_INT,
-                0);
+        mBundle.putInt(CarrierConfigManager.KEY_PREF_NETWORK_NOTIFICATION_DELAY_INT, 0);
+        mBundle.putInt(CarrierConfigManager.KEY_EMERGENCY_NOTIFICATION_DELAY_INT, 0);
     }
 
     @After
     public void tearDown() throws Exception {
-        mCarrierServiceStateTrackerTestHandler.quit();
         super.tearDown();
     }
 
@@ -113,11 +98,12 @@
         doReturn(false).when(mSpyCarrierSST).evaluateSendingMessage(any());
         doReturn(mNotificationManager).when(mSpyCarrierSST).getNotificationManager(any());
         mSpyCarrierSST.handleMessage(notificationMsg);
-        waitForHandlerAction(mSpyCarrierSST, TEST_TIMEOUT);
+        processAllMessages();
         verify(mNotificationManager).cancel(
-                CarrierServiceStateTracker.NOTIFICATION_EMERGENCY_NETWORK);
+                CarrierServiceStateTracker.EMERGENCY_NOTIFICATION_TAG, SUB_ID);
         verify(mNotificationManager).cancel(
-                CarrierServiceStateTracker.NOTIFICATION_PREF_NETWORK);
+                CarrierServiceStateTracker.PREF_NETWORK_NOTIFICATION_TAG, SUB_ID);
+
     }
 
     @Test
@@ -133,11 +119,13 @@
         doReturn(mNotificationBuilder).when(mSpyCarrierSST).getNotificationBuilder(any());
         doReturn(mNotificationManager).when(mSpyCarrierSST).getNotificationManager(any());
         mSpyCarrierSST.handleMessage(notificationMsg);
-        waitForHandlerAction(mSpyCarrierSST, TEST_TIMEOUT);
+        processAllMessages();
         verify(mNotificationManager).notify(
-                eq(CarrierServiceStateTracker.NOTIFICATION_PREF_NETWORK), isA(Notification.class));
+                eq(CarrierServiceStateTracker.EMERGENCY_NOTIFICATION_TAG),
+                eq(SUB_ID), isA(Notification.class));
         verify(mNotificationManager).notify(
-                eq(CarrierServiceStateTracker.NOTIFICATION_EMERGENCY_NETWORK), any());
+                eq(CarrierServiceStateTracker.PREF_NETWORK_NOTIFICATION_TAG),
+                eq(SUB_ID), any());
     }
 
     @Test
@@ -146,7 +134,7 @@
         logd(LOG_TAG + ":testSendPrefNetworkNotification()");
         Intent intent = new Intent().setAction(CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED);
         mContext.sendBroadcast(intent);
-        waitForMs(300);
+        processAllMessages();
 
         Map<Integer, CarrierServiceStateTracker.NotificationType> notificationTypeMap =
                 mCarrierSST.getNotificationTypeMap();
@@ -157,8 +145,8 @@
         notificationTypeMap.put(CarrierServiceStateTracker.NOTIFICATION_PREF_NETWORK,
                 spyPrefNetworkNotification);
         Notification.Builder mNotificationBuilder = new Notification.Builder(mContext);
-        doReturn(ServiceState.STATE_OUT_OF_SERVICE).when(mSST.mSS).getVoiceRegState();
-        doReturn(ServiceState.STATE_OUT_OF_SERVICE).when(mSST.mSS).getDataRegState();
+        doReturn(ServiceState.STATE_OUT_OF_SERVICE).when(mSST.mSS).getState();
+        doReturn(ServiceState.STATE_OUT_OF_SERVICE).when(mSST.mSS).getDataRegistrationState();
         doReturn(true).when(mSST).isRadioOn();
         doReturn(mNotificationBuilder).when(spyPrefNetworkNotification).getNotificationBuilder();
 
@@ -167,17 +155,19 @@
                 RILConstants.NETWORK_MODE_LTE_CDMA_EVDO);
         mSpyCarrierSST.getContentObserver().dispatchChange(false,
                 Settings.Global.getUriFor(prefNetworkMode));
-        waitForMs(500);
+        processAllMessages();
         verify(mNotificationManager, atLeast(1)).notify(
-                eq(CarrierServiceStateTracker.NOTIFICATION_PREF_NETWORK), isA(Notification.class));
+                eq(CarrierServiceStateTracker.PREF_NETWORK_NOTIFICATION_TAG),
+                eq(SUB_ID), isA(Notification.class));
+
 
         Settings.Global.putInt(mContext.getContentResolver(), prefNetworkMode,
                 RILConstants.NETWORK_MODE_LTE_CDMA_EVDO_GSM_WCDMA);
         mSpyCarrierSST.getContentObserver().dispatchChange(false,
                 Settings.Global.getUriFor(prefNetworkMode));
-        waitForMs(500);
+        processAllMessages();
         verify(mNotificationManager, atLeast(1)).cancel(
-                CarrierServiceStateTracker.NOTIFICATION_PREF_NETWORK);
+                CarrierServiceStateTracker.PREF_NETWORK_NOTIFICATION_TAG, SUB_ID);
     }
 
     @Test
@@ -186,7 +176,7 @@
         logd(LOG_TAG + ":testSendEmergencyNetworkNotification()");
         Intent intent = new Intent().setAction(CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED);
         mContext.sendBroadcast(intent);
-        waitForMs(300);
+        processAllMessages();
 
         Map<Integer, CarrierServiceStateTracker.NotificationType> notificationTypeMap =
                 mCarrierSST.getNotificationTypeMap();
@@ -197,7 +187,6 @@
         notificationTypeMap.put(CarrierServiceStateTracker.NOTIFICATION_EMERGENCY_NETWORK,
                 spyEmergencyNetworkNotification);
         Notification.Builder mNotificationBuilder = new Notification.Builder(mContext);
-        doReturn(ServiceState.STATE_OUT_OF_SERVICE).when(mSST.mSS).getVoiceRegState();
         doReturn(mNotificationBuilder).when(spyEmergencyNetworkNotification)
                 .getNotificationBuilder();
 
@@ -205,15 +194,15 @@
         Message notificationMsg = mSpyCarrierSST.obtainMessage(
                 CarrierServiceStateTracker.CARRIER_EVENT_IMS_CAPABILITIES_CHANGED, null);
         mSpyCarrierSST.handleMessage(notificationMsg);
-        waitForHandlerAction(mSpyCarrierSST, TEST_TIMEOUT);
+        processAllMessages();
         verify(mNotificationManager).notify(
-                eq(CarrierServiceStateTracker.NOTIFICATION_EMERGENCY_NETWORK),
-                isA(Notification.class));
+                eq(CarrierServiceStateTracker.EMERGENCY_NOTIFICATION_TAG),
+                eq(SUB_ID), isA(Notification.class));
 
         doReturn(false).when(mPhone).isWifiCallingEnabled();
         mSpyCarrierSST.handleMessage(notificationMsg);
-        waitForHandlerAction(mSpyCarrierSST, TEST_TIMEOUT);
+        processAllMessages();
         verify(mNotificationManager, atLeast(2)).cancel(
-                CarrierServiceStateTracker.NOTIFICATION_EMERGENCY_NETWORK);
+                CarrierServiceStateTracker.EMERGENCY_NOTIFICATION_TAG, SUB_ID);
     }
 }
diff --git a/tests/telephonytests/src/com/android/internal/telephony/CarrierServicesSmsFilterTest.java b/tests/telephonytests/src/com/android/internal/telephony/CarrierServicesSmsFilterTest.java
index 6fd43e6..88481dc 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/CarrierServicesSmsFilterTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/CarrierServicesSmsFilterTest.java
@@ -39,8 +39,8 @@
 import android.service.carrier.ICarrierMessagingService;
 import android.service.carrier.MessagePdu;
 import android.test.suitebuilder.annotation.SmallTest;
-import android.util.LocalLog;
 
+import com.android.internal.telephony.LocalLog;
 import com.android.internal.telephony.uicc.UiccCard;
 
 import org.junit.After;
@@ -76,12 +76,10 @@
         super.setUp(getClass().getSimpleName());
         if (Looper.myLooper() == null) {
             Looper.prepare();
-            Looper.loop();
         }
         mCarrierServicesSmsFilterUT = new CarrierServicesSmsFilter(
                 mContext, mPhone, new byte[][]{SMS_PDU},
-                0, "3gpp", mFilterCallback, getClass().getSimpleName(), new LocalLog(64)
-        );
+                0, "3gpp", mFilterCallback, getClass().getSimpleName(), new LocalLog(10), 1L);
     }
 
     @After
diff --git a/tests/telephonytests/src/com/android/internal/telephony/CarrierSignalAgentTest.java b/tests/telephonytests/src/com/android/internal/telephony/CarrierSignalAgentTest.java
index 96a5e7f..9030124 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/CarrierSignalAgentTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/CarrierSignalAgentTest.java
@@ -15,10 +15,8 @@
  */
 package com.android.internal.telephony;
 
-import static com.android.internal.telephony.TelephonyIntents.ACTION_CARRIER_SIGNAL_PCO_VALUE;
-import static com.android.internal.telephony.TelephonyIntents
-        .ACTION_CARRIER_SIGNAL_REQUEST_NETWORK_FAILED;
-import static com.android.internal.telephony.TelephonyTestUtils.waitForMs;
+import static android.telephony.TelephonyManager.ACTION_CARRIER_SIGNAL_PCO_VALUE;
+import static android.telephony.TelephonyManager.ACTION_CARRIER_SIGNAL_REQUEST_NETWORK_FAILED;
 
 import static org.junit.Assert.assertEquals;
 import static org.mockito.ArgumentMatchers.anyLong;
@@ -31,15 +29,17 @@
 
 import android.content.Intent;
 import android.content.pm.ResolveInfo;
-import android.os.HandlerThread;
 import android.os.Message;
 import android.os.PersistableBundle;
 import android.telephony.CarrierConfigManager;
 import android.test.suitebuilder.annotation.SmallTest;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
 
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
+import org.junit.runner.RunWith;
 import org.mockito.ArgumentCaptor;
 import org.mockito.Mock;
 
@@ -47,43 +47,29 @@
 import java.util.Arrays;
 import java.util.Objects;
 
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper
 public class CarrierSignalAgentTest extends TelephonyTest {
 
     private CarrierSignalAgent mCarrierSignalAgentUT;
     private PersistableBundle mBundle;
-    private CarrierSignalAgentHandler mCarrierSignalAgentHandler;
     private static final String PCO_RECEIVER = "pak/PCO_RECEIVER";
     private static final String DC_ERROR_RECEIVER = "pak/DC_ERROR_RECEIVER";
     @Mock
     ResolveInfo mResolveInfo;
 
-    private class CarrierSignalAgentHandler extends HandlerThread {
-
-        private CarrierSignalAgentHandler(String name) {
-            super(name);
-        }
-
-        @Override
-        public void onLooperPrepared() {
-            mCarrierSignalAgentUT = new CarrierSignalAgent(mPhone);
-            setReady(true);
-        }
-    }
-
     @Before
     public void setUp() throws Exception {
         logd("CarrierSignalAgentTest +Setup!");
         super.setUp(getClass().getSimpleName());
         mBundle = mContextFixture.getCarrierConfigBundle();
-        mCarrierSignalAgentHandler = new CarrierSignalAgentHandler(getClass().getSimpleName());
-        mCarrierSignalAgentHandler.start();
-        waitUntilReady();
+        mCarrierSignalAgentUT = new CarrierSignalAgent(mPhone);
+        processAllMessages();
         logd("CarrierSignalAgentTest -Setup!");
     }
 
     @After
     public void tearDown() throws Exception {
-        mCarrierSignalAgentHandler.quit();
         super.tearDown();
     }
 
@@ -106,7 +92,7 @@
 
         // Trigger carrier config reloading
         mContext.sendBroadcast(new Intent(CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED));
-        waitForMs(50);
+        processAllMessages();
         count++;
 
         // Verify no broadcast has been sent due to no manifest receivers
@@ -148,7 +134,7 @@
 
         // Trigger carrier config reloading
         mContext.sendBroadcast(new Intent(CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED));
-        waitForMs(50);
+        processAllMessages();
         count++;
 
         // Verify broadcast has been sent to registered components
@@ -185,8 +171,8 @@
                 anyInt());
 
         mContext.sendBroadcast(new Intent(CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED));
+        processAllMessages();
         count++;
-        waitForMs(50);
 
         // Wake signal for PAK_PCO_RECEIVER
         mCarrierSignalAgentUT.notifyCarrierSignalReceivers(
@@ -240,7 +226,7 @@
                 new String[]{ PCO_RECEIVER + ":" + ACTION_CARRIER_SIGNAL_PCO_VALUE + ","
                         + ACTION_CARRIER_SIGNAL_REQUEST_NETWORK_FAILED });
         mContext.sendBroadcast(new Intent(CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED));
-        waitForMs(50);
+        processAllMessages();
         // verify no reset action on initial config load
         verify(mCarrierActionAgent, times(0)).sendMessageAtTime(any(Message.class), anyLong());
 
@@ -250,7 +236,7 @@
                 new String[]{ PCO_RECEIVER + ":" + ACTION_CARRIER_SIGNAL_REQUEST_NETWORK_FAILED
                         + "," + ACTION_CARRIER_SIGNAL_PCO_VALUE});
         mContext.sendBroadcast(new Intent(CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED));
-        waitForMs(50);
+        processAllMessages();
         // verify no reset action for the same config (different order)
         verify(mCarrierActionAgent, times(0)).sendMessageAtTime(any(Message.class), anyLong());
 
@@ -260,7 +246,7 @@
                 new String[]{ DC_ERROR_RECEIVER + ":" + ACTION_CARRIER_SIGNAL_REQUEST_NETWORK_FAILED
                         + "," + ACTION_CARRIER_SIGNAL_PCO_VALUE});
         mContext.sendBroadcast(new Intent(CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED));
-        waitForMs(50);
+        processAllMessages();
         // verify there is no reset action
         ArgumentCaptor<Message> messageArgumentCaptor = ArgumentCaptor.forClass(Message.class);
         verify(mCarrierActionAgent, times(1))
diff --git a/tests/telephonytests/src/com/android/internal/telephony/CbGeoUtilsTest.java b/tests/telephonytests/src/com/android/internal/telephony/CbGeoUtilsTest.java
index 787cb43..f9b3599 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/CbGeoUtilsTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/CbGeoUtilsTest.java
@@ -18,10 +18,11 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
-import com.android.internal.telephony.CbGeoUtils.Circle;
-import com.android.internal.telephony.CbGeoUtils.Geometry;
-import com.android.internal.telephony.CbGeoUtils.LatLng;
-import com.android.internal.telephony.CbGeoUtils.Polygon;
+import android.telephony.CbGeoUtils;
+import android.telephony.CbGeoUtils.Circle;
+import android.telephony.CbGeoUtils.Geometry;
+import android.telephony.CbGeoUtils.LatLng;
+import android.telephony.CbGeoUtils.Polygon;
 
 import org.junit.Test;
 
diff --git a/tests/telephonytests/src/com/android/internal/telephony/CellIdentityCdmaTest.java b/tests/telephonytests/src/com/android/internal/telephony/CellIdentityCdmaTest.java
index 2d7e29e..133b4ba 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/CellIdentityCdmaTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/CellIdentityCdmaTest.java
@@ -26,6 +26,7 @@
 /** Unit tests for {@link CellIdentityCdma}. */
 
 public class CellIdentityCdmaTest extends AndroidTestCase {
+    private static final String LOG_TAG = "CellIdentityCdmaTest";
 
     // Network Id ranges from 0 to 65535.
     private static final int NETWORK_ID  = 65535;
@@ -51,6 +52,10 @@
         assertEquals(LONGITUDE, ci.getLongitude());
         assertEquals(ALPHA_LONG, ci.getOperatorAlphaLong());
         assertEquals(ALPHA_SHORT, ci.getOperatorAlphaShort());
+
+        String globalCi = Integer.toString(SYSTEM_ID, 16) + Integer.toString(NETWORK_ID, 16)
+                + Integer.toString(BASESTATION_ID, 16);
+        assertEquals(globalCi, ci.getGlobalCellId());
     }
 
     @SmallTest
diff --git a/tests/telephonytests/src/com/android/internal/telephony/CellIdentityGsmTest.java b/tests/telephonytests/src/com/android/internal/telephony/CellIdentityGsmTest.java
index f50cea1..9d39b07 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/CellIdentityGsmTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/CellIdentityGsmTest.java
@@ -21,9 +21,11 @@
 import android.test.AndroidTestCase;
 import android.test.suitebuilder.annotation.SmallTest;
 
-/** Unit tests for {@link CellIdentityGsm}. */
+import java.util.Collections;
 
+/** Unit tests for {@link CellIdentityGsm}. */
 public class CellIdentityGsmTest extends AndroidTestCase {
+    private static final String LOG_TAG = "CellIdentityGsmTest";
 
     // Location Area Code ranges from 0 to 65535.
     private static final int LAC = 65535;
@@ -45,7 +47,7 @@
     public void testDefaultConstructor() {
         CellIdentityGsm ci =
                 new CellIdentityGsm(LAC, CID, ARFCN, BSIC, MCC_STR, MNC_STR,
-                        ALPHA_LONG, ALPHA_SHORT);
+                        ALPHA_LONG, ALPHA_SHORT, Collections.emptyList());
 
         assertEquals(LAC, ci.getLac());
         assertEquals(CID, ci.getCid());
@@ -59,6 +61,10 @@
         assertEquals(MCC_STR + MNC_STR, ci.getMobileNetworkOperator());
         assertEquals(ALPHA_LONG, ci.getOperatorAlphaLong());
         assertEquals(ALPHA_SHORT, ci.getOperatorAlphaShort());
+
+        String globalCi = MCC_STR + MNC_STR + Integer.toString(LAC, 16)
+                + Integer.toString(CID, 16);
+        assertTrue(globalCi.equals(ci.getGlobalCellId()));
     }
 
     @SmallTest
@@ -66,13 +72,17 @@
         final String mncWithThreeDigit = "061";
         CellIdentityGsm ci =
                 new CellIdentityGsm(LAC, CID, ARFCN, BSIC, MCC_STR, mncWithThreeDigit,
-                        ALPHA_LONG, ALPHA_SHORT);
+                        ALPHA_LONG, ALPHA_SHORT, Collections.emptyList());
 
         assertEquals(MCC, ci.getMcc());
         assertEquals(61, ci.getMnc());
         assertEquals(MCC_STR, ci.getMccString());
         assertEquals(mncWithThreeDigit, ci.getMncString());
         assertEquals(MCC_STR + mncWithThreeDigit, ci.getMobileNetworkOperator());
+
+        String globalCi = MCC_STR + mncWithThreeDigit + Integer.toString(LAC, 16)
+                + Integer.toString(CID, 16);
+        assertEquals(globalCi, ci.getGlobalCellId());
     }
 
     @SmallTest
@@ -80,68 +90,86 @@
         final String mncWithTwoDigit = "61";
         CellIdentityGsm ci =
                 new CellIdentityGsm(LAC, CID, ARFCN, BSIC, MCC_STR, mncWithTwoDigit,
-                        ALPHA_LONG, ALPHA_SHORT);
+                        ALPHA_LONG, ALPHA_SHORT, Collections.emptyList());
 
         assertEquals(MCC, ci.getMcc());
         assertEquals(61, ci.getMnc());
         assertEquals(MCC_STR, ci.getMccString());
         assertEquals(mncWithTwoDigit, ci.getMncString());
         assertEquals(MCC_STR + mncWithTwoDigit, ci.getMobileNetworkOperator());
+
+        String globalCi = MCC_STR + mncWithTwoDigit + Integer.toString(LAC, 16)
+                + Integer.toString(CID, 16);
+        assertEquals(globalCi, ci.getGlobalCellId());
     }
 
     @SmallTest
     public void testConstructorWithEmptyMccMnc() {
         CellIdentityGsm ci =
-                new CellIdentityGsm(LAC, CID, ARFCN, BSIC, null, null, ALPHA_LONG, ALPHA_SHORT);
+                new CellIdentityGsm(LAC, CID, ARFCN, BSIC, null, null, ALPHA_LONG, ALPHA_SHORT,
+                        Collections.emptyList());
 
         assertEquals(Integer.MAX_VALUE, ci.getMcc());
         assertEquals(Integer.MAX_VALUE, ci.getMnc());
         assertNull(ci.getMccString());
         assertNull(ci.getMncString());
         assertNull(ci.getMobileNetworkOperator());
+        assertNull(ci.getGlobalCellId());
 
-        ci = new CellIdentityGsm(LAC, CID, ARFCN, BSIC, MCC_STR, null, ALPHA_LONG, ALPHA_SHORT);
+        ci = new CellIdentityGsm(LAC, CID, ARFCN, BSIC, MCC_STR, null, ALPHA_LONG, ALPHA_SHORT,
+                Collections.emptyList());
 
         assertEquals(MCC, ci.getMcc());
         assertEquals(Integer.MAX_VALUE, ci.getMnc());
         assertEquals(MCC_STR, ci.getMccString());
         assertNull(ci.getMncString());
         assertNull(ci.getMobileNetworkOperator());
+        assertNull(ci.getGlobalCellId());
 
-        ci = new CellIdentityGsm(LAC, CID, ARFCN, BSIC, null, MNC_STR, ALPHA_LONG, ALPHA_SHORT);
+        ci = new CellIdentityGsm(LAC, CID, ARFCN, BSIC, null, MNC_STR, ALPHA_LONG, ALPHA_SHORT,
+                Collections.emptyList());
 
         assertEquals(MNC, ci.getMnc());
         assertEquals(Integer.MAX_VALUE, ci.getMcc());
         assertEquals(MNC_STR, ci.getMncString());
         assertNull(ci.getMccString());
         assertNull(ci.getMobileNetworkOperator());
+        assertNull(ci.getGlobalCellId());
 
-        ci = new CellIdentityGsm(LAC, CID, ARFCN, BSIC, "", "", ALPHA_LONG, ALPHA_SHORT);
+        ci = new CellIdentityGsm(LAC, CID, ARFCN, BSIC, "", "", ALPHA_LONG, ALPHA_SHORT,
+                Collections.emptyList());
 
         assertEquals(Integer.MAX_VALUE, ci.getMcc());
         assertEquals(Integer.MAX_VALUE, ci.getMnc());
         assertNull(ci.getMccString());
         assertNull(ci.getMncString());
         assertNull(ci.getMobileNetworkOperator());
+        assertNull(ci.getGlobalCellId());
     }
 
     @SmallTest
     public void testEquals() {
         CellIdentityGsm ciA = new CellIdentityGsm(
-                LAC, CID, ARFCN, BSIC, MCC_STR, MNC_STR, ALPHA_LONG, ALPHA_SHORT);
+                LAC, CID, ARFCN, BSIC, MCC_STR, MNC_STR, ALPHA_LONG, ALPHA_SHORT,
+                Collections.emptyList());
         CellIdentityGsm ciB = new CellIdentityGsm(
-                LAC, CID, ARFCN, BSIC,  MCC_STR, MNC_STR, ALPHA_LONG, ALPHA_SHORT);
+                LAC, CID, ARFCN, BSIC,  MCC_STR, MNC_STR, ALPHA_LONG, ALPHA_SHORT,
+                Collections.emptyList());
 
         assertTrue(ciA.equals(ciB));
 
-        ciA = new CellIdentityGsm(LAC, CID, ARFCN, BSIC, null, MNC_STR, ALPHA_LONG, ALPHA_SHORT);
-        ciB = new CellIdentityGsm(LAC, CID, ARFCN, BSIC, null, MNC_STR, ALPHA_LONG, ALPHA_SHORT);
+        ciA = new CellIdentityGsm(LAC, CID, ARFCN, BSIC, null, MNC_STR, ALPHA_LONG, ALPHA_SHORT,
+                Collections.emptyList());
+        ciB = new CellIdentityGsm(LAC, CID, ARFCN, BSIC, null, MNC_STR, ALPHA_LONG, ALPHA_SHORT,
+                Collections.emptyList());
 
         assertTrue(ciA.equals(ciB));
 
         ciA = new CellIdentityGsm(
-                LAC, CID, ARFCN, BSIC, MCC_STR,  MNC_STR, ALPHA_LONG, ALPHA_SHORT);
-        ciB = new CellIdentityGsm(LAC, CID, ARFCN, BSIC, null, MNC_STR, ALPHA_LONG, ALPHA_SHORT);
+                LAC, CID, ARFCN, BSIC, MCC_STR,  MNC_STR, ALPHA_LONG, ALPHA_SHORT,
+                Collections.emptyList());
+        ciB = new CellIdentityGsm(LAC, CID, ARFCN, BSIC, null, MNC_STR, ALPHA_LONG, ALPHA_SHORT,
+                Collections.emptyList());
 
         assertFalse(ciA.equals(ciB));
     }
@@ -150,7 +178,7 @@
     public void testParcel() {
         CellIdentityGsm ci =
                 new CellIdentityGsm(LAC, CID, ARFCN, BSIC, MCC_STR, MNC_STR,
-                        ALPHA_LONG, ALPHA_SHORT);
+                        ALPHA_LONG, ALPHA_SHORT, Collections.emptyList());
 
         Parcel p = Parcel.obtain();
         ci.writeToParcel(p, 0);
@@ -163,7 +191,8 @@
     @SmallTest
     public void testParcelWithUnknowMccMnc() {
         CellIdentityGsm ci =
-                new CellIdentityGsm(LAC, CID, ARFCN, BSIC, null, null, ALPHA_LONG, ALPHA_SHORT);
+                new CellIdentityGsm(LAC, CID, ARFCN, BSIC, null, null, ALPHA_LONG, ALPHA_SHORT,
+                        Collections.emptyList());
 
         Parcel p = Parcel.obtain();
         ci.writeToParcel(p, 0);
@@ -178,7 +207,8 @@
         final String invalidMcc = "randomStuff";
         final String invalidMnc = "randomStuff";
         CellIdentityGsm ci =
-                new CellIdentityGsm(LAC, CID, ARFCN, BSIC, null, null, ALPHA_LONG, ALPHA_SHORT);
+                new CellIdentityGsm(LAC, CID, ARFCN, BSIC, null, null, ALPHA_LONG, ALPHA_SHORT,
+                        Collections.emptyList());
 
         Parcel p = Parcel.obtain();
         ci.writeToParcel(p, 0);
@@ -187,4 +217,31 @@
         CellIdentityGsm newCi = CellIdentityGsm.CREATOR.createFromParcel(p);
         assertEquals(ci, newCi);
     }
+
+    @SmallTest
+    public void testgetGlobalCellId() {
+        CellIdentityGsm ci = new CellIdentityGsm(
+                LAC + 1, CID, ARFCN, BSIC, MCC_STR, MNC_STR, ALPHA_LONG, ALPHA_SHORT,
+                Collections.emptyList());
+        assertNull(ci.getGlobalCellId());
+
+        ci = new CellIdentityGsm(
+                LAC, CID + 1, ARFCN, BSIC, MCC_STR, MNC_STR, ALPHA_LONG, ALPHA_SHORT,
+                Collections.emptyList());
+        assertNull(ci.getGlobalCellId());
+
+        ci = new CellIdentityGsm(
+                LAC, -1, ARFCN, BSIC, MCC_STR, MNC_STR, ALPHA_LONG, ALPHA_SHORT,
+                Collections.emptyList());
+        assertNull(ci.getGlobalCellId());
+
+        // Test id with one digit and corresponding zero padding
+        int cid = 1;
+        ci = new CellIdentityGsm(
+                LAC, cid, ARFCN, BSIC, MCC_STR, MNC_STR, ALPHA_LONG, ALPHA_SHORT,
+                Collections.emptyList());
+        String globalCi = MCC_STR + MNC_STR + Integer.toString(LAC, 16)
+                + "000" + Integer.toString(cid, 16);
+        assertEquals(globalCi, ci.getGlobalCellId());
+    }
 }
diff --git a/tests/telephonytests/src/com/android/internal/telephony/CellIdentityLteTest.java b/tests/telephonytests/src/com/android/internal/telephony/CellIdentityLteTest.java
index bb0b6fa..77bea60 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/CellIdentityLteTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/CellIdentityLteTest.java
@@ -22,9 +22,12 @@
 import android.test.AndroidTestCase;
 import android.test.suitebuilder.annotation.SmallTest;
 
+import java.util.Collections;
+
 /** Unit tests for {@link CellIdentityLte}. */
 
 public class CellIdentityLteTest extends AndroidTestCase {
+    private static final String LOG_TAG = "CellIdentityLteTest";
 
     // Cell identity ranges from 0 to 268435455.
     private static final int CI = 268435455;
@@ -34,6 +37,7 @@
     private static final int TAC = 65535;
     // Absolute RF Channel Number ranges from 0 to 262140.
     private static final int EARFCN = 262140;
+    private static final int[] BANDS = new int[] {1, 2};
     private static final int MCC = 120;
     private static final int MNC = 260;
     private static final int BANDWIDTH = 5000;  // kHz
@@ -45,8 +49,8 @@
     @SmallTest
     public void testDefaultConstructor() {
         CellIdentityLte ci =
-                new CellIdentityLte(CI, PCI, TAC, EARFCN, BANDWIDTH, MCC_STR, MNC_STR,
-                        ALPHA_LONG, ALPHA_SHORT);
+                new CellIdentityLte(CI, PCI, TAC, EARFCN, BANDS, BANDWIDTH, MCC_STR, MNC_STR,
+                        ALPHA_LONG, ALPHA_SHORT, Collections.emptyList(), null);
 
         assertEquals(CI, ci.getCi());
         assertEquals(PCI, ci.getPci());
@@ -61,14 +65,17 @@
         assertEquals(MCC_STR + MNC_STR, ci.getMobileNetworkOperator());
         assertEquals(ALPHA_LONG, ci.getOperatorAlphaLong());
         assertEquals(ALPHA_SHORT, ci.getOperatorAlphaShort());
+
+        String globalCi = MCC_STR + MNC_STR + Integer.toString(CI, 16);
+        assertEquals(globalCi, ci.getGlobalCellId());
     }
 
     @SmallTest
     public void testConstructorWithThreeDigitMnc() {
         final String mncWithThreeDigit = "061";
         CellIdentityLte ci =
-                new CellIdentityLte(CI, PCI, TAC, EARFCN, BANDWIDTH, MCC_STR, mncWithThreeDigit,
-                        ALPHA_LONG, ALPHA_SHORT);
+                new CellIdentityLte(CI, PCI, TAC, EARFCN, BANDS, BANDWIDTH, MCC_STR,
+                        mncWithThreeDigit, ALPHA_LONG, ALPHA_SHORT, Collections.emptyList(), null);
 
         assertEquals(MCC, ci.getMcc());
         assertEquals(61, ci.getMnc());
@@ -81,8 +88,8 @@
     public void testConstructorWithTwoDigitMnc() {
         final String mncWithTwoDigit = "61";
         CellIdentityLte ci =
-                new CellIdentityLte(CI, PCI, TAC, EARFCN, BANDWIDTH, MCC_STR, mncWithTwoDigit,
-                        ALPHA_LONG, ALPHA_SHORT);
+                new CellIdentityLte(CI, PCI, TAC, EARFCN, BANDS, BANDWIDTH, MCC_STR,
+                        mncWithTwoDigit, ALPHA_LONG, ALPHA_SHORT, Collections.emptyList(), null);
 
         assertEquals(MCC, ci.getMcc());
         assertEquals(61, ci.getMnc());
@@ -94,7 +101,8 @@
     @SmallTest
     public void testConstructorWithEmptyMccMnc() {
         CellIdentityLte ci = new CellIdentityLte(
-                CI, PCI, TAC, EARFCN, BANDWIDTH, null, null, ALPHA_LONG, ALPHA_SHORT);
+                CI, PCI, TAC, EARFCN, BANDS, BANDWIDTH, null, null, ALPHA_LONG,
+                ALPHA_SHORT, Collections.emptyList(), null);
 
         assertEquals(Integer.MAX_VALUE, ci.getMcc());
         assertEquals(Integer.MAX_VALUE, ci.getMnc());
@@ -103,7 +111,8 @@
         assertNull(ci.getMobileNetworkOperator());
 
         ci = new CellIdentityLte(
-                CI, PCI, TAC, EARFCN, BANDWIDTH, MCC_STR, null, ALPHA_LONG, ALPHA_SHORT);
+                CI, PCI, TAC, EARFCN, BANDS, BANDWIDTH, MCC_STR, null, ALPHA_LONG, ALPHA_SHORT,
+                Collections.emptyList(), null);
 
         assertEquals(MCC, ci.getMcc());
         assertEquals(Integer.MAX_VALUE, ci.getMnc());
@@ -112,7 +121,8 @@
         assertNull(ci.getMobileNetworkOperator());
 
         ci = new CellIdentityLte(
-                CI, PCI, TAC, EARFCN, BANDWIDTH, null, MNC_STR, ALPHA_LONG, ALPHA_SHORT);
+                CI, PCI, TAC, EARFCN, BANDS, BANDWIDTH, null, MNC_STR, ALPHA_LONG, ALPHA_SHORT,
+                Collections.emptyList(), null);
 
         assertEquals(MNC, ci.getMnc());
         assertEquals(Integer.MAX_VALUE, ci.getMcc());
@@ -121,7 +131,8 @@
         assertNull(ci.getMobileNetworkOperator());
 
         ci = new CellIdentityLte(
-                CI, PCI, TAC, EARFCN, BANDWIDTH, "", "", ALPHA_LONG, ALPHA_SHORT);
+                CI, PCI, TAC, EARFCN, BANDS, BANDWIDTH, "", "", ALPHA_LONG, ALPHA_SHORT,
+                Collections.emptyList(), null);
 
         assertEquals(Integer.MAX_VALUE, ci.getMcc());
         assertEquals(Integer.MAX_VALUE, ci.getMnc());
@@ -152,23 +163,29 @@
     @SmallTest
     public void testEquals() {
         CellIdentityLte ciA = new CellIdentityLte(
-                CI, PCI, TAC, EARFCN, BANDWIDTH, MCC_STR, MNC_STR, ALPHA_LONG, ALPHA_SHORT);
+                CI, PCI, TAC, EARFCN, BANDS, BANDWIDTH, MCC_STR, MNC_STR, ALPHA_LONG, ALPHA_SHORT,
+                Collections.emptyList(), null);
         CellIdentityLte ciB = new CellIdentityLte(
-                CI, PCI, TAC, EARFCN, BANDWIDTH, MCC_STR, MNC_STR, ALPHA_LONG, ALPHA_SHORT);
+                CI, PCI, TAC, EARFCN, BANDS, BANDWIDTH, MCC_STR, MNC_STR, ALPHA_LONG, ALPHA_SHORT,
+                Collections.emptyList(), null);
 
         assertTrue(ciA.equals(ciB));
 
         ciA = new CellIdentityLte(
-                CI, PCI, TAC, EARFCN, BANDWIDTH, null, null, ALPHA_LONG, ALPHA_SHORT);
+                CI, PCI, TAC, EARFCN, BANDS, BANDWIDTH, null, null, ALPHA_LONG, ALPHA_SHORT,
+                Collections.emptyList(), null);
         ciB = new CellIdentityLte(
-                CI, PCI, TAC, EARFCN, BANDWIDTH, null, null, ALPHA_LONG, ALPHA_SHORT);
+                CI, PCI, TAC, EARFCN, BANDS, BANDWIDTH, null, null, ALPHA_LONG, ALPHA_SHORT,
+                Collections.emptyList(), null);
 
         assertTrue(ciA.equals(ciB));
 
         ciA = new CellIdentityLte(
-                CI, PCI, TAC, EARFCN, BANDWIDTH, MCC_STR, null, ALPHA_LONG, ALPHA_SHORT);
+                CI, PCI, TAC, EARFCN, BANDS, BANDWIDTH, MCC_STR, null, ALPHA_LONG, ALPHA_SHORT,
+                Collections.emptyList(), null);
         ciB = new CellIdentityLte(
-                CI, PCI, TAC, EARFCN, BANDWIDTH, null, null, ALPHA_LONG, ALPHA_SHORT);
+                CI, PCI, TAC, EARFCN, BANDS, BANDWIDTH, null, null, ALPHA_LONG, ALPHA_SHORT,
+                Collections.emptyList(), null);
 
         assertFalse(ciA.equals(ciB));
     }
@@ -176,8 +193,8 @@
     @SmallTest
     public void testParcel() {
         CellIdentityLte ci =
-                new CellIdentityLte(CI, PCI, TAC, EARFCN, BANDWIDTH, MCC_STR, MNC_STR,
-                        ALPHA_LONG, ALPHA_SHORT);
+                new CellIdentityLte(CI, PCI, TAC, EARFCN, BANDS, BANDWIDTH, MCC_STR, MNC_STR,
+                        ALPHA_LONG, ALPHA_SHORT, Collections.emptyList(), null);
 
         Parcel p = Parcel.obtain();
         ci.writeToParcel(p, 0);
@@ -190,7 +207,8 @@
     @SmallTest
     public void testParcelWithUnknownMccMnc() {
         CellIdentityLte ci = new CellIdentityLte(
-                CI, PCI, TAC, EARFCN, BANDWIDTH, null, null, ALPHA_LONG, ALPHA_SHORT);
+                CI, PCI, TAC, EARFCN, BANDS, BANDWIDTH, null, null, ALPHA_LONG, ALPHA_SHORT,
+                Collections.emptyList(), null);
 
         Parcel p = Parcel.obtain();
         p.writeInt(CellInfo.TYPE_LTE);
@@ -202,6 +220,7 @@
         p.writeInt(PCI);
         p.writeInt(TAC);
         p.writeInt(EARFCN);
+        p.writeIntArray(BANDS);
         p.writeInt(BANDWIDTH);
         p.setDataPosition(0);
 
@@ -214,7 +233,8 @@
         final String invalidMcc = "randomStuff";
         final String invalidMnc = "randomStuff";
         CellIdentityLte ci = new CellIdentityLte(
-                CI, PCI, TAC, EARFCN, BANDWIDTH, null, null, ALPHA_LONG, ALPHA_SHORT);
+                CI, PCI, TAC, EARFCN, BANDS, BANDWIDTH, null, null, ALPHA_LONG, ALPHA_SHORT,
+                Collections.emptyList(), null);
 
         Parcel p = Parcel.obtain();
         p.writeInt(CellInfo.TYPE_LTE);
@@ -226,6 +246,7 @@
         p.writeInt(PCI);
         p.writeInt(TAC);
         p.writeInt(EARFCN);
+        p.writeIntArray(BANDS);
         p.writeInt(BANDWIDTH);
         p.setDataPosition(0);
 
diff --git a/tests/telephonytests/src/com/android/internal/telephony/CellIdentityNrTest.java b/tests/telephonytests/src/com/android/internal/telephony/CellIdentityNrTest.java
index ed7fc1c..e350e66 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/CellIdentityNrTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/CellIdentityNrTest.java
@@ -19,29 +19,39 @@
 import static com.google.common.truth.Truth.assertThat;
 
 import android.os.Parcel;
+import android.telephony.AccessNetworkConstants;
 import android.telephony.CellIdentityNr;
 import android.telephony.CellInfo;
 import android.test.AndroidTestCase;
 
 import org.junit.Test;
 
+import java.util.Arrays;
+import java.util.Collections;
+
 public class CellIdentityNrTest extends AndroidTestCase {
-    private static final String MCC = "310";
-    private static final String MNC = "260";
-    private static final String ANOTHER_MCC = "134";
-    private static final String ANOTHER_MNC = "256";
+    private static final String LOG_TAG = "CellIdentityNrTest";
+    private static final String MCC_STR = "310";
+    private static final String MNC_STR = "260";
+    private static final String ANOTHER_MCC_STR = "134";
+    private static final String ANOTHER_MNC_STR = "256";
     private static final String ALPHAL = "long operator name";
     private static final String ALPHAS = "lon";
     private static final int NRARFCN = 13456;
     private static final int PCI = 123;
     private static final int TAC = 32767;
     private static final int NCI = 8675309;
+    private static final int[] BANDS = new int[] {
+            AccessNetworkConstants.NgranBands.BAND_1,
+            AccessNetworkConstants.NgranBands.BAND_2
+    };
 
     @Test
     public void testGetMethod() {
         // GIVEN an instance of CellIdentityNr
         CellIdentityNr cellIdentityNr =
-                new CellIdentityNr(PCI, TAC, NRARFCN, MCC, MNC, NCI, ALPHAL, ALPHAS);
+                new CellIdentityNr(PCI, TAC, NRARFCN, BANDS, MCC_STR, MNC_STR, NCI, ALPHAL, ALPHAS,
+                        Collections.emptyList());
 
         // THEN the get method should return correct value
         assertThat(cellIdentityNr.getType()).isEqualTo(CellInfo.TYPE_NR);
@@ -50,19 +60,23 @@
         assertThat(cellIdentityNr.getTac()).isEqualTo(TAC);
         assertThat(cellIdentityNr.getOperatorAlphaLong()).isEqualTo(ALPHAL);
         assertThat(cellIdentityNr.getOperatorAlphaShort()).isEqualTo(ALPHAS);
-        assertThat(cellIdentityNr.getMccString()).isEqualTo(MCC);
-        assertThat(cellIdentityNr.getMncString()).isEqualTo(MNC);
-        assertThat(cellIdentityNr.getMncString()).isEqualTo(MNC);
+        assertThat(cellIdentityNr.getMccString()).isEqualTo(MCC_STR);
+        assertThat(cellIdentityNr.getMncString()).isEqualTo(MNC_STR);
         assertThat(cellIdentityNr.getNci()).isEqualTo(NCI);
+
+        String globalCi = MCC_STR + MNC_STR + "000" + Integer.toString(NCI, 16);
+        assertEquals(globalCi, cellIdentityNr.getGlobalCellId());
     }
 
     @Test
     public void testEquals_sameParameters() {
         // GIVEN an instance of CellIdentityNr, and create another object with the same parameters
         CellIdentityNr cellIdentityNr =
-                new CellIdentityNr(PCI, TAC, NRARFCN, MCC, MNC, NCI, ALPHAL, ALPHAS);
+                new CellIdentityNr(PCI, TAC, NRARFCN, BANDS, MCC_STR, MNC_STR, NCI,
+                        ALPHAL, ALPHAS, Collections.emptyList());
         CellIdentityNr anotherCellIdentityNr =
-                new CellIdentityNr(PCI, TAC, NRARFCN, MCC, MNC, NCI, ALPHAL, ALPHAS);
+                new CellIdentityNr(PCI, TAC, NRARFCN, BANDS, MCC_STR, MNC_STR, NCI,
+                        ALPHAL, ALPHAS, Collections.emptyList());
 
         // THEN this two objects are equivalent
         assertThat(cellIdentityNr).isEqualTo(anotherCellIdentityNr);
@@ -72,9 +86,11 @@
     public void testEquals_differentParameters() {
         // GIVEN an instance of CellIdentityNr, and create another object with different parameters
         CellIdentityNr cellIdentityNr =
-                new CellIdentityNr(PCI, TAC, NRARFCN, MCC, MNC, NCI, ALPHAL, ALPHAS);
+                new CellIdentityNr(PCI, TAC, NRARFCN, BANDS, MCC_STR, MNC_STR, NCI,
+                        ALPHAL, ALPHAS, Collections.emptyList());
         CellIdentityNr anotherCellIdentityNr =
-                new CellIdentityNr(PCI, TAC, NRARFCN, MCC, MNC, NCI + 1, ALPHAL, ALPHAS);
+                new CellIdentityNr(PCI, TAC, NRARFCN, BANDS, MCC_STR, MNC_STR, NCI + 1,
+                        ALPHAL, ALPHAS, Collections.emptyList());
 
         // THEN this two objects are different
         assertThat(cellIdentityNr).isNotEqualTo(anotherCellIdentityNr);
@@ -84,7 +100,8 @@
     public void testParcel() {
         // GIVEN an instance of CellIdentityNr
         CellIdentityNr cellIdentityNr =
-                new CellIdentityNr(PCI, TAC, NRARFCN, MCC, MNC, NCI, ALPHAL, ALPHAS);
+                new CellIdentityNr(PCI, TAC, NRARFCN, BANDS, MCC_STR, MNC_STR, NCI,
+                        ALPHAL, ALPHAS, Collections.emptyList());
 
         // WHEN write the object to parcel and create another object with that parcel
         Parcel parcel = Parcel.obtain();
@@ -93,15 +110,16 @@
         CellIdentityNr anotherCellIdentityNr = CellIdentityNr.CREATOR.createFromParcel(parcel);
 
         // THEN the new object is equal to the old one
-        assertThat(anotherCellIdentityNr).isEqualTo(anotherCellIdentityNr);
+        assertThat(anotherCellIdentityNr).isEqualTo(cellIdentityNr);
         assertThat(anotherCellIdentityNr.getType()).isEqualTo(CellInfo.TYPE_NR);
         assertThat(anotherCellIdentityNr.getNrarfcn()).isEqualTo(NRARFCN);
         assertThat(anotherCellIdentityNr.getPci()).isEqualTo(PCI);
         assertThat(anotherCellIdentityNr.getTac()).isEqualTo(TAC);
+        assertTrue(Arrays.equals(anotherCellIdentityNr.getBands(), BANDS));
         assertThat(anotherCellIdentityNr.getOperatorAlphaLong()).isEqualTo(ALPHAL);
         assertThat(anotherCellIdentityNr.getOperatorAlphaShort()).isEqualTo(ALPHAS);
-        assertThat(anotherCellIdentityNr.getMccString()).isEqualTo(MCC);
-        assertThat(anotherCellIdentityNr.getMncString()).isEqualTo(MNC);
+        assertThat(anotherCellIdentityNr.getMccString()).isEqualTo(MCC_STR);
+        assertThat(anotherCellIdentityNr.getMncString()).isEqualTo(MNC_STR);
         assertThat(anotherCellIdentityNr.getNci()).isEqualTo(NCI);
     }
 }
diff --git a/tests/telephonytests/src/com/android/internal/telephony/CellIdentityTdscdmaTest.java b/tests/telephonytests/src/com/android/internal/telephony/CellIdentityTdscdmaTest.java
index 78c5547..d1da119 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/CellIdentityTdscdmaTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/CellIdentityTdscdmaTest.java
@@ -24,7 +24,10 @@
 
 /** Unit tests for {@link CellIdentityTdscdma}. */
 
+import java.util.Collections;
+
 public class CellIdentityTdscdmaTest extends AndroidTestCase {
+    private static final String LOG_TAG = "CellIdentityTdscdmaTest";
 
     // Cell identity ranges from 0 to 268435456.
     private static final int CI = 268435456;
@@ -61,13 +64,15 @@
         assertEquals(CellInfo.UNAVAILABLE, ci.getUarfcn());
         assertNull(ci.getOperatorAlphaLong());
         assertNull(ci.getOperatorAlphaShort());
+        assertNull(ci.getGlobalCellId());
     }
 
     @SmallTest
     public void testConstructor() {
         CellIdentityTdscdma ci =
                 new CellIdentityTdscdma(
-                        MCC_STR, MNC_STR, LAC, CID, CPID, UARFCN, ALPHA_LONG, ALPHA_SHORT);
+                        MCC_STR, MNC_STR, LAC, CID, CPID, UARFCN, ALPHA_LONG, ALPHA_SHORT,
+                        Collections.emptyList(), null);
 
         assertEquals(MCC_STR, ci.getMccString());
         assertEquals(MNC_STR, ci.getMncString());
@@ -78,27 +83,33 @@
         assertEquals(UARFCN, ci.getUarfcn());
         assertEquals(ALPHA_LONG, ci.getOperatorAlphaLong());
         assertEquals(ALPHA_SHORT, ci.getOperatorAlphaShort());
+
+        String globalCi = MCC_STR + MNC_STR + Integer.toString(LAC, 16) + Integer.toString(CID, 16);
+        assertEquals(globalCi, ci.getGlobalCellId());
     }
 
     @SmallTest
     public void testConstructorWithEmptyMccMnc() {
         CellIdentityTdscdma ci = new CellIdentityTdscdma(
-                null, null, LAC, CID, CPID, UARFCN, "", "");
+                null, null, LAC, CID, CPID, UARFCN, "", "", Collections.emptyList(), null);
 
         assertNull(ci.getMccString());
         assertNull(ci.getMncString());
 
-        ci = new CellIdentityTdscdma(MCC_STR, null, LAC, CID, CPID, UARFCN, "", "");
+        ci = new CellIdentityTdscdma(MCC_STR, null, LAC, CID, CPID, UARFCN, "", "",
+                Collections.emptyList(), null);
 
         assertEquals(MCC_STR, ci.getMccString());
         assertNull(ci.getMncString());
 
-        ci = new CellIdentityTdscdma(null, MNC_STR, LAC, CID, CPID, UARFCN, "", "");
+        ci = new CellIdentityTdscdma(null, MNC_STR, LAC, CID, CPID, UARFCN, "", "",
+                Collections.emptyList(), null);
 
         assertEquals(MNC_STR, ci.getMncString());
         assertNull(ci.getMccString());
 
-        ci = new CellIdentityTdscdma("", "", LAC, CID, CPID, UARFCN, "", "");
+        ci = new CellIdentityTdscdma("", "", LAC, CID, CPID, UARFCN, "", "",
+                Collections.emptyList(), null);
 
         assertNull(ci.getMccString());
         assertNull(ci.getMncString());
@@ -107,7 +118,8 @@
     @SmallTest
     public void testParcel() {
         CellIdentityTdscdma ci = new CellIdentityTdscdma(
-                MCC_STR, MNC_STR, LAC, CID, UARFCN, CPID, ALPHA_LONG, ALPHA_SHORT);
+                MCC_STR, MNC_STR, LAC, CID, UARFCN, CPID, ALPHA_LONG, ALPHA_SHORT,
+                Collections.emptyList(), null);
 
         Parcel p = Parcel.obtain();
         ci.writeToParcel(p, 0);
@@ -121,7 +133,8 @@
     public void testParcelWithUnknowMccMnc() {
         CellIdentityTdscdma ci =
                 new CellIdentityTdscdma(
-                        null, null, LAC, CID, CPID, UARFCN, ALPHA_LONG, ALPHA_SHORT);
+                        null, null, LAC, CID, CPID, UARFCN, ALPHA_LONG, ALPHA_SHORT,
+                        Collections.emptyList(), null);
 
         Parcel p = Parcel.obtain();
         p.writeInt(CellInfo.TYPE_TDSCDMA);
@@ -145,7 +158,8 @@
         final String invalidMnc = "randomStuff";
         CellIdentityTdscdma ci =
                 new CellIdentityTdscdma(
-                        null, null, LAC, CID, CPID, UARFCN, ALPHA_LONG, ALPHA_SHORT);
+                        null, null, LAC, CID, CPID, UARFCN, ALPHA_LONG, ALPHA_SHORT,
+                        Collections.emptyList(), null);
 
         Parcel p = Parcel.obtain();
         p.writeInt(CellInfo.TYPE_TDSCDMA);
diff --git a/tests/telephonytests/src/com/android/internal/telephony/CellIdentityTest.java b/tests/telephonytests/src/com/android/internal/telephony/CellIdentityTest.java
index bb73f92..0ceed1c 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/CellIdentityTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/CellIdentityTest.java
@@ -19,10 +19,19 @@
 import android.os.Parcel;
 import android.telephony.CellIdentity;
 import android.telephony.CellIdentityCdma;
+import android.telephony.CellIdentityGsm;
 import android.telephony.CellIdentityLte;
+import android.telephony.CellIdentityNr;
+import android.telephony.CellIdentityTdscdma;
+import android.telephony.CellIdentityWcdma;
+import android.telephony.CellInfo;
 import android.test.AndroidTestCase;
 import android.test.suitebuilder.annotation.SmallTest;
 
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
 public class CellIdentityTest extends AndroidTestCase {
 
     // Cell identity ranges from 0 to 268435456.
@@ -33,6 +42,7 @@
     private static final int TAC = 65535;
     // Absolute RF Channel Number ranges from 0 to 262140.
     private static final int EARFCN = 262140;
+    private static final int[] BANDS = new int[] {1, 2};
     private static final int BANDWIDTH = 5000;  // kHz
     private static final int MCC = 120;
     private static final int MNC = 260;
@@ -40,6 +50,9 @@
     private static final String MNC_STR = "260";
     private static final String ALPHA_LONG = "long";
     private static final String ALPHA_SHORT = "short";
+    private static final int CPID = 127;
+    private static final int UARFCN = 16383;
+    private static final int PSC = 511;
 
     // Network Id ranges from 0 to 65535.
     private static final int NETWORK_ID  = 65535;
@@ -52,34 +65,75 @@
     // Latitude ranges from -1296000 to 1296000.
     private static final int LATITUDE = 1296000;
 
+    private static final String PLMN_INVALID_SHORT = "1234";
+    private static final String PLMN_INVALID_LONG = "1234567";
+    private static final String PLMN_INVALID_NON_NUM = "12a45b";
+    private static final String PLMN_VALID = "12345";
+
+    private static final int MAX_LAC = 65535;
+    private static final int MAX_CID = 65535;
+    private static final int MAX_ARFCN = 65535;
+    private static final int MAX_BSIC = 63;
+
+    @SmallTest
+    public void testConstructCellIdentityGsm() {
+        // Test values below zero (these must all be non-negative)
+        CellIdentityGsm gsm = new CellIdentityGsm(-1, -1, -1, -1, null, null, null, null,
+                Collections.emptyList());
+        assertEquals(CellInfo.UNAVAILABLE, gsm.getLac());
+        assertEquals(CellInfo.UNAVAILABLE, gsm.getCid());
+        assertEquals(CellInfo.UNAVAILABLE, gsm.getArfcn());
+        assertEquals(CellInfo.UNAVAILABLE, gsm.getBsic());
+
+        // Test max values of LAC, CID, ARFCN, and BSIC
+        gsm = new CellIdentityGsm(MAX_LAC, MAX_CID, MAX_ARFCN, MAX_BSIC, null, null, null, null,
+                Collections.emptyList());
+        assertEquals(MAX_LAC, gsm.getLac());
+        assertEquals(MAX_CID, gsm.getCid());
+        assertEquals(MAX_ARFCN, gsm.getArfcn());
+        assertEquals(MAX_BSIC, gsm.getBsic());
+
+        // Test max values + 1 of LAC, CID, ARFCN, and BSIC
+        gsm = new CellIdentityGsm(
+                MAX_LAC + 1, MAX_CID + 1, MAX_ARFCN + 1, MAX_BSIC + 1, null, null, null, null,
+                Collections.emptyList());
+        assertEquals(CellInfo.UNAVAILABLE, gsm.getLac());
+        assertEquals(CellInfo.UNAVAILABLE, gsm.getCid());
+        assertEquals(CellInfo.UNAVAILABLE, gsm.getArfcn());
+        assertEquals(CellInfo.UNAVAILABLE, gsm.getBsic());
+    }
+
+
     @SmallTest
     public void testEquals() {
         CellIdentity ciA = new CellIdentityLte(
-                CI, PCI, TAC, EARFCN, BANDWIDTH, MCC_STR, MNC_STR, ALPHA_LONG, ALPHA_SHORT);
+                CI, PCI, TAC, EARFCN, BANDS, BANDWIDTH, MCC_STR, MNC_STR, ALPHA_LONG, ALPHA_SHORT,
+                Collections.emptyList(), null);
         CellIdentity ciB = new CellIdentityLte(
-                CI, PCI, TAC, EARFCN, BANDWIDTH, MCC_STR, MNC_STR, ALPHA_LONG, ALPHA_SHORT);
+                CI, PCI, TAC, EARFCN, BANDS, BANDWIDTH, MCC_STR, MNC_STR, ALPHA_LONG, ALPHA_SHORT,
+                Collections.emptyList(), null);
 
         assertTrue(ciA.equals(ciB));
 
-        ciA = new CellIdentityLte(CI, PCI, TAC, EARFCN, BANDWIDTH, null, null, ALPHA_LONG,
-                ALPHA_SHORT);
-        ciB = new CellIdentityLte(CI, PCI, TAC, EARFCN, BANDWIDTH, null, null, ALPHA_LONG,
-                ALPHA_SHORT);
+        ciA = new CellIdentityLte(CI, PCI, TAC, EARFCN, BANDS, BANDWIDTH, null, null, ALPHA_LONG,
+                ALPHA_SHORT, Collections.emptyList(), null);
+        ciB = new CellIdentityLte(CI, PCI, TAC, EARFCN, BANDS, BANDWIDTH, null, null, ALPHA_LONG,
+                ALPHA_SHORT, Collections.emptyList(), null);
 
         assertTrue(ciA.equals(ciB));
 
-        ciA = new CellIdentityLte(CI, PCI, TAC, EARFCN, BANDWIDTH, MCC_STR, null, ALPHA_LONG,
-                ALPHA_SHORT);
-        ciB = new CellIdentityLte(CI, PCI, TAC, EARFCN, BANDWIDTH, null, null, ALPHA_LONG,
-                ALPHA_SHORT);
+        ciA = new CellIdentityLte(CI, PCI, TAC, EARFCN, BANDS, BANDWIDTH, MCC_STR, null,
+                ALPHA_LONG, ALPHA_SHORT, Collections.emptyList(), null);
+        ciB = new CellIdentityLte(CI, PCI, TAC, EARFCN, BANDS, BANDWIDTH, null, null, ALPHA_LONG,
+                ALPHA_SHORT, Collections.emptyList(), null);
 
         assertFalse(ciA.equals(ciB));
     }
 
     @SmallTest
     public void testParcel() {
-        CellIdentity ci = new CellIdentityLte(CI, PCI, TAC, EARFCN, BANDWIDTH, MCC_STR, MNC_STR,
-                ALPHA_LONG, ALPHA_SHORT);
+        CellIdentity ci = new CellIdentityLte(CI, PCI, TAC, EARFCN, BANDS, BANDWIDTH, MCC_STR,
+                MNC_STR, ALPHA_LONG, ALPHA_SHORT, Collections.emptyList(), null);
 
         Parcel p = Parcel.obtain();
         ci.writeToParcel(p, 0);
@@ -98,4 +152,84 @@
         newCi = CellIdentity.CREATOR.createFromParcel(p);
         assertEquals(ci, newCi);
     }
+
+    @SmallTest
+    public void testIsValidPlmn() {
+        assertTrue(CellIdentity.isValidPlmn(PLMN_VALID));
+    }
+
+    @SmallTest
+    public void testIsValidPlmnInvalidPlmns() {
+        assertFalse(CellIdentity.isValidPlmn(PLMN_INVALID_SHORT));
+        assertFalse(CellIdentity.isValidPlmn(PLMN_INVALID_LONG));
+        assertFalse(CellIdentity.isValidPlmn(PLMN_INVALID_NON_NUM));
+    }
+
+    @SmallTest
+    public void testIsSameCell() {
+        int curCi = 268435455;
+        CellIdentity ciA = new CellIdentityLte(
+                curCi, PCI, TAC, EARFCN, BANDS, BANDWIDTH, MCC_STR, MNC_STR, ALPHA_LONG,
+                ALPHA_SHORT, Collections.emptyList(), null);
+        CellIdentity ciB = new CellIdentityLte(
+                curCi, PCI, TAC, EARFCN, BANDS, BANDWIDTH, MCC_STR, MNC_STR, ALPHA_LONG,
+                ALPHA_SHORT, Collections.emptyList(), null);
+        CellIdentity ciC = new CellIdentityLte(
+                curCi, PCI, TAC, EARFCN, BANDS, BANDWIDTH, null, MNC_STR, ALPHA_LONG, ALPHA_SHORT,
+                Collections.emptyList(), null);
+        CellIdentity ciD = new CellIdentityLte(
+                -1, PCI, TAC, EARFCN, BANDS, BANDWIDTH, MCC_STR, MNC_STR, ALPHA_LONG, ALPHA_SHORT,
+                Collections.emptyList(), null);
+        assertTrue(ciA.isSameCell(ciB));
+        assertFalse(ciA.isSameCell(null));
+        assertFalse(ciA.isSameCell(ciC));
+        assertFalse(ciA.isSameCell(ciD));
+
+        CellIdentityNr cellIdentityNr =
+                new CellIdentityNr(PCI, TAC, EARFCN, BANDS, MCC_STR, MNC_STR, curCi, ALPHA_LONG,
+                ALPHA_SHORT, Collections.emptyList());
+        assertFalse(ciA.isSameCell(cellIdentityNr));
+    }
+
+    @SmallTest
+    public void testGetMccMncString() {
+        List<CellIdentity> identities = new ArrayList<>(5);
+
+        CellIdentityGsm gsm = new CellIdentityGsm(MAX_LAC, MAX_CID, MAX_ARFCN, MAX_BSIC,
+                MCC_STR, MNC_STR, null, null, Collections.emptyList());
+        identities.add(gsm);
+
+        CellIdentityCdma cdma = new CellIdentityCdma(NETWORK_ID, SYSTEM_ID, BASESTATION_ID,
+                LONGITUDE, LATITUDE, ALPHA_LONG, ALPHA_SHORT);
+        identities.add(cdma);
+
+        CellIdentity lte = new CellIdentityLte(
+                CI, PCI, TAC, EARFCN, BANDS, BANDWIDTH, MCC_STR, MNC_STR, ALPHA_LONG, ALPHA_SHORT,
+                Collections.emptyList(), null);
+        identities.add(lte);
+
+        CellIdentityWcdma wcdma  = new CellIdentityWcdma(MAX_LAC, MAX_CID, PSC, UARFCN, MCC_STR,
+                MNC_STR, ALPHA_LONG, ALPHA_SHORT, Collections.emptyList(), null);
+        identities.add(wcdma);
+
+        CellIdentityTdscdma tdscdma = new CellIdentityTdscdma(MCC_STR, MNC_STR, MAX_LAC, MAX_CID,
+                CPID, UARFCN, ALPHA_LONG, ALPHA_SHORT, Collections.emptyList(), null);
+        identities.add(tdscdma);
+
+        CellIdentityNr nr = new CellIdentityNr(PCI, TAC, EARFCN, BANDS, MCC_STR, MNC_STR, CI,
+                ALPHA_LONG, ALPHA_SHORT, Collections.emptyList());
+        identities.add(nr);
+
+        for (CellIdentity identity : identities) {
+            final String mccStr = identity.getMccString();
+            final String mncStr = identity.getMncString();
+            if (identity instanceof CellIdentityCdma) {
+                assertNull(mccStr);
+                assertNull(mncStr);
+            } else {
+                assertEquals(MCC_STR, mccStr);
+                assertEquals(MNC_STR, mncStr);
+            }
+        }
+    }
 }
diff --git a/tests/telephonytests/src/com/android/internal/telephony/CellIdentityWcdmaTest.java b/tests/telephonytests/src/com/android/internal/telephony/CellIdentityWcdmaTest.java
index c978c28..1e472fb 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/CellIdentityWcdmaTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/CellIdentityWcdmaTest.java
@@ -21,9 +21,12 @@
 import android.test.AndroidTestCase;
 import android.test.suitebuilder.annotation.SmallTest;
 
+import java.util.Collections;
+
 /** Unit tests for {@link CellIdentityWcdma}. */
 
 public class CellIdentityWcdmaTest extends AndroidTestCase {
+    private static final String LOG_TAG = "CellIdentityWcdmaTest";
 
     // Location Area Code ranges from 0 to 65535.
     private static final int LAC = 65535;
@@ -44,7 +47,7 @@
     public void testDefaultConstructor() {
         CellIdentityWcdma ci =
                 new CellIdentityWcdma(LAC, CID, PSC, UARFCN, MCC_STR, MNC_STR,
-                        ALPHA_LONG, ALPHA_SHORT);
+                        ALPHA_LONG, ALPHA_SHORT, Collections.emptyList(), null);
 
         assertEquals(LAC, ci.getLac());
         assertEquals(CID, ci.getCid());
@@ -65,13 +68,17 @@
         final String mncWithThreeDigit = "061";
         CellIdentityWcdma ci =
                 new CellIdentityWcdma(LAC, CID, PSC, UARFCN, MCC_STR, mncWithThreeDigit,
-                        ALPHA_LONG, ALPHA_SHORT);
+                        ALPHA_LONG, ALPHA_SHORT, Collections.emptyList(), null);
 
         assertEquals(MCC, ci.getMcc());
         assertEquals(61, ci.getMnc());
         assertEquals(MCC_STR, ci.getMccString());
         assertEquals(mncWithThreeDigit, ci.getMncString());
         assertEquals(MCC_STR + mncWithThreeDigit, ci.getMobileNetworkOperator());
+
+        String globalCi = MCC_STR + mncWithThreeDigit + Integer.toString(LAC, 16)
+                + Integer.toString(CID, 16);
+        assertEquals(globalCi, ci.getGlobalCellId());
     }
 
     @SmallTest
@@ -79,7 +86,7 @@
         final String mncWithTwoDigit = "61";
         CellIdentityWcdma ci =
                 new CellIdentityWcdma(LAC, CID, PSC, UARFCN, MCC_STR, mncWithTwoDigit,
-                        ALPHA_LONG, ALPHA_SHORT);
+                        ALPHA_LONG, ALPHA_SHORT, Collections.emptyList(), null);
 
         assertEquals(MCC, ci.getMcc());
         assertEquals(61, ci.getMnc());
@@ -92,7 +99,8 @@
     public void testConstructorWithEmptyMccMnc() {
         final String integerMaxValue = String.valueOf(Integer.MAX_VALUE);
         CellIdentityWcdma ci =
-                new CellIdentityWcdma(LAC, CID, PSC, UARFCN, null, null, ALPHA_LONG, ALPHA_SHORT);
+                new CellIdentityWcdma(LAC, CID, PSC, UARFCN, null, null, ALPHA_LONG, ALPHA_SHORT,
+                        Collections.emptyList(), null);
 
         assertEquals(Integer.MAX_VALUE, ci.getMcc());
         assertEquals(Integer.MAX_VALUE, ci.getMnc());
@@ -100,7 +108,8 @@
         assertNull(ci.getMncString());
         assertNull(ci.getMobileNetworkOperator());
 
-        ci = new CellIdentityWcdma(LAC, CID, PSC, UARFCN, MCC_STR, null, ALPHA_LONG, ALPHA_SHORT);
+        ci = new CellIdentityWcdma(LAC, CID, PSC, UARFCN, MCC_STR, null, ALPHA_LONG, ALPHA_SHORT,
+                Collections.emptyList(), null);
 
         assertEquals(MCC, ci.getMcc());
         assertEquals(Integer.MAX_VALUE, ci.getMnc());
@@ -108,7 +117,8 @@
         assertNull(ci.getMncString());
         assertNull(ci.getMobileNetworkOperator());
 
-        ci = new CellIdentityWcdma(LAC, CID, PSC, UARFCN, null, MNC_STR, ALPHA_LONG, ALPHA_SHORT);
+        ci = new CellIdentityWcdma(LAC, CID, PSC, UARFCN, null, MNC_STR, ALPHA_LONG, ALPHA_SHORT,
+                Collections.emptyList(), null);
 
         assertEquals(MNC, ci.getMnc());
         assertEquals(Integer.MAX_VALUE, ci.getMcc());
@@ -116,7 +126,8 @@
         assertNull(ci.getMccString());
         assertNull(ci.getMobileNetworkOperator());
 
-        ci = new CellIdentityWcdma(LAC, CID, PSC, UARFCN, "", "", ALPHA_LONG, ALPHA_SHORT);
+        ci = new CellIdentityWcdma(LAC, CID, PSC, UARFCN, "", "", ALPHA_LONG, ALPHA_SHORT,
+                Collections.emptyList(), null);
 
         assertEquals(Integer.MAX_VALUE, ci.getMcc());
         assertEquals(Integer.MAX_VALUE, ci.getMnc());
@@ -128,19 +139,25 @@
     @SmallTest
     public void testEquals() {
         CellIdentityWcdma ciA = new CellIdentityWcdma(
-                LAC, CID, PSC, UARFCN, MCC_STR, MNC_STR, ALPHA_LONG, ALPHA_SHORT);
+                LAC, CID, PSC, UARFCN, MCC_STR, MNC_STR, ALPHA_LONG, ALPHA_SHORT,
+                Collections.emptyList(), null);
         CellIdentityWcdma ciB = new CellIdentityWcdma(
-                LAC, CID, PSC, UARFCN, MCC_STR, MNC_STR, ALPHA_LONG, ALPHA_SHORT);
+                LAC, CID, PSC, UARFCN, MCC_STR, MNC_STR, ALPHA_LONG, ALPHA_SHORT,
+                Collections.emptyList(), null);
 
         assertTrue(ciA.equals(ciB));
 
-        ciA = new CellIdentityWcdma(LAC, CID, PSC, UARFCN, null, null, ALPHA_LONG, ALPHA_SHORT);
-        ciB = new CellIdentityWcdma(LAC, CID, PSC, UARFCN, null, null, ALPHA_LONG, ALPHA_SHORT);
+        ciA = new CellIdentityWcdma(LAC, CID, PSC, UARFCN, null, null, ALPHA_LONG, ALPHA_SHORT,
+                Collections.emptyList(), null);
+        ciB = new CellIdentityWcdma(LAC, CID, PSC, UARFCN, null, null, ALPHA_LONG, ALPHA_SHORT,
+                Collections.emptyList(), null);
 
         assertTrue(ciA.equals(ciB));
 
-        ciA = new CellIdentityWcdma(LAC, CID, PSC, UARFCN, MCC_STR, null, ALPHA_LONG, ALPHA_SHORT);
-        ciB = new CellIdentityWcdma(LAC, CID, PSC, UARFCN, null, null, ALPHA_LONG, ALPHA_SHORT);
+        ciA = new CellIdentityWcdma(LAC, CID, PSC, UARFCN, MCC_STR, null, ALPHA_LONG, ALPHA_SHORT,
+                Collections.emptyList(), null);
+        ciB = new CellIdentityWcdma(LAC, CID, PSC, UARFCN, null, null, ALPHA_LONG, ALPHA_SHORT,
+                Collections.emptyList(), null);
 
         assertFalse(ciA.equals(ciB));
     }
@@ -149,7 +166,7 @@
     public void testParcel() {
         CellIdentityWcdma ci =
                 new CellIdentityWcdma(LAC, CID, PSC, UARFCN, MCC_STR, MNC_STR,
-                        ALPHA_LONG, ALPHA_SHORT);
+                        ALPHA_LONG, ALPHA_SHORT, Collections.emptyList(), null);
 
         Parcel p = Parcel.obtain();
         ci.writeToParcel(p, 0);
@@ -162,7 +179,8 @@
     @SmallTest
     public void testParcelWithUnknowMccMnc() {
         CellIdentityWcdma ci =
-                new CellIdentityWcdma(LAC, CID, PSC, UARFCN, null, null, ALPHA_LONG, ALPHA_SHORT);
+                new CellIdentityWcdma(LAC, CID, PSC, UARFCN, null, null, ALPHA_LONG, ALPHA_SHORT,
+                        Collections.emptyList(), null);
 
         Parcel p = Parcel.obtain();
         ci.writeToParcel(p, 0);
@@ -177,7 +195,8 @@
         final String invalidMcc = "randomStuff";
         final String invalidMnc = "randomStuff";
         CellIdentityWcdma ci =
-                new CellIdentityWcdma(LAC, CID, PSC, UARFCN, null, null, ALPHA_LONG, ALPHA_SHORT);
+                new CellIdentityWcdma(LAC, CID, PSC, UARFCN, null, null, ALPHA_LONG, ALPHA_SHORT,
+                        Collections.emptyList(), null);
 
         Parcel p = Parcel.obtain();
         ci.writeToParcel(p, 0);
diff --git a/tests/telephonytests/src/com/android/internal/telephony/CellSignalStrengthNrTest.java b/tests/telephonytests/src/com/android/internal/telephony/CellSignalStrengthNrTest.java
index 074e9fc..a2e765e 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/CellSignalStrengthNrTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/CellSignalStrengthNrTest.java
@@ -18,7 +18,9 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
+import android.hardware.radio.V1_4.NrSignalStrength;
 import android.os.Parcel;
+import android.telephony.CellInfo;
 import android.telephony.CellSignalStrength;
 import android.telephony.CellSignalStrengthNr;
 import android.test.AndroidTestCase;
@@ -34,6 +36,7 @@
     private static final int ANOTHER_CSIRSRP = -111;
     private static final int ANOTHER_CSIRSRQ = -12;
     private static final int INVALID_CSIRSRP = Integer.MAX_VALUE;
+    private static final int INVALID_SSRSRP = Integer.MAX_VALUE;
     private static final int CSISINR = 18;
     private static final int SSRSRP = -112;
     private static final int SSRSRQ = -13;
@@ -52,6 +55,51 @@
         assertThat(css.getSsRsrp()).isEqualTo(SSRSRP);
         assertThat(css.getSsRsrq()).isEqualTo(SSRSRQ);
         assertThat(css.getSsSinr()).isEqualTo(SSSINR);
+        assertThat(css.getDbm()).isEqualTo(SSRSRP);
+    }
+
+    @Test
+    public void testGetMethodWithHal() {
+        // GIVEN an instance of NrSignalStrength with some positive values
+        NrSignalStrength nrSignalStrength = new NrSignalStrength();
+        nrSignalStrength.csiRsrp = -CSIRSRP;
+        nrSignalStrength.csiRsrq = -CSIRSRQ;
+        nrSignalStrength.csiSinr = CSISINR;
+        nrSignalStrength.ssRsrp = -SSRSRP;
+        nrSignalStrength.ssRsrq = -SSRSRQ;
+        nrSignalStrength.ssSinr = SSSINR;
+
+        // THEN the get method should return the correct value
+        CellSignalStrengthNr css = new CellSignalStrengthNr(nrSignalStrength);
+        assertThat(css.getCsiRsrp()).isEqualTo(CSIRSRP);
+        assertThat(css.getCsiRsrq()).isEqualTo(CSIRSRQ);
+        assertThat(css.getCsiSinr()).isEqualTo(CSISINR);
+        assertThat(css.getSsRsrp()).isEqualTo(SSRSRP);
+        assertThat(css.getSsRsrq()).isEqualTo(SSRSRQ);
+        assertThat(css.getSsSinr()).isEqualTo(SSSINR);
+        assertThat(css.getDbm()).isEqualTo(SSRSRP);
+    }
+
+    @Test
+    public void testUnavailableValueWithHal() {
+        // GIVEN an instance of NrSignalStrength
+        NrSignalStrength nrSignalStrength = new NrSignalStrength();
+        nrSignalStrength.csiRsrp = CellInfo.UNAVAILABLE;
+        nrSignalStrength.csiRsrq = CellInfo.UNAVAILABLE;
+        nrSignalStrength.csiSinr = CellInfo.UNAVAILABLE;
+        nrSignalStrength.ssRsrp = CellInfo.UNAVAILABLE;
+        nrSignalStrength.ssRsrq = CellInfo.UNAVAILABLE;
+        nrSignalStrength.ssSinr = CellInfo.UNAVAILABLE;
+
+        // THEN the get method should return unavailable value
+        CellSignalStrengthNr css = new CellSignalStrengthNr(nrSignalStrength);
+        assertThat(css.getCsiRsrp()).isEqualTo(CellInfo.UNAVAILABLE);
+        assertThat(css.getCsiRsrq()).isEqualTo(CellInfo.UNAVAILABLE);
+        assertThat(css.getCsiSinr()).isEqualTo(CellInfo.UNAVAILABLE);
+        assertThat(css.getSsRsrp()).isEqualTo(CellInfo.UNAVAILABLE);
+        assertThat(css.getSsRsrq()).isEqualTo(CellInfo.UNAVAILABLE);
+        assertThat(css.getSsSinr()).isEqualTo(CellInfo.UNAVAILABLE);
+        assertThat(css.getDbm()).isEqualTo(CellInfo.UNAVAILABLE);
     }
 
     @Test
@@ -93,7 +141,7 @@
     public void testAsuLevel_invalidValue() {
         // GIVEN an instance of CellSignalStrengthNr with invalid csirsrp
         CellSignalStrengthNr css = new CellSignalStrengthNr(
-                INVALID_CSIRSRP, CSIRSRQ, CSISINR, SSRSRP, SSRSRQ, SSSINR);
+                CSIRSRP, CSIRSRQ, CSISINR, INVALID_SSRSRP, SSRSRQ, SSSINR);
 
         // THEN the asu level is unknown
         assertThat(css.getAsuLevel()).isEqualTo(CellSignalStrengthNr.UNKNOWN_ASU_LEVEL);
@@ -101,17 +149,15 @@
 
     @Test
     public void testSignalLevel_validValue() {
-        for (int csiRsrp = -140; csiRsrp <= -44; csiRsrp++) {
+        for (int ssRsrp = -140; ssRsrp <= -44; ssRsrp++) {
             // GIVEN an instance of CellSignalStrengthNr with valid csirsrp
             CellSignalStrengthNr css = new CellSignalStrengthNr(
-                    csiRsrp, CSIRSRQ, CSISINR, SSRSRP, SSRSRQ, SSSINR);
+                    CSIRSRP, CSIRSRQ, CSISINR, ssRsrp, SSRSRQ, SSSINR);
 
             // THEN the signal level is valid
-            assertThat(css.getLevel()).isAnyOf(
-                    CellSignalStrength.SIGNAL_STRENGTH_GREAT,
-                    CellSignalStrength.SIGNAL_STRENGTH_GOOD,
-                    CellSignalStrength.SIGNAL_STRENGTH_MODERATE,
-                    CellSignalStrength.SIGNAL_STRENGTH_POOR);
+            assertThat(css.getLevel()).isIn(Range.range(
+                    CellSignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN, BoundType.CLOSED,
+                    CellSignalStrength.SIGNAL_STRENGTH_GREAT, BoundType.CLOSED));
         }
     }
 
@@ -119,7 +165,7 @@
     public void testSignalLevel_invalidValue() {
         // GIVEN an instance of CellSignalStrengthNr with invalid csirsrp
         CellSignalStrengthNr css = new CellSignalStrengthNr(
-                INVALID_CSIRSRP, CSIRSRQ, CSISINR, SSRSRP, SSRSRQ, SSSINR);
+                CSIRSRP, CSIRSRQ, CSISINR, INVALID_SSRSRP, SSRSRQ, SSSINR);
 
         // THEN the signal level is unknown
         assertThat(css.getLevel()).isEqualTo(CellSignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN);
diff --git a/tests/telephonytests/src/com/android/internal/telephony/CellularNetworkServiceTest.java b/tests/telephonytests/src/com/android/internal/telephony/CellularNetworkServiceTest.java
index 9cc0f09..e2bacc2 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/CellularNetworkServiceTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/CellularNetworkServiceTest.java
@@ -16,12 +16,10 @@
 
 package com.android.internal.telephony;
 
-import static com.android.internal.telephony.TelephonyTestUtils.waitForMs;
-
 import static org.junit.Assert.assertTrue;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.eq;
-import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.timeout;
 import static org.mockito.Mockito.verify;
 
 import android.content.IntentFilter;
@@ -91,6 +89,9 @@
 
     @After
     public void tearDown() throws Exception {
+        if (mCellularNetworkService != null) {
+            mCellularNetworkService.onDestroy();
+        }
         super.tearDown();
     }
 
@@ -134,16 +135,14 @@
             assertTrue(false);
         }
 
-        waitForMs(1000);
-
         NetworkRegistrationInfo expectedState = new NetworkRegistrationInfo(
                 domain, AccessNetworkConstants.TRANSPORT_TYPE_WWAN, voiceRegState,
                 ServiceState.rilRadioTechnologyToNetworkType(voiceRadioTech), reasonForDenial,
-                false, availableServices, null, cssSupported,
+                false, availableServices, null, "", cssSupported,
                 roamingIndicator, systemIsInPrl, defaultRoamingIndicator);
 
         try {
-            verify(mCallback, times(1)).onRequestNetworkRegistrationInfoComplete(
+            verify(mCallback, timeout(1000).times(1)).onRequestNetworkRegistrationInfoComplete(
                     eq(NetworkServiceCallback.RESULT_SUCCESS), eq(expectedState));
         } catch (RemoteException e) {
             assertTrue(false);
@@ -157,8 +156,6 @@
             assertTrue(false);
         }
 
-        waitForMs(1000);
-
         LteVopsSupportInfo lteVopsSupportInfo =
                 new LteVopsSupportInfo(LteVopsSupportInfo.LTE_STATUS_NOT_AVAILABLE,
                         LteVopsSupportInfo.LTE_STATUS_NOT_AVAILABLE);
@@ -166,11 +163,11 @@
         expectedState = new NetworkRegistrationInfo(
                 domain, AccessNetworkConstants.TRANSPORT_TYPE_WWAN, voiceRegState,
                 ServiceState.rilRadioTechnologyToNetworkType(voiceRadioTech), reasonForDenial,
-                false, availableServices, null, maxDataCalls, false, false, false,
+                false, availableServices, null, "", maxDataCalls, false, false, false,
                 lteVopsSupportInfo, false);
 
         try {
-            verify(mCallback, times(1)).onRequestNetworkRegistrationInfoComplete(
+            verify(mCallback, timeout(1000).times(1)).onRequestNetworkRegistrationInfoComplete(
                     eq(NetworkServiceCallback.RESULT_SUCCESS), eq(expectedState));
         } catch (RemoteException e) {
             assertTrue(false);
diff --git a/tests/telephonytests/src/com/android/internal/telephony/CellularNetworkValidatorTest.java b/tests/telephonytests/src/com/android/internal/telephony/CellularNetworkValidatorTest.java
index 9723150..0601384 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/CellularNetworkValidatorTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/CellularNetworkValidatorTest.java
@@ -16,42 +16,54 @@
 
 package com.android.internal.telephony;
 
+import static com.android.internal.telephony.TelephonyTestUtils.waitForMs;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.clearInvocations;
 import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.verify;
 
+import android.content.Context;
 import android.net.Network;
 import android.net.NetworkCapabilities;
 import android.net.NetworkRequest;
-import android.os.HandlerThread;
+import android.net.TelephonyNetworkSpecifier;
+import android.os.PersistableBundle;
+import android.telephony.CarrierConfigManager;
+import android.telephony.CellIdentityLte;
+import android.telephony.NetworkRegistrationInfo;
 import android.telephony.PhoneCapability;
 import android.telephony.SubscriptionManager;
+import android.telephony.TelephonyManager;
 import android.test.suitebuilder.annotation.SmallTest;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
 
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
 
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper
 public class CellularNetworkValidatorTest extends TelephonyTest {
-    private boolean mValidated = false;
     private CellularNetworkValidator mValidatorUT;
-    private int mValidatedSubId = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
     private static final PhoneCapability CAPABILITY_WITH_VALIDATION_SUPPORTED =
             new PhoneCapability(1, 1, 0, null, true);
     private static final PhoneCapability CAPABILITY_WITHOUT_VALIDATION_SUPPORTED =
             new PhoneCapability(1, 1, 0, null, false);
-    private HandlerThread mHandlerThread;
-
-    CellularNetworkValidator.ValidationCallback mCallback = (validated, subId) -> {
-        mValidated = validated;
-        mValidatedSubId = subId;
-    };
+    private final CellIdentityLte mCellIdentityLte1 = new CellIdentityLte(123, 456, 0, 0, 111);
+    private final CellIdentityLte mCellIdentityLte2 = new CellIdentityLte(321, 654, 0, 0, 222);
+    @Mock
+    CellularNetworkValidator.ValidationCallback mCallback;
 
     @Before
     public void setUp() throws Exception {
@@ -59,22 +71,15 @@
 
         doReturn(CAPABILITY_WITH_VALIDATION_SUPPORTED).when(mPhoneConfigurationManager)
                 .getCurrentPhoneCapability();
+        mValidatorUT = new CellularNetworkValidator(mContext);
         doReturn(true).when(mSubscriptionController).isActiveSubId(anyInt());
-
-        mHandlerThread = new HandlerThread("PhoneSwitcherTestThread") {
-            @Override
-            public void onLooperPrepared() {
-                mValidatorUT = new CellularNetworkValidator(mContext);
-            }
-        };
-
-        mHandlerThread.start();
-        waitABit();
-
+        processAllMessages();
+        setCacheTtlInCarrierConfig(5000);
     }
 
     @After
     public void tearDown() throws Exception {
+        mValidatorUT.stopValidation();
         super.tearDown();
     }
 
@@ -83,7 +88,7 @@
      */
     @Test
     @SmallTest
-    public void testValidationSupported() throws Exception {
+    public void testValidationSupported() {
         doReturn(CAPABILITY_WITH_VALIDATION_SUPPORTED).when(mPhoneConfigurationManager)
                 .getCurrentPhoneCapability();
         assertTrue(mValidatorUT.isValidationFeatureSupported());
@@ -98,137 +103,388 @@
      */
     @Test
     @SmallTest
-    public void testValidateSuccess() throws Exception {
+    public void testValidateSuccess() {
         int subId = 1;
         int timeout = 1000;
-        NetworkRequest expectedRequest = new NetworkRequest.Builder()
-                .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
-                .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR)
-                .setNetworkSpecifier(String.valueOf(subId))
-                .build();
-
         mValidatorUT.validate(subId, timeout, true, mCallback);
-
-        assertTrue(mValidatorUT.isValidating());
-        assertEquals(subId, mValidatorUT.getSubIdInValidation());
-        verify(mConnectivityManager).requestNetwork(
-                eq(expectedRequest), eq(mValidatorUT.mNetworkCallback), any());
+        assertInValidation(subId);
 
         mValidatorUT.mNetworkCallback.onCapabilitiesChanged(null, new NetworkCapabilities()
                 .addCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED));
-
-        assertTrue(mValidated);
-        assertEquals(subId, mValidatedSubId);
-        verify(mConnectivityManager).unregisterNetworkCallback(eq(mValidatorUT.mNetworkCallback));
-        assertFalse(mValidatorUT.mHandler.hasCallbacks(mValidatorUT.mTimeoutCallback));
-        assertFalse(mValidatorUT.isValidating());
-        assertEquals(SubscriptionManager.INVALID_SUBSCRIPTION_ID,
-                mValidatorUT.getSubIdInValidation());
+        assertValidationResult(subId, true);
     }
 
-    /**
-     * Test that a single phone case results in our phone being active and the RIL called
-     */
     @Test
     @SmallTest
-    public void testValidateTimeout() throws Exception {
+    public void testValidateTimeout() {
         int subId = 1;
         int timeout = 100;
-        NetworkRequest expectedRequest = new NetworkRequest.Builder()
-                .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
-                .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR)
-                .setNetworkSpecifier(String.valueOf(subId))
-                .build();
-
         mValidatorUT.validate(subId, timeout, true, mCallback);
+        assertInValidation(subId);
 
-        assertTrue(mValidatorUT.isValidating());
-        assertEquals(subId, mValidatorUT.getSubIdInValidation());
-        verify(mConnectivityManager).requestNetwork(
-                eq(expectedRequest), eq(mValidatorUT.mNetworkCallback), any());
-
-        // Wait for timeout.
-        waitABit();
-
-        assertFalse(mValidated);
-        assertEquals(subId, mValidatedSubId);
-        verify(mConnectivityManager).unregisterNetworkCallback(eq(mValidatorUT.mNetworkCallback));
-        assertFalse(mValidatorUT.mHandler.hasCallbacks(mValidatorUT.mTimeoutCallback));
-        assertFalse(mValidatorUT.isValidating());
-        assertEquals(SubscriptionManager.INVALID_SUBSCRIPTION_ID,
-                mValidatorUT.getSubIdInValidation());
+        // Wait for timeout
+        moveTimeForward(timeout);
+        processAllMessages();
+        assertValidationResult(subId, false);
     }
 
-    /**
-     * Test that a single phone case results in our phone being active and the RIL called
-     */
     @Test
     @SmallTest
-    public void testValidateFailure() throws Exception {
+    public void testValidateFailure() {
         int subId = 1;
         int timeout = 100;
-        NetworkRequest expectedRequest = new NetworkRequest.Builder()
-                .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
-                .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR)
-                .setNetworkSpecifier(String.valueOf(subId))
-                .build();
-
         mValidatorUT.validate(subId, timeout, true, mCallback);
-
-        assertTrue(mValidatorUT.isValidating());
-        assertEquals(subId, mValidatorUT.getSubIdInValidation());
-        verify(mConnectivityManager).requestNetwork(
-                eq(expectedRequest), eq(mValidatorUT.mNetworkCallback), any());
-
+        assertInValidation(subId);
         mValidatorUT.mNetworkCallback.onUnavailable();
-
-        assertFalse(mValidated);
-        assertEquals(subId, mValidatedSubId);
-        verify(mConnectivityManager).unregisterNetworkCallback(eq(mValidatorUT.mNetworkCallback));
-        assertFalse(mValidatorUT.mHandler.hasCallbacks(mValidatorUT.mTimeoutCallback));
-        assertFalse(mValidatorUT.isValidating());
-        assertEquals(SubscriptionManager.INVALID_SUBSCRIPTION_ID,
-                mValidatorUT.getSubIdInValidation());
+        assertValidationResult(subId, false);
     }
 
-    /**
-     * Test that a single phone case results in our phone being active and the RIL called
-     */
     @Test
     @SmallTest
-    public void testNetworkAvailableNotValidated() throws Exception {
+    public void testNetworkAvailableNotValidated() {
         int subId = 1;
         int timeout = 100;
-        NetworkRequest expectedRequest = new NetworkRequest.Builder()
-                .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
-                .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR)
-                .setNetworkSpecifier(String.valueOf(subId))
-                .build();
-
         mValidatorUT.validate(subId, timeout, true, mCallback);
-
-        assertTrue(mValidatorUT.isValidating());
-        assertEquals(subId, mValidatorUT.getSubIdInValidation());
-        verify(mConnectivityManager).requestNetwork(
-                eq(expectedRequest), eq(mValidatorUT.mNetworkCallback), any());
+        assertInValidation(subId);
 
         mValidatorUT.mNetworkCallback.onAvailable(new Network(100));
-        // Wait for timeout.
-        waitABit();
+        assertInValidation(subId);
 
-        assertFalse(mValidated);
-        assertEquals(subId, mValidatedSubId);
+        // Wait for timeout
+        moveTimeForward(timeout);
+        processAllMessages();
+
+        assertValidationResult(subId, false);
+    }
+
+    @Test
+    @SmallTest
+    public void testSkipRecentlyValidatedNetwork() {
+        int subId = 1;
+        int timeout = 1000;
+        mNetworkRegistrationInfo = new NetworkRegistrationInfo.Builder()
+                .setAccessNetworkTechnology(TelephonyManager.NETWORK_TYPE_LTE)
+                .setRegistrationState(NetworkRegistrationInfo.REGISTRATION_STATE_HOME)
+                .setCellIdentity(mCellIdentityLte1)
+                .build();
+        doReturn(mNetworkRegistrationInfo).when(mServiceState).getNetworkRegistrationInfo(
+                anyInt(), anyInt());
+        testValidateSuccess();
+
+        resetStates();
+        mValidatorUT.validate(subId, timeout, true, mCallback);
+        assertInValidation(subId);
+
+        // As recently validated, onAvailable should trigger switch.
+        mValidatorUT.mNetworkCallback.onAvailable(new Network(100));
+        assertValidationResult(subId, true);
+    }
+
+    @Test
+    @SmallTest
+    public void testDoNotSkipIfValidationFailed() {
+        int subId = 1;
+        int timeout = 1000;
+        mNetworkRegistrationInfo = new NetworkRegistrationInfo.Builder()
+                .setAccessNetworkTechnology(TelephonyManager.NETWORK_TYPE_LTE)
+                .setRegistrationState(NetworkRegistrationInfo.REGISTRATION_STATE_HOME)
+                .setCellIdentity(mCellIdentityLte1)
+                .build();
+        doReturn(mNetworkRegistrationInfo).when(mServiceState).getNetworkRegistrationInfo(
+                anyInt(), anyInt());
+        testValidateFailure();
+
+        resetStates();
+        mValidatorUT.validate(subId, timeout, true, mCallback);
+        assertInValidation(subId);
+        // Last time validation fialed, onAvailable should NOT trigger switch.
+        mValidatorUT.mNetworkCallback.onAvailable(new Network(100));
+        assertInValidation(subId);
+    }
+
+    @Test
+    @SmallTest
+    public void testDoNotSkipIfCacheExpires() {
+        int subId = 1;
+        int timeout = 1000;
+        mNetworkRegistrationInfo = new NetworkRegistrationInfo.Builder()
+                .setAccessNetworkTechnology(TelephonyManager.NETWORK_TYPE_LTE)
+                .setRegistrationState(NetworkRegistrationInfo.REGISTRATION_STATE_HOME)
+                .setCellIdentity(mCellIdentityLte1)
+                .build();
+        doReturn(mNetworkRegistrationInfo).when(mServiceState).getNetworkRegistrationInfo(
+                anyInt(), anyInt());
+        testValidateSuccess();
+
+        // Mark mValidationCacheTtl to only 1 second.
+        setCacheTtlInCarrierConfig(1000);
+        waitForMs(1100);
+
+        resetStates();
+        mValidatorUT.validate(subId, timeout, true, mCallback);
+        assertInValidation(subId);
+
+        // Last time validation expired, onAvailable should NOT trigger switch.
+        mValidatorUT.mNetworkCallback.onAvailable(new Network(100));
+        assertInValidation(subId);
+    }
+
+    @Test
+    @SmallTest
+    public void testNetworkCachingOfMultipleSub() {
+        int timeout = 1000;
+        mNetworkRegistrationInfo = new NetworkRegistrationInfo.Builder()
+                .setAccessNetworkTechnology(TelephonyManager.NETWORK_TYPE_LTE)
+                .setRegistrationState(NetworkRegistrationInfo.REGISTRATION_STATE_HOME)
+                .setCellIdentity(mCellIdentityLte1)
+                .build();
+        doReturn(mNetworkRegistrationInfo).when(mServiceState).getNetworkRegistrationInfo(
+                anyInt(), anyInt());
+
+        assertNetworkRecentlyValidated(1, false);
+        assertNetworkRecentlyValidated(2, false);
+        assertNetworkRecentlyValidated(3, false);
+        // Validate sub 1, 2, and 3.
+        mValidatorUT.validate(1, timeout, true, mCallback);
+        mValidatorUT.mNetworkCallback.onCapabilitiesChanged(null, new NetworkCapabilities()
+                .addCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED));
+        assertValidationResult(1, true);
+        assertNetworkRecentlyValidated(1, true);
+        assertNetworkRecentlyValidated(2, false);
+        assertNetworkRecentlyValidated(3, false);
+        mValidatorUT.validate(2, timeout, true, mCallback);
+        mValidatorUT.mNetworkCallback.onCapabilitiesChanged(null, new NetworkCapabilities()
+                .addCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED));
+        assertValidationResult(2, true);
+        mValidatorUT.validate(3, timeout, true, mCallback);
+        mValidatorUT.mNetworkCallback.onCapabilitiesChanged(null, new NetworkCapabilities()
+                .addCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED));
+        assertValidationResult(3, true);
+        assertNetworkRecentlyValidated(1, true);
+        assertNetworkRecentlyValidated(2, true);
+        assertNetworkRecentlyValidated(3, true);
+
+        // When re-validating sub 3, onAvailable should trigger validation callback.
+        resetStates();
+        mValidatorUT.validate(3, timeout, true, mCallback);
+        mValidatorUT.mNetworkCallback.onAvailable(new Network(100));
+        assertValidationResult(3, true);
+        // Mark sub 2 validation failed. Should clear the network from cache.
+        resetStates();
+        mValidatorUT.validate(2, timeout, true, mCallback);
+        mValidatorUT.mNetworkCallback.onLost(new Network(100));
+        assertNetworkRecentlyValidated(2, false);
+
+        mNetworkRegistrationInfo = new NetworkRegistrationInfo.Builder()
+                .setAccessNetworkTechnology(TelephonyManager.NETWORK_TYPE_LTE)
+                .setRegistrationState(NetworkRegistrationInfo.REGISTRATION_STATE_HOME)
+                .setCellIdentity(mCellIdentityLte1)
+                .build();
+        doReturn(mNetworkRegistrationInfo).when(mServiceState).getNetworkRegistrationInfo(
+                anyInt(), anyInt());
+    }
+
+    @Test
+    @SmallTest
+    public void testNetworkCachingOfMultipleNetworks() {
+        int timeout = 1000;
+        mNetworkRegistrationInfo = new NetworkRegistrationInfo.Builder()
+                .setAccessNetworkTechnology(TelephonyManager.NETWORK_TYPE_LTE)
+                .setRegistrationState(NetworkRegistrationInfo.REGISTRATION_STATE_HOME)
+                .setCellIdentity(mCellIdentityLte1)
+                .build();
+        doReturn(mNetworkRegistrationInfo).when(mServiceState).getNetworkRegistrationInfo(
+                anyInt(), anyInt());
+
+        // Validate sub 1.
+        assertNetworkRecentlyValidated(1, false);
+        mValidatorUT.validate(1, timeout, true, mCallback);
+        mValidatorUT.mNetworkCallback.onCapabilitiesChanged(null, new NetworkCapabilities()
+                .addCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED));
+        assertNetworkRecentlyValidated(1, true);
+
+        // Change reg state to a different network.
+        mNetworkRegistrationInfo = new NetworkRegistrationInfo.Builder()
+                .setAccessNetworkTechnology(TelephonyManager.NETWORK_TYPE_LTE)
+                .setRegistrationState(NetworkRegistrationInfo.REGISTRATION_STATE_HOME)
+                .setCellIdentity(mCellIdentityLte2)
+                .build();
+        doReturn(mNetworkRegistrationInfo).when(mServiceState).getNetworkRegistrationInfo(
+                anyInt(), anyInt());
+
+        // Should NOT skip validation.
+        assertNetworkRecentlyValidated(1, false);
+    }
+
+    @Test
+    @SmallTest
+    public void testNetworkCachingOverflow() {
+        int timeout = 1000;
+        mNetworkRegistrationInfo = new NetworkRegistrationInfo.Builder()
+                .setAccessNetworkTechnology(TelephonyManager.NETWORK_TYPE_LTE)
+                .setRegistrationState(NetworkRegistrationInfo.REGISTRATION_STATE_HOME)
+                .setCellIdentity(mCellIdentityLte1)
+                .build();
+        doReturn(mNetworkRegistrationInfo).when(mServiceState).getNetworkRegistrationInfo(
+                anyInt(), anyInt());
+
+        for (int subId = 8; subId <= 100; subId++) {
+            mValidatorUT.validate(subId, timeout, true, mCallback);
+            mValidatorUT.mNetworkCallback.onCapabilitiesChanged(null, new NetworkCapabilities()
+                    .addCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED));
+            assertNetworkRecentlyValidated(subId, true);
+        }
+
+        // Last 10 subs are kept in cache.
+        for (int subId = 1; subId <= 90; subId++) {
+            assertNetworkRecentlyValidated(subId, false);
+        }
+        // Last 10 subs are kept in cache.
+        for (int subId = 91; subId <= 100; subId++) {
+            assertNetworkRecentlyValidated(subId, true);
+        }
+    }
+
+    @Test
+    @SmallTest
+    public void testOnNetworkAvailable() {
+        int subId = 1;
+        int timeout = 1000;
+        mValidatorUT.validate(subId, timeout, true, mCallback);
+        Network network = new Network(100);
+        mValidatorUT.mNetworkCallback.onAvailable(network);
+
+        assertInValidation(subId);
+        verify(mCallback).onNetworkAvailable(network, subId);
+    }
+
+    @Test
+    @SmallTest
+    public void testReleaseRequestAfterValidation_shouldReleaseImmediately() {
+        int subId = 1;
+        int timeout = 1000;
+        mValidatorUT.validate(subId, timeout, true, mCallback);
+        mValidatorUT.mNetworkCallback.onCapabilitiesChanged(null, new NetworkCapabilities()
+                .addCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED));
+        assertValidationResult(subId, true);
+    }
+
+    @Test
+    @SmallTest
+    public void testDoNotReleaseRequestAfterValidation_shouldReleaseLater() {
+        int subId = 1;
+        int timeout = 1000;
+        mValidatorUT.validate(subId, timeout, false, mCallback);
+        mValidatorUT.mNetworkCallback.onCapabilitiesChanged(null, new NetworkCapabilities()
+                .addCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED));
+        verify(mCallback).onValidationDone(true, subId);
+        assertInValidation(subId);
+        moveTimeForward(1000);
+        processAllMessages();
+        assertValidationResult(subId, true);
+    }
+
+    @Test
+    @SmallTest
+    public void testDoNotReleaseRequestAfterValidation_validationFails_shouldReleaseImmediately() {
+        int subId = 1;
+        int timeout = 1000;
+        mValidatorUT.validate(subId, timeout, false, mCallback);
+        mValidatorUT.mNetworkCallback.onLost(new Network(100));
+        assertValidationResult(subId, false);
+    }
+
+    @Test
+    @SmallTest
+    public void testDoNotReleaseRequestAfterValidation_timeout_shouldReleaseImmediately() {
+        int subId = 1;
+        int timeout = 1000;
+        mValidatorUT.validate(subId, timeout, false, mCallback);
+        assertInValidation(subId);
+        moveTimeForward(timeout);
+        processAllMessages();
+        assertValidationResult(subId, false);
+    }
+
+    @Test
+    @SmallTest
+    public void testDoNotReleaseRequestAfterValidation_BackToBackRequest() {
+        int subId1 = 1;
+        int timeout = 1000;
+        mValidatorUT.validate(subId1, timeout, false, mCallback);
+        mValidatorUT.mNetworkCallback.onCapabilitiesChanged(null, new NetworkCapabilities()
+                .addCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED));
+        verify(mCallback).onValidationDone(true, subId1);
+        assertInValidation(subId1);
+
+        resetStates();
+        int subId2 = 2;
+        mValidatorUT.validate(subId2, timeout, false, mCallback);
+        assertInValidation(subId2);
+        mValidatorUT.mNetworkCallback.onCapabilitiesChanged(null, new NetworkCapabilities()
+                .addCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED));
+        verify(mCallback).onValidationDone(true, subId2);
+        assertInValidation(subId2);
+        moveTimeForward(1000);
+        processAllMessages();
+        assertValidationResult(subId2, true);
+        // No callback should be triggered on subId1
+        verify(mCallback, never()).onValidationDone(anyBoolean(), eq(subId1));
+    }
+
+    private void assertNetworkRecentlyValidated(int subId, boolean shouldBeRecentlyValidated) {
+        // Start validation and send network available callback.
+        resetStates();
+        mValidatorUT.validate(subId, 1000, true, mCallback);
+        mValidatorUT.mNetworkCallback.onAvailable(new Network(1000));
+
+        if (shouldBeRecentlyValidated) {
+            assertValidationResult(subId, true);
+        } else {
+            assertInValidation(subId);
+        }
+
+        mValidatorUT.stopValidation();
+        resetStates();
+    }
+
+    private void assertValidationResult(int subId, boolean shouldPass) {
+        // Verify that validation is over.
         verify(mConnectivityManager).unregisterNetworkCallback(eq(mValidatorUT.mNetworkCallback));
-        assertFalse(mValidatorUT.mHandler.hasCallbacks(mValidatorUT.mTimeoutCallback));
+        assertFalse(mValidatorUT.mHandler.hasMessagesOrCallbacks());
         assertFalse(mValidatorUT.isValidating());
         assertEquals(SubscriptionManager.INVALID_SUBSCRIPTION_ID,
                 mValidatorUT.getSubIdInValidation());
+
+        // Verify result.
+        verify(mCallback).onValidationDone(shouldPass, subId);
     }
 
-    private void waitABit() {
-        try {
-            Thread.sleep(250);
-        } catch (Exception e) {
-        }
+    private void assertInValidation(int subId) {
+        assertEquals(subId, mValidatorUT.getSubIdInValidation());
+        assertTrue(mValidatorUT.mHandler.hasMessagesOrCallbacks());
+        assertEquals(subId, mValidatorUT.getSubIdInValidation());
+        NetworkRequest expectedRequest = new NetworkRequest.Builder()
+                .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
+                .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR)
+                .setNetworkSpecifier(new TelephonyNetworkSpecifier.Builder()
+                        .setSubscriptionId(subId).build())
+                .build();
+        verify(mConnectivityManager).requestNetwork(
+                eq(expectedRequest), eq(mValidatorUT.mNetworkCallback), any());
+        assertTrue(mValidatorUT.isValidating());
+    }
+
+    private void resetStates() {
+        clearInvocations(mConnectivityManager);
+        clearInvocations(mCallback);
+    }
+
+    private void setCacheTtlInCarrierConfig(long ttl) {
+        // Mark to skip validation in 5 seconds.
+        CarrierConfigManager carrierConfigManager = (CarrierConfigManager)
+                mContext.getSystemService(Context.CARRIER_CONFIG_SERVICE);
+        PersistableBundle bundle = carrierConfigManager.getConfigForSubId(anyInt());
+        bundle.putLong(CarrierConfigManager.KEY_DATA_SWITCH_VALIDATION_MIN_GAP_LONG, ttl);
     }
 }
diff --git a/tests/telephonytests/src/com/android/internal/telephony/ClientWakelockAccountantTest.java b/tests/telephonytests/src/com/android/internal/telephony/ClientWakelockAccountantTest.java
index f7132b8..d406996 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/ClientWakelockAccountantTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/ClientWakelockAccountantTest.java
@@ -16,12 +16,7 @@
 
 package com.android.internal.telephony;
 
-import static com.android.internal.telephony.TelephonyTestUtils.waitForMs;
-
-import android.os.Build;
-import android.util.Log;
-import android.telephony.Rlog;
-import android.telephony.TelephonyHistogram;
+import com.android.internal.telephony.util.TelephonyUtils;
 
 import junit.framework.Assert;
 import junit.framework.TestCase;
@@ -87,7 +82,7 @@
     }
 
     public void testStartAttributingWithZeroConcurrentRequests() throws Exception {
-        if(Build.IS_DEBUGGABLE) {
+        if (TelephonyUtils.IS_DEBUGGABLE) {
             try {
                 mClient.startAttributingWakelock(15, 25, 0, 100);
                 fail("Expecting an illegal argument Exception to be thrown");
diff --git a/tests/telephonytests/src/com/android/internal/telephony/ClosedSubscriberGroupInfoTest.java b/tests/telephonytests/src/com/android/internal/telephony/ClosedSubscriberGroupInfoTest.java
new file mode 100644
index 0000000..026f01d
--- /dev/null
+++ b/tests/telephonytests/src/com/android/internal/telephony/ClosedSubscriberGroupInfoTest.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright 2020 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;
+
+import android.os.Parcel;
+import android.telephony.ClosedSubscriberGroupInfo;
+import android.test.AndroidTestCase;
+
+import org.junit.Test;
+
+public class ClosedSubscriberGroupInfoTest extends AndroidTestCase {
+
+    private static final boolean CSG_INDICATOR = true;
+    private static final String HOME_NODEB_NAME = "MyHomeNodeB";
+    private static final String HOME_NODEB_NAME_EMPTY = "";
+    private static final int CSG_IDENTITY = 1234;
+
+    @Test
+    public void testConstructorNormalInput() {
+        ClosedSubscriberGroupInfo csgInfo = new ClosedSubscriberGroupInfo(
+                CSG_INDICATOR, HOME_NODEB_NAME, CSG_IDENTITY);
+
+        assertEquals(CSG_INDICATOR, csgInfo.getCsgIndicator());
+        assertEquals(HOME_NODEB_NAME, csgInfo.getHomeNodebName());
+        assertEquals(CSG_IDENTITY, csgInfo.getCsgIdentity());
+    }
+
+    @Test
+    public void testConstructorHomeNodebNameIsNull() {
+        ClosedSubscriberGroupInfo csgInfo = new ClosedSubscriberGroupInfo(
+                CSG_INDICATOR, null, CSG_IDENTITY);
+
+        assertEquals(CSG_INDICATOR, csgInfo.getCsgIndicator());
+        assertEquals(HOME_NODEB_NAME_EMPTY, csgInfo.getHomeNodebName());
+        assertEquals(CSG_IDENTITY, csgInfo.getCsgIdentity());
+    }
+
+    @Test
+    public void testEqualsSameValues() {
+        ClosedSubscriberGroupInfo csgInfo1 = new ClosedSubscriberGroupInfo(
+                CSG_INDICATOR, HOME_NODEB_NAME, CSG_IDENTITY);
+        // homeNodebName is copied, same value with diff reference
+        ClosedSubscriberGroupInfo csgInfo2 = new ClosedSubscriberGroupInfo(
+                CSG_INDICATOR, new String(HOME_NODEB_NAME), CSG_IDENTITY);
+
+        assertEquals(csgInfo1, csgInfo2);
+    }
+
+    @Test
+    public void testEqualsHomeNodebNameIsNull() {
+        ClosedSubscriberGroupInfo csgInfo1 = new ClosedSubscriberGroupInfo(
+                CSG_INDICATOR, null /* homeNodebName */, CSG_IDENTITY);
+        ClosedSubscriberGroupInfo csgInfo2 = new ClosedSubscriberGroupInfo(
+                CSG_INDICATOR, ""/* homeNodebName */, CSG_IDENTITY);
+
+        assertEquals(csgInfo1, csgInfo2);
+    }
+
+    @Test
+    public void testParcel() {
+        ClosedSubscriberGroupInfo csgInfo = new ClosedSubscriberGroupInfo(
+                CSG_INDICATOR, HOME_NODEB_NAME, CSG_IDENTITY);
+        Parcel p = Parcel.obtain();
+        csgInfo.writeToParcel(p, 0);
+        p.setDataPosition(0);
+
+        ClosedSubscriberGroupInfo csgInfoFromParcel = ClosedSubscriberGroupInfo.CREATOR
+                .createFromParcel(p);
+
+        assertEquals(csgInfo, csgInfoFromParcel);
+    }
+}
diff --git a/tests/telephonytests/src/com/android/internal/telephony/ConnectionTest.java b/tests/telephonytests/src/com/android/internal/telephony/ConnectionTest.java
old mode 100644
new mode 100755
index 5e2affb..9986cfc
--- a/tests/telephonytests/src/com/android/internal/telephony/ConnectionTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/ConnectionTest.java
@@ -70,6 +70,13 @@
         public void deflect(String number) throws CallStateException {}
 
         @Override
+        public void transfer(String number, boolean isConfirmationRequired)
+                throws CallStateException {}
+
+        @Override
+        public void consultativeTransfer(Connection other) throws CallStateException {}
+
+        @Override
         public void hangup() throws CallStateException {}
 
         @Override
diff --git a/tests/telephonytests/src/com/android/internal/telephony/ContextFixture.java b/tests/telephonytests/src/com/android/internal/telephony/ContextFixture.java
index cd5eff3..106a2a8 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/ContextFixture.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/ContextFixture.java
@@ -16,6 +16,8 @@
 
 package com.android.internal.telephony;
 
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.nullable;
 import static org.mockito.Mockito.any;
 import static org.mockito.Mockito.anyInt;
 import static org.mockito.Mockito.doAnswer;
@@ -25,6 +27,7 @@
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.when;
 
+import android.app.ActivityManager;
 import android.app.AlarmManager;
 import android.app.AppOpsManager;
 import android.app.DownloadManager;
@@ -52,6 +55,7 @@
 import android.database.Cursor;
 import android.database.MatrixCursor;
 import android.net.ConnectivityManager;
+import android.net.Network;
 import android.net.Uri;
 import android.net.wifi.WifiManager;
 import android.os.BatteryManager;
@@ -59,6 +63,8 @@
 import android.os.Handler;
 import android.os.IInterface;
 import android.os.PersistableBundle;
+import android.os.PowerWhitelistManager;
+import android.os.SystemConfigManager;
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.preference.PreferenceManager;
@@ -68,6 +74,7 @@
 import android.telephony.CarrierConfigManager;
 import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyManager;
+import android.telephony.TelephonyRegistryManager;
 import android.telephony.euicc.EuiccManager;
 import android.test.mock.MockContentProvider;
 import android.test.mock.MockContentResolver;
@@ -221,6 +228,8 @@
             switch (name) {
                 case Context.TELEPHONY_SERVICE:
                     return mTelephonyManager;
+                case Context.ACTIVITY_SERVICE:
+                    return mActivityManager;
                 case Context.APP_OPS_SERVICE:
                     return mAppOpsManager;
                 case Context.NOTIFICATION_SERVICE:
@@ -247,11 +256,19 @@
                     return mTelecomManager;
                 case Context.DOWNLOAD_SERVICE:
                     return mDownloadManager;
+                case Context.TELEPHONY_REGISTRY_SERVICE:
+                    return mTelephonyRegistryManager;
+                case Context.SYSTEM_CONFIG_SERVICE:
+                    return mSystemConfigManager;
+                case Context.BATTERY_STATS_SERVICE:
                 case Context.DISPLAY_SERVICE:
                 case Context.POWER_SERVICE:
-                    // PowerManager and DisplayManager are final classes so cannot be mocked,
+                case Context.PERMISSION_SERVICE:
+                    // These are final classes so cannot be mocked,
                     // return real services.
                     return TestApplication.getAppContext().getSystemService(name);
+                case Context.POWER_WHITELIST_MANAGER:
+                    return mPowerWhitelistManager;
                 default:
                     return null;
             }
@@ -265,6 +282,14 @@
                 return Context.APP_OPS_SERVICE;
             } else if (serviceClass == TelecomManager.class) {
                 return Context.TELECOM_SERVICE;
+            } else if (serviceClass == UserManager.class) {
+                return Context.USER_SERVICE;
+            } else if (serviceClass == ConnectivityManager.class) {
+                return Context.CONNECTIVITY_SERVICE;
+            } else if (serviceClass == PowerWhitelistManager.class) {
+                return Context.POWER_WHITELIST_MANAGER;
+            } else if (serviceClass == SystemConfigManager.class) {
+                return Context.SYSTEM_CONFIG_SERVICE;
             }
             return super.getSystemServiceName(serviceClass);
         }
@@ -310,18 +335,28 @@
 
         @Override
         public Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter) {
-            return registerReceiver(receiver, filter, null, null);
+            return registerReceiverFakeImpl(receiver, filter);
         }
 
         @Override
         public Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter,
                 String broadcastPermission, Handler scheduler) {
-            return registerReceiverAsUser(receiver, null, filter, broadcastPermission, scheduler);
+            return registerReceiverFakeImpl(receiver, filter);
+        }
+
+        @Override
+        public Intent registerReceiverForAllUsers(BroadcastReceiver receiver,
+                IntentFilter filter, String broadcastPermission, Handler scheduler) {
+            return registerReceiverFakeImpl(receiver, filter);
         }
 
         @Override
         public Intent registerReceiverAsUser(BroadcastReceiver receiver, UserHandle user,
                 IntentFilter filter, String broadcastPermission, Handler scheduler) {
+            return registerReceiverFakeImpl(receiver, filter);
+        }
+
+        private Intent registerReceiverFakeImpl(BroadcastReceiver receiver, IntentFilter filter) {
             Intent result = null;
             synchronized (mBroadcastReceiversByAction) {
                 for (int i = 0 ; i < filter.countActions() ; i++) {
@@ -405,6 +440,11 @@
         }
 
         @Override
+        public Context createContextAsUser(UserHandle user, int flags) {
+            return this;
+        }
+
+        @Override
         public void sendOrderedBroadcastAsUser(Intent intent, UserHandle user,
                 String receiverPermission, BroadcastReceiver resultReceiver, Handler scheduler,
                 int initialCode, String initialData, Bundle initialExtras) {
@@ -446,6 +486,20 @@
         }
 
         @Override
+        public void sendOrderedBroadcast(Intent intent, int initialCode, String receiverPermission,
+                String receiverAppOp, BroadcastReceiver resultReceiver, Handler scheduler,
+                String initialData, Bundle initialExtras, Bundle options) {
+            logd("sendOrderedBroadcast called for " + intent.getAction());
+            mLastBroadcastOptions = options;
+            sendBroadcast(intent);
+            if (resultReceiver != null) {
+                synchronized (mOrderedBroadcastReceivers) {
+                    mOrderedBroadcastReceivers.put(intent, resultReceiver);
+                }
+            }
+        }
+
+        @Override
         public void sendStickyBroadcast(Intent intent) {
             logd("sendStickyBroadcast called for " + intent.getAction());
             synchronized (mBroadcastReceiversByAction) {
@@ -552,6 +606,7 @@
     private final ApplicationInfo mApplicationInfo = mock(ApplicationInfo.class);
     private final PackageManager mPackageManager = mock(PackageManager.class);
     private final TelephonyManager mTelephonyManager = mock(TelephonyManager.class);
+    private final ActivityManager mActivityManager = mock(ActivityManager.class);
     private final DownloadManager mDownloadManager = mock(DownloadManager.class);
     private final AppOpsManager mAppOpsManager = mock(AppOpsManager.class);
     private final NotificationManager mNotificationManager = mock(NotificationManager.class);
@@ -567,6 +622,10 @@
     private final EuiccManager mEuiccManager = mock(EuiccManager.class);
     private final TelecomManager mTelecomManager = mock(TelecomManager.class);
     private final PackageInfo mPackageInfo = mock(PackageInfo.class);
+    private final TelephonyRegistryManager mTelephonyRegistryManager =
+        mock(TelephonyRegistryManager.class);
+    private final SystemConfigManager mSystemConfigManager = mock(SystemConfigManager.class);
+    private final PowerWhitelistManager mPowerWhitelistManager = mock(PowerWhitelistManager.class);
 
     private final ContentProvider mContentProvider = spy(new FakeContentProvider());
 
@@ -595,10 +654,10 @@
                         (Intent) invocation.getArguments()[0],
                         (Integer) invocation.getArguments()[1]);
             }
-        }).when(mPackageManager).queryIntentServicesAsUser((Intent) any(), anyInt(), anyInt());
+        }).when(mPackageManager).queryIntentServicesAsUser((Intent) any(), anyInt(), any());
 
         try {
-            doReturn(mPackageInfo).when(mPackageManager).getPackageInfoAsUser(any(), anyInt(),
+            doReturn(mPackageInfo).when(mPackageManager).getPackageInfo(nullable(String.class),
                     anyInt());
         } catch (NameNotFoundException e) {
         }
@@ -607,10 +666,19 @@
                 invocation -> mSystemFeatures.contains((String) invocation.getArgument(0)))
                 .when(mPackageManager).hasSystemFeature(any());
 
+        try {
+            doReturn(mResources).when(mPackageManager).getResourcesForApplication(anyString());
+        } catch (NameNotFoundException ex) {
+            Log.d(TAG, "NameNotFoundException: " + ex);
+        }
+
         doReturn(mBundle).when(mCarrierConfigManager).getConfigForSubId(anyInt());
         //doReturn(mBundle).when(mCarrierConfigManager).getConfig(anyInt());
         doReturn(mBundle).when(mCarrierConfigManager).getConfig();
 
+        doReturn(mock(Network.class)).when(mConnectivityManager).registerNetworkAgent(
+                any(), any(), any(), any(), anyInt(), any(), anyInt());
+
         doReturn(true).when(mEuiccManager).isEnabled();
 
         mConfiguration.locale = Locale.US;
diff --git a/tests/telephonytests/src/com/android/internal/telephony/DefaultPhoneNotifierTest.java b/tests/telephonytests/src/com/android/internal/telephony/DefaultPhoneNotifierTest.java
index be94c47..81c2f56 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/DefaultPhoneNotifierTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/DefaultPhoneNotifierTest.java
@@ -16,23 +16,25 @@
 package com.android.internal.telephony;
 
 import static org.junit.Assert.assertEquals;
-import static org.mockito.Matchers.anyString;
 import static org.mockito.Matchers.eq;
 import static org.mockito.Mockito.anyInt;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 
-import android.os.Bundle;
+import android.telephony.CellIdentityGsm;
 import android.telephony.CellInfo;
+import android.telephony.DataFailCause;
 import android.telephony.DisconnectCause;
 import android.telephony.PreciseCallState;
 import android.telephony.PreciseDisconnectCause;
 import android.telephony.SignalStrength;
 import android.telephony.TelephonyManager;
-import android.telephony.gsm.GsmCellLocation;
+import android.telephony.data.ApnSetting;
 import android.test.suitebuilder.annotation.SmallTest;
 
+import com.android.internal.telephony.PhoneInternalInterface.DataActivityState;
+
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
@@ -40,14 +42,15 @@
 import org.mockito.Mock;
 
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.List;
 
 public class DefaultPhoneNotifierTest extends TelephonyTest {
+    private static final int PHONE_ID = 1;
+    private static final int SUB_ID = 0;
 
     private DefaultPhoneNotifier mDefaultPhoneNotifierUT;
     @Mock
-    ITelephonyRegistry.Stub mTelephonyRegisteryMock;
-    @Mock
     SignalStrength mSignalStrength;
     @Mock
     CellInfo mCellInfo;
@@ -61,11 +64,7 @@
     @Before
     public void setUp() throws Exception {
         super.setUp(getClass().getSimpleName());
-        mServiceManagerMockedServices.put("telephony.registry", mTelephonyRegisteryMock);
-        doReturn(mTelephonyRegisteryMock).when(mTelephonyRegisteryMock)
-                .queryLocalInterface(anyString());
-
-        mDefaultPhoneNotifierUT = new DefaultPhoneNotifier();
+        mDefaultPhoneNotifierUT = new DefaultPhoneNotifier(mContext);
     }
 
     @After
@@ -76,26 +75,26 @@
     @Test @SmallTest
     public void testNotifyCallForwarding() throws Exception {
         mDefaultPhoneNotifierUT.notifyCallForwardingChanged(mPhone);
-        verify(mTelephonyRegisteryMock).notifyCallForwardingChangedForSubscriber(eq(0), eq(false));
+        verify(mTelephonyRegistryManager).notifyCallForwardingChanged(eq(0), eq(false));
 
         doReturn(true).when(mPhone).getCallForwardingIndicator();
         doReturn(1).when(mPhone).getSubId();
         mDefaultPhoneNotifierUT.notifyCallForwardingChanged(mPhone);
-        verify(mTelephonyRegisteryMock).notifyCallForwardingChangedForSubscriber(eq(1), eq(true));
+        verify(mTelephonyRegistryManager).notifyCallForwardingChanged(eq(1), eq(true));
     }
 
     @Test @SmallTest
     public void testNotifyDataActivity() throws Exception {
         //mock data activity state
-        doReturn(Phone.DataActivityState.NONE).when(mPhone).getDataActivityState();
+        doReturn(DataActivityState.NONE).when(mPhone).getDataActivityState();
         mDefaultPhoneNotifierUT.notifyDataActivity(mPhone);
-        verify(mTelephonyRegisteryMock).notifyDataActivityForSubscriber(eq(0),
+        verify(mTelephonyRegistryManager).notifyDataActivityChanged(eq(0),
                 eq(TelephonyManager.DATA_ACTIVITY_NONE));
 
         doReturn(1).when(mPhone).getSubId();
-        doReturn(Phone.DataActivityState.DATAIN).when(mPhone).getDataActivityState();
+        doReturn(DataActivityState.DATAIN).when(mPhone).getDataActivityState();
         mDefaultPhoneNotifierUT.notifyDataActivity(mPhone);
-        verify(mTelephonyRegisteryMock).notifyDataActivityForSubscriber(eq(1),
+        verify(mTelephonyRegistryManager).notifyDataActivityChanged(eq(1),
                 eq(TelephonyManager.DATA_ACTIVITY_IN));
     }
 
@@ -108,14 +107,14 @@
                 ArgumentCaptor.forClass(SignalStrength.class);
 
         mDefaultPhoneNotifierUT.notifySignalStrength(mPhone);
-        verify(mTelephonyRegisteryMock).notifySignalStrengthForPhoneId(eq(0), eq(0),
+        verify(mTelephonyRegistryManager).notifySignalStrengthChanged(eq(0), eq(0),
                 signalStrengthArgumentCaptor.capture());
         assertEquals(99, signalStrengthArgumentCaptor.getValue().getGsmSignalStrength());
 
         doReturn(1).when(mPhone).getSubId();
         doReturn(2).when(mPhone).getPhoneId();
         mDefaultPhoneNotifierUT.notifySignalStrength(mPhone);
-        verify(mTelephonyRegisteryMock).notifySignalStrengthForPhoneId(eq(2), eq(1),
+        verify(mTelephonyRegistryManager).notifySignalStrengthChanged(eq(1), eq(2),
                 signalStrengthArgumentCaptor.capture());
         assertEquals(99, signalStrengthArgumentCaptor.getValue().getGsmSignalStrength());
     }
@@ -129,7 +128,7 @@
 
         mDefaultPhoneNotifierUT.notifyCellInfo(mPhone, mCellInfoList);
 
-        verify(mTelephonyRegisteryMock).notifyCellInfoForSubscriber(eq(0),
+        verify(mTelephonyRegistryManager).notifyCellInfoChanged(eq(0),
                 cellInfoArgumentCaptor.capture());
         assertEquals(mCellInfo, cellInfoArgumentCaptor.getValue().get(0));
     }
@@ -138,47 +137,56 @@
     public void testNotifyMessageWaiting() throws Exception {
         doReturn(1).when(mPhone).getPhoneId();
         mDefaultPhoneNotifierUT.notifyMessageWaitingChanged(mPhone);
-        verify(mTelephonyRegisteryMock).notifyMessageWaitingChangedForPhoneId(1, 0, false);
+        verify(mTelephonyRegistryManager).notifyMessageWaitingChanged(0, 1, false);
 
         doReturn(2).when(mPhone).getPhoneId();
         mDefaultPhoneNotifierUT.notifyMessageWaitingChanged(mPhone);
-        verify(mTelephonyRegisteryMock).notifyMessageWaitingChangedForPhoneId(2, 0, false);
+        verify(mTelephonyRegistryManager).notifyMessageWaitingChanged(0, 2, false);
 
         doReturn(1).when(mPhone).getSubId();
         mDefaultPhoneNotifierUT.notifyMessageWaitingChanged(mPhone);
-        verify(mTelephonyRegisteryMock).notifyMessageWaitingChangedForPhoneId(2, 1, false);
+        verify(mTelephonyRegistryManager).notifyMessageWaitingChanged(1, 2, false);
 
         doReturn(true).when(mPhone).getMessageWaitingIndicator();
         mDefaultPhoneNotifierUT.notifyMessageWaitingChanged(mPhone);
-        verify(mTelephonyRegisteryMock).notifyMessageWaitingChangedForPhoneId(2, 1, true);
+        verify(mTelephonyRegistryManager).notifyMessageWaitingChanged(1, 2, true);
     }
 
     @Test @SmallTest
     public void testNotifyDisconnectCause() throws Exception {
-        doReturn(1).when(mPhone).getPhoneId();
-        doReturn(0).when(mPhone).getSubId();
+        doReturn(PHONE_ID).when(mPhone).getPhoneId();
+        doReturn(SUB_ID).when(mPhone).getSubId();
         mDefaultPhoneNotifierUT.notifyDisconnectCause(mPhone, DisconnectCause.NOT_VALID,
                 PreciseDisconnectCause.FDN_BLOCKED);
-        verify(mTelephonyRegisteryMock).notifyDisconnectCause(1, 0, DisconnectCause.NOT_VALID,
-                PreciseDisconnectCause.FDN_BLOCKED);
+        verify(mTelephonyRegistryManager).notifyDisconnectCause(PHONE_ID, SUB_ID,
+                DisconnectCause.NOT_VALID, PreciseDisconnectCause.FDN_BLOCKED);
 
         mDefaultPhoneNotifierUT.notifyDisconnectCause(mPhone, DisconnectCause.LOCAL,
                 PreciseDisconnectCause.CHANNEL_NOT_AVAIL);
-        verify(mTelephonyRegisteryMock).notifyDisconnectCause(1, 0, DisconnectCause.LOCAL,
-                PreciseDisconnectCause.CHANNEL_NOT_AVAIL);
+        verify(mTelephonyRegistryManager).notifyDisconnectCause(PHONE_ID, SUB_ID,
+                DisconnectCause.LOCAL, PreciseDisconnectCause.CHANNEL_NOT_AVAIL);
     }
 
     @Test @SmallTest
     public void testNotifyDataConnectionFailed() throws Exception {
-        mDefaultPhoneNotifierUT.notifyDataConnectionFailed(mPhone, "APN_0");
-        verify(mTelephonyRegisteryMock).notifyDataConnectionFailedForSubscriber(0, 0, "APN_0");
+        mDefaultPhoneNotifierUT.notifyDataConnectionFailed(mPhone, "default", "APN_0",
+                DataFailCause.INSUFFICIENT_RESOURCES);
+        verify(mTelephonyRegistryManager).notifyPreciseDataConnectionFailed(
+                eq(0), eq(0), eq(ApnSetting.TYPE_DEFAULT), eq("APN_0"),
+                eq(DataFailCause.INSUFFICIENT_RESOURCES));
 
-        mDefaultPhoneNotifierUT.notifyDataConnectionFailed(mPhone, "APN_1");
-        verify(mTelephonyRegisteryMock).notifyDataConnectionFailedForSubscriber(0, 0, "APN_1");
+        mDefaultPhoneNotifierUT.notifyDataConnectionFailed(mPhone, "default", "APN_1",
+                DataFailCause.INSUFFICIENT_RESOURCES);
+        verify(mTelephonyRegistryManager).notifyPreciseDataConnectionFailed(
+                eq(0), eq(0), eq(ApnSetting.TYPE_DEFAULT), eq("APN_1"),
+                eq(DataFailCause.INSUFFICIENT_RESOURCES));
 
         doReturn(1).when(mPhone).getSubId();
-        mDefaultPhoneNotifierUT.notifyDataConnectionFailed(mPhone, "APN_1");
-        verify(mTelephonyRegisteryMock).notifyDataConnectionFailedForSubscriber(0,1, "APN_1");
+        mDefaultPhoneNotifierUT.notifyDataConnectionFailed(mPhone, "default", "APN_1",
+                DataFailCause.INSUFFICIENT_RESOURCES);
+        verify(mTelephonyRegistryManager).notifyPreciseDataConnectionFailed(
+                eq(1), eq(0), eq(ApnSetting.TYPE_DEFAULT), eq("APN_1"),
+                eq(DataFailCause.INSUFFICIENT_RESOURCES));
     }
 
     @Test @SmallTest
@@ -190,22 +198,22 @@
         doReturn(Call.State.IDLE).when(mRingingCall).getState();
 
         mDefaultPhoneNotifierUT.notifyPreciseCallState(mPhone);
-        verify(mTelephonyRegisteryMock, times(0)).notifyPreciseCallState(
+        verify(mTelephonyRegistryManager, times(0)).notifyPreciseCallState(
                 anyInt(), anyInt(), anyInt(), anyInt(), anyInt());
 
         doReturn(mForeGroundCall).when(mPhone).getForegroundCall();
         mDefaultPhoneNotifierUT.notifyPreciseCallState(mPhone);
-        verify(mTelephonyRegisteryMock, times(0)).notifyPreciseCallState(
+        verify(mTelephonyRegistryManager, times(0)).notifyPreciseCallState(
                 anyInt(), anyInt(), anyInt(), anyInt(), anyInt());
 
         doReturn(mBackGroundCall).when(mPhone).getBackgroundCall();
         mDefaultPhoneNotifierUT.notifyPreciseCallState(mPhone);
-        verify(mTelephonyRegisteryMock, times(0)).notifyPreciseCallState(
+        verify(mTelephonyRegistryManager, times(0)).notifyPreciseCallState(
                 anyInt(), anyInt(), anyInt(), anyInt(), anyInt());
 
         doReturn(mRingingCall).when(mPhone).getRingingCall();
         mDefaultPhoneNotifierUT.notifyPreciseCallState(mPhone);
-        verify(mTelephonyRegisteryMock, times(1)).notifyPreciseCallState(
+        verify(mTelephonyRegistryManager, times(1)).notifyPreciseCallState(
                 mPhone.getPhoneId(),
                 mPhone.getSubId(),
                 PreciseCallState.PRECISE_CALL_STATE_IDLE,
@@ -214,7 +222,7 @@
 
         doReturn(Call.State.ACTIVE).when(mForeGroundCall).getState();
         mDefaultPhoneNotifierUT.notifyPreciseCallState(mPhone);
-        verify(mTelephonyRegisteryMock, times(1)).notifyPreciseCallState(
+        verify(mTelephonyRegistryManager, times(1)).notifyPreciseCallState(
                 mPhone.getPhoneId(),
                 mPhone.getSubId(),
                 PreciseCallState.PRECISE_CALL_STATE_IDLE,
@@ -223,7 +231,7 @@
 
         doReturn(Call.State.HOLDING).when(mBackGroundCall).getState();
         mDefaultPhoneNotifierUT.notifyPreciseCallState(mPhone);
-        verify(mTelephonyRegisteryMock, times(1)).notifyPreciseCallState(
+        verify(mTelephonyRegistryManager, times(1)).notifyPreciseCallState(
                 mPhone.getPhoneId(),
                 mPhone.getSubId(),
                 PreciseCallState.PRECISE_CALL_STATE_IDLE,
@@ -232,7 +240,7 @@
 
         doReturn(Call.State.ALERTING).when(mRingingCall).getState();
         mDefaultPhoneNotifierUT.notifyPreciseCallState(mPhone);
-        verify(mTelephonyRegisteryMock, times(1)).notifyPreciseCallState(
+        verify(mTelephonyRegistryManager, times(1)).notifyPreciseCallState(
                 mPhone.getPhoneId(),
                 mPhone.getSubId(),
                 PreciseCallState.PRECISE_CALL_STATE_ALERTING,
@@ -243,26 +251,25 @@
     @Test @SmallTest
     public void testNotifyCellLocation() throws Exception {
         // mock gsm cell location
-        GsmCellLocation mGsmCellLocation = new GsmCellLocation();
-        mGsmCellLocation.setLacAndCid(2, 3);
-        doReturn(mGsmCellLocation).when(mPhone).getCellLocation();
-        ArgumentCaptor<Bundle> cellLocationCapture =
-                ArgumentCaptor.forClass(Bundle.class);
+        CellIdentityGsm mGsmCellLocation = new CellIdentityGsm(
+                2, 3, 0, 0, null, null, null, null, Collections.emptyList());
+        doReturn(mGsmCellLocation).when(mPhone).getCellIdentity();
+        ArgumentCaptor<CellIdentityGsm> cellLocationCapture =
+                ArgumentCaptor.forClass(CellIdentityGsm.class);
 
         mDefaultPhoneNotifierUT.notifyCellLocation(mPhone, mGsmCellLocation);
-        verify(mTelephonyRegisteryMock).notifyCellLocationForSubscriber(eq(0),
+        verify(mTelephonyRegistryManager).notifyCellLocation(eq(0),
                 cellLocationCapture.capture());
-        assertEquals(2, cellLocationCapture.getValue().getInt("lac"));
-        assertEquals(3, cellLocationCapture.getValue().getInt("cid"));
-        assertEquals(-1, cellLocationCapture.getValue().getInt("psc"));
+        assertEquals(2, cellLocationCapture.getValue().asCellLocation().getLac());
+        assertEquals(3, cellLocationCapture.getValue().asCellLocation().getCid());
+        assertEquals(-1, cellLocationCapture.getValue().asCellLocation().getPsc());
 
         doReturn(1).when(mPhone).getSubId();
-        mGsmCellLocation.setPsc(5);
         mDefaultPhoneNotifierUT.notifyCellLocation(mPhone, mGsmCellLocation);
-        verify(mTelephonyRegisteryMock).notifyCellLocationForSubscriber(eq(1),
+        verify(mTelephonyRegistryManager).notifyCellLocation(eq(1),
                 cellLocationCapture.capture());
-        assertEquals(2, cellLocationCapture.getValue().getInt("lac"));
-        assertEquals(3, cellLocationCapture.getValue().getInt("cid"));
-        assertEquals(5, cellLocationCapture.getValue().getInt("psc"));
+        assertEquals(2, cellLocationCapture.getValue().asCellLocation().getLac());
+        assertEquals(3, cellLocationCapture.getValue().asCellLocation().getCid());
+        assertEquals(-1, cellLocationCapture.getValue().asCellLocation().getPsc());
     }
 }
diff --git a/tests/telephonytests/src/com/android/internal/telephony/DeviceStateMonitorTest.java b/tests/telephonytests/src/com/android/internal/telephony/DeviceStateMonitorTest.java
index a5eb233..be03eca 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/DeviceStateMonitorTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/DeviceStateMonitorTest.java
@@ -17,158 +17,352 @@
 
 import static android.hardware.radio.V1_0.DeviceStateType.CHARGING_STATE;
 import static android.hardware.radio.V1_0.DeviceStateType.LOW_DATA_EXPECTED;
-
-import static com.android.internal.telephony.TelephonyTestUtils.waitForMs;
+import static android.hardware.radio.V1_0.DeviceStateType.POWER_SAVE_MODE;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+import static org.mockito.Matchers.anyBoolean;
+import static org.mockito.Matchers.anyInt;
 import static org.mockito.Matchers.eq;
 import static org.mockito.Matchers.nullable;
-import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.reset;
 import static org.mockito.Mockito.verify;
 
+import static java.util.Arrays.asList;
+
+import android.annotation.IntDef;
 import android.content.Intent;
+import android.hardware.radio.V1_5.IndicationFilter;
 import android.net.ConnectivityManager;
 import android.os.BatteryManager;
-import android.os.HandlerThread;
 import android.os.Message;
 import android.test.suitebuilder.annotation.MediumTest;
-
-import androidx.test.filters.FlakyTest;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
 
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
 
-import static java.util.Arrays.asList;
-
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 import java.util.ArrayList;
+import java.util.Map;
 
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper
 public class DeviceStateMonitorTest extends TelephonyTest {
+    private static final int INDICATION_FILTERS_MINIMUM = IndicationFilter.REGISTRATION_FAILURE;
+
+    // All implemented indiation filters set so far
+    // which is a subset of IndicationFilter.ALL
+    private static final int INDICATION_FILTERS_ALL =
+            IndicationFilter.SIGNAL_STRENGTH
+            | IndicationFilter.FULL_NETWORK_STATE
+            | IndicationFilter.DATA_CALL_DORMANCY_CHANGED
+            | IndicationFilter.LINK_CAPACITY_ESTIMATE
+            | IndicationFilter.PHYSICAL_CHANNEL_CONFIG
+            | IndicationFilter.REGISTRATION_FAILURE
+            | IndicationFilter.BARRING_INFO;
+
+    // INDICATION_FILTERS_ALL but excludes Indication.SIGNAL_STRENGTH
+    private static final int INDICATION_FILTERS_WHEN_TETHERING_ON =
+            INDICATION_FILTERS_ALL & ~IndicationFilter.SIGNAL_STRENGTH;
+    private static final int INDICATION_FILTERS_WHEN_CHARGING = INDICATION_FILTERS_ALL;
+    private static final int INDICATION_FILTERS_WHEN_SCREEN_ON = INDICATION_FILTERS_ALL;
+
+    /** @hide */
+    @IntDef(prefix = {"STATE_TYPE_"}, value = {
+        STATE_TYPE_RIL_CONNECTED,
+        STATE_TYPE_SCREEN,
+        STATE_TYPE_POWER_SAVE_MODE,
+        STATE_TYPE_CHARGING,
+        STATE_TYPE_TETHERING,
+        STATE_TYPE_RADIO_AVAILABLE,
+        STATE_TYPE_WIFI_CONNECTED,
+        STATE_TYPE_ALWAYS_SIGNAL_STRENGTH_REPORTED,
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    private @interface StateType {}
+
+    // Keep the same value as correspoinding event
+    // See state2Event() for detail
+    private static final int STATE_TYPE_RIL_CONNECTED = 0;
+    // EVENT_UPDATE_NODE_CHANGED is not here, it will be removed in aosp soon
+    private static final int STATE_TYPE_SCREEN = 2;
+    private static final int STATE_TYPE_POWER_SAVE_MODE = 3;
+    private static final int STATE_TYPE_CHARGING = 4;
+    private static final int STATE_TYPE_TETHERING = 5;
+    private static final int STATE_TYPE_RADIO_AVAILABLE = 6;
+    private static final int STATE_TYPE_WIFI_CONNECTED = 7;
+    private static final int STATE_TYPE_ALWAYS_SIGNAL_STRENGTH_REPORTED = 8;
+
+    /** @hide */
+    @IntDef(prefix = {"STATE_"}, value = {
+        STATE_OFF,
+        STATE_ON
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    private @interface StateStatus {}
+
+    private static final int STATE_OFF = 0;
+    private static final int STATE_ON = 1;
+
+    // The keys are the single IndicationFilter flags,
+    // The values are the array of states, when one state turn on, the corresponding
+    // IndicationFilter flag should NOT be turned off.
+    private static final Map<Integer, int[]> INDICATION_FILTER_2_TRIGGERS = Map.of(
+            IndicationFilter.SIGNAL_STRENGTH,             new int[] {
+                STATE_TYPE_ALWAYS_SIGNAL_STRENGTH_REPORTED, STATE_TYPE_CHARGING, STATE_TYPE_SCREEN},
+            IndicationFilter.FULL_NETWORK_STATE,          new int[] {
+                STATE_TYPE_CHARGING, STATE_TYPE_SCREEN, STATE_TYPE_TETHERING},
+            IndicationFilter.DATA_CALL_DORMANCY_CHANGED,  new int[] {
+                STATE_TYPE_CHARGING, STATE_TYPE_SCREEN, STATE_TYPE_TETHERING},
+            IndicationFilter.LINK_CAPACITY_ESTIMATE,      new int[] {
+                STATE_TYPE_CHARGING, STATE_TYPE_SCREEN, STATE_TYPE_TETHERING},
+            IndicationFilter.PHYSICAL_CHANNEL_CONFIG,     new int[] {
+                STATE_TYPE_CHARGING, STATE_TYPE_SCREEN, STATE_TYPE_TETHERING}
+    );
 
     private DeviceStateMonitor mDSM;
+    // Given a stateType, return the event type that can change the state
+    private int state2Event(@StateType int stateType) {
+        // As long as we keep the same value, we can directly return the stateType
+        return stateType;
+    }
 
-    private DeviceStateMonitorTestHandler mDeviceStateMonitorTestHandler;
+    private void updateState(@StateType int stateType, @StateStatus int stateValue) {
+        final int event = state2Event(stateType);
+        mDSM.obtainMessage(event, stateValue, 0 /* arg2, not used*/).sendToTarget();
+        processAllMessages();
+    }
 
-    private class DeviceStateMonitorTestHandler extends HandlerThread {
-
-        private DeviceStateMonitorTestHandler(String name) {
-            super(name);
-        }
-
-        @Override
-        public void onLooperPrepared() {
-            mDSM = new DeviceStateMonitor(mPhone);
-            setReady(true);
-        }
+    private void updateAllStatesToOff() {
+        updateState(STATE_TYPE_RIL_CONNECTED, STATE_OFF);
+        updateState(STATE_TYPE_SCREEN, STATE_OFF);
+        updateState(STATE_TYPE_POWER_SAVE_MODE, STATE_OFF);
+        updateState(STATE_TYPE_CHARGING, STATE_OFF);
+        updateState(STATE_TYPE_TETHERING, STATE_OFF);
+        updateState(STATE_TYPE_RADIO_AVAILABLE, STATE_OFF);
+        updateState(STATE_TYPE_WIFI_CONNECTED, STATE_OFF);
+        updateState(STATE_TYPE_ALWAYS_SIGNAL_STRENGTH_REPORTED, STATE_OFF);
     }
 
     @Before
     public void setUp() throws Exception {
         super.setUp(getClass().getSimpleName());
-        mDeviceStateMonitorTestHandler = new DeviceStateMonitorTestHandler(TAG);
-        mDeviceStateMonitorTestHandler.start();
-        waitUntilReady();
+        mDSM = new DeviceStateMonitor(mPhone);
+
+        // Initialize with ALL states off
+        updateAllStatesToOff();
+
+        // eliminate the accumuted impact on Mockito.verify()
+        reset(mSimulatedCommandsVerifier);
     }
 
     @After
     public void tearDown() throws Exception {
-        mDeviceStateMonitor = null;
-        mDeviceStateMonitorTestHandler.quit();
+        mDSM = null;
         super.tearDown();
     }
 
-    @FlakyTest
-    public void testTethering() throws Exception {
+    /**
+     * Verify the behavior of CI.setUnsolResponseFilter().
+     * Keeping other state unchanged, when one state change. setUnsolResponseFilter()
+     * should be called with right IndicationFilter flag set.
+     */
+    @Test @MediumTest
+    public void testSetUnsolResponseFilter_singleStateChange() {
+        for (int indicationFilter : INDICATION_FILTER_2_TRIGGERS.keySet()) {
+            for (int state : INDICATION_FILTER_2_TRIGGERS.get(indicationFilter)) {
+                verifySetUnsolResponseFilter(state, indicationFilter);
+            }
+        }
+    }
+
+    private void verifySetUnsolResponseFilter(int state, int indicationFilter) {
+        reset(mSimulatedCommandsVerifier);
+        // In the beginning, all states are off
+
+        // Turn on the state
+        updateState(state, STATE_ON);
+
+        // Keep other states off, then specified indication filter should NOT be turn off
+        ArgumentCaptor<Integer> acIndicationFilter = ArgumentCaptor.forClass(Integer.class);
+        verify(mSimulatedCommandsVerifier).setUnsolResponseFilter(
+                acIndicationFilter.capture(), nullable(Message.class));
+        assertNotEquals((acIndicationFilter.getValue() & indicationFilter), 0);
+
+        // Turn off the state again
+        updateState(state, STATE_OFF);
+
+        // Keep other states off, then no filter flag is on
+        verify(mSimulatedCommandsVerifier).setUnsolResponseFilter(
+                eq(INDICATION_FILTERS_MINIMUM), nullable(Message.class));
+    }
+
+    @Test
+    public void testSetUnsolResponseFilter_noReduandantCall() {
+        // initially all state off, turn screen on
+        updateState(STATE_TYPE_SCREEN, STATE_ON);
+        verify(mSimulatedCommandsVerifier).setUnsolResponseFilter(anyInt(),
+                nullable(Message.class));
+        reset(mSimulatedCommandsVerifier);
+
+        updateState(STATE_TYPE_CHARGING, STATE_ON);
+        verify(mSimulatedCommandsVerifier, never()).setUnsolResponseFilter(anyInt(),
+                nullable(Message.class));
+
+        updateState(STATE_TYPE_POWER_SAVE_MODE, STATE_ON);
+        verify(mSimulatedCommandsVerifier, never()).setUnsolResponseFilter(anyInt(),
+                nullable(Message.class));
+    }
+
+    @Test
+    public void testScreenOnOff() {
+        // screen was off by default, turn it on now
+        updateState(STATE_TYPE_SCREEN, STATE_ON);
+        processAllMessages();
+
+        verify(mSimulatedCommandsVerifier).setUnsolResponseFilter(
+                eq(INDICATION_FILTERS_WHEN_SCREEN_ON), nullable(Message.class));
+
+        // turn screen off
+        updateState(STATE_TYPE_SCREEN, STATE_OFF);
+        processAllMessages();
+
+        verify(mSimulatedCommandsVerifier).setUnsolResponseFilter(
+                eq(INDICATION_FILTERS_MINIMUM), nullable(Message.class));
+    }
+
+    @Test
+    public void testTethering() {
         // Turn tethering on
         Intent intent = new Intent(ConnectivityManager.ACTION_TETHER_STATE_CHANGED);
         intent.putExtra(ConnectivityManager.EXTRA_ACTIVE_TETHER, new ArrayList<>(asList("abc")));
         mContext.sendBroadcast(intent);
+        processAllMessages();
 
-        waitForMs(100);
-
-        verify(mSimulatedCommandsVerifier, times(1)).setUnsolResponseFilter(eq(6),
-                nullable(Message.class));
+        verify(mSimulatedCommandsVerifier).setUnsolResponseFilter(
+                eq(INDICATION_FILTERS_WHEN_TETHERING_ON), nullable(Message.class));
 
         // Turn tethering off
         intent = new Intent(ConnectivityManager.ACTION_TETHER_STATE_CHANGED);
         intent.putExtra(ConnectivityManager.EXTRA_ACTIVE_TETHER, new ArrayList<>());
         mContext.sendBroadcast(intent);
-        waitForMs(100);
+        processAllMessages();
 
-        verify(mSimulatedCommandsVerifier, times(1)).setUnsolResponseFilter(eq(0),
-                nullable(Message.class));
+        verify(mSimulatedCommandsVerifier).setUnsolResponseFilter(
+                eq(INDICATION_FILTERS_MINIMUM), nullable(Message.class));
 
-        verify(mSimulatedCommandsVerifier, times(1)).sendDeviceState(eq(LOW_DATA_EXPECTED),
+        verify(mSimulatedCommandsVerifier).sendDeviceState(eq(LOW_DATA_EXPECTED),
                 eq(true), nullable(Message.class));
     }
 
-    @FlakyTest
-    public void testCharging() throws Exception {
+    @Test
+    public void testCharging() {
         // Charging
         Intent intent = new Intent(BatteryManager.ACTION_CHARGING);
         mContext.sendBroadcast(intent);
-        waitForMs(100);
+        processAllMessages();
 
-        verify(mSimulatedCommandsVerifier, times(1)).sendDeviceState(eq(CHARGING_STATE),
+        verify(mSimulatedCommandsVerifier).setUnsolResponseFilter(
+                eq(INDICATION_FILTERS_WHEN_CHARGING), nullable(Message.class));
+        verify(mSimulatedCommandsVerifier).sendDeviceState(eq(CHARGING_STATE),
                 eq(true), nullable(Message.class));
 
         // Not charging
         intent = new Intent(BatteryManager.ACTION_DISCHARGING);
         mContext.sendBroadcast(intent);
-        waitForMs(100);
+        processAllMessages();
 
-        verify(mSimulatedCommandsVerifier, times(1)).setUnsolResponseFilter(eq(0),
-                nullable(Message.class));
-
-        verify(mSimulatedCommandsVerifier, times(1)).sendDeviceState(eq(LOW_DATA_EXPECTED),
+        verify(mSimulatedCommandsVerifier).setUnsolResponseFilter(
+                eq(INDICATION_FILTERS_MINIMUM), nullable(Message.class));
+        verify(mSimulatedCommandsVerifier).sendDeviceState(eq(LOW_DATA_EXPECTED),
                 eq(true), nullable(Message.class));
-
-        verify(mSimulatedCommandsVerifier, times(1)).sendDeviceState(eq(CHARGING_STATE),
+        verify(mSimulatedCommandsVerifier).sendDeviceState(eq(CHARGING_STATE),
                 eq(false), nullable(Message.class));
     }
 
-    @FlakyTest
-    public void testReset() throws Exception {
-        mDSM.obtainMessage(6).sendToTarget();
-
-        verify(mSimulatedCommandsVerifier, times(1)).setUnsolResponseFilter(eq(-1),
-                nullable(Message.class));
+    @Test
+    public void testReset() {
+        testResetFromEvent(DeviceStateMonitor.EVENT_RIL_CONNECTED);
+        testResetFromEvent(DeviceStateMonitor.EVENT_RADIO_AVAILABLE);
     }
 
-    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();
+    private void testResetFromEvent(int event) {
+        reset(mSimulatedCommandsVerifier);
+        mDSM.obtainMessage(event).sendToTarget();
+        processAllMessages();
+
+        verify(mSimulatedCommandsVerifier).sendDeviceState(eq(CHARGING_STATE),
+                anyBoolean(), nullable(Message.class));
+        verify(mSimulatedCommandsVerifier).sendDeviceState(eq(LOW_DATA_EXPECTED),
+                anyBoolean(), nullable(Message.class));
+        verify(mSimulatedCommandsVerifier).sendDeviceState(eq(POWER_SAVE_MODE),
+                anyBoolean(), nullable(Message.class));
+        verify(mSimulatedCommandsVerifier).setUnsolResponseFilter(anyInt(),
+                nullable(Message.class));
     }
 
     @Test
     @MediumTest
-    public void testWifi() throws Exception  {
-        // screen off
-        sendStates(0, 0, 0);
+    public void testComputeCellInfoMinInternal() {
+        // by default, screen is off, charging is off and wifi is off
         assertEquals(
                 DeviceStateMonitor.CELL_INFO_INTERVAL_LONG_MS, mDSM.computeCellInfoMinInterval());
-        // screen off, but charging
-        sendStates(0, 1, 0);
+
+        // keep screen off, but turn charging on
+        updateState(STATE_TYPE_CHARGING, STATE_ON);
         assertEquals(
                 DeviceStateMonitor.CELL_INFO_INTERVAL_LONG_MS, mDSM.computeCellInfoMinInterval());
-        // screen on, no wifi
-        sendStates(1, 0, 0);
+
+        // turn screen on, turn charging off and keep wifi off
+        updateState(STATE_TYPE_SCREEN, STATE_ON);
+        updateState(STATE_TYPE_CHARGING, STATE_OFF);
         assertEquals(
                 DeviceStateMonitor.CELL_INFO_INTERVAL_SHORT_MS, mDSM.computeCellInfoMinInterval());
+
         // screen on, but on wifi
-        sendStates(1, 0, 1);
+        updateState(STATE_TYPE_WIFI_CONNECTED, STATE_ON);
         assertEquals(
                 DeviceStateMonitor.CELL_INFO_INTERVAL_LONG_MS, mDSM.computeCellInfoMinInterval());
+
         // screen on, charging
-        sendStates(1, 1, 0);
+        updateState(STATE_TYPE_WIFI_CONNECTED, STATE_OFF);
+        updateState(STATE_TYPE_CHARGING, STATE_OFF);
         assertEquals(
                 DeviceStateMonitor.CELL_INFO_INTERVAL_SHORT_MS, mDSM.computeCellInfoMinInterval());
     }
+
+    @Test
+    public void testGetBarringInfo() {
+        // At beginning, all states off. Now turn screen on
+        updateState(STATE_TYPE_SCREEN, STATE_ON);
+
+        ArgumentCaptor<Integer> acBarringInfo = ArgumentCaptor.forClass(Integer.class);
+        verify(mSimulatedCommandsVerifier).setUnsolResponseFilter(acBarringInfo.capture(),
+                nullable(Message.class));
+        assertNotEquals((acBarringInfo.getValue() & IndicationFilter.BARRING_INFO), 0);
+        verify(mSimulatedCommandsVerifier).getBarringInfo(nullable(Message.class));
+
+        reset(mSimulatedCommandsVerifier);
+
+        // Turn screen off
+        updateState(STATE_TYPE_SCREEN, STATE_OFF);
+        verify(mSimulatedCommandsVerifier, never()).getBarringInfo(nullable(Message.class));
+        verify(mSimulatedCommandsVerifier).setUnsolResponseFilter(acBarringInfo.capture(),
+                nullable(Message.class));
+        assertEquals((acBarringInfo.getValue() & IndicationFilter.BARRING_INFO), 0);
+
+        reset(mSimulatedCommandsVerifier);
+
+        // Turn tethering on, then screen on, getBarringInfo() should only be called once
+        updateState(STATE_TYPE_TETHERING, STATE_ON);
+        updateState(STATE_TYPE_SCREEN, STATE_ON);
+        verify(mSimulatedCommandsVerifier).getBarringInfo(nullable(Message.class));
+    }
 }
diff --git a/tests/telephonytests/src/com/android/internal/telephony/FakeTelephonyProvider.java b/tests/telephonytests/src/com/android/internal/telephony/FakeTelephonyProvider.java
index a9e4ed0..86cf43f 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/FakeTelephonyProvider.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/FakeTelephonyProvider.java
@@ -25,7 +25,7 @@
 import android.net.Uri;
 import android.os.Bundle;
 import android.provider.BaseColumns;
-import android.telephony.SubscriptionManager;
+import android.provider.Telephony;
 import android.test.mock.MockContentProvider;
 import android.util.Log;
 
@@ -52,66 +52,68 @@
         // This should always be consistent with TelephonyProvider#getStringForSimInfoTableCreation.
         private String getStringForSimInfoTableCreation(String tableName) {
             return "CREATE TABLE " + tableName + "("
-                    + SubscriptionManager.UNIQUE_KEY_SUBSCRIPTION_ID
+                    + Telephony.SimInfo.COLUMN_UNIQUE_KEY_SUBSCRIPTION_ID
                     + " INTEGER PRIMARY KEY AUTOINCREMENT,"
-                    + SubscriptionManager.ICC_ID + " TEXT NOT NULL,"
-                    + SubscriptionManager.SIM_SLOT_INDEX
-                    + " INTEGER DEFAULT " + SubscriptionManager.SIM_NOT_INSERTED + ","
-                    + SubscriptionManager.DISPLAY_NAME + " TEXT,"
-                    + SubscriptionManager.CARRIER_NAME + " TEXT,"
-                    + SubscriptionManager.NAME_SOURCE
-                    + " INTEGER DEFAULT " + SubscriptionManager.NAME_SOURCE_DEFAULT_SOURCE + ","
-                    + SubscriptionManager.COLOR + " INTEGER DEFAULT "
-                    + SubscriptionManager.COLOR_DEFAULT + ","
-                    + SubscriptionManager.NUMBER + " TEXT,"
-                    + SubscriptionManager.DISPLAY_NUMBER_FORMAT
+                    + Telephony.SimInfo.COLUMN_ICC_ID + " TEXT NOT NULL,"
+                    + Telephony.SimInfo.COLUMN_SIM_SLOT_INDEX
+                    + " INTEGER DEFAULT " + Telephony.SimInfo.SIM_NOT_INSERTED + ","
+                    + Telephony.SimInfo.COLUMN_DISPLAY_NAME + " TEXT,"
+                    + Telephony.SimInfo.COLUMN_CARRIER_NAME + " TEXT,"
+                    + Telephony.SimInfo.COLUMN_NAME_SOURCE
+                    + " INTEGER DEFAULT " + Telephony.SimInfo.NAME_SOURCE_CARRIER_ID + ","
+                    + Telephony.SimInfo.COLUMN_COLOR + " INTEGER DEFAULT "
+                    + Telephony.SimInfo.COLOR_DEFAULT + ","
+                    + Telephony.SimInfo.COLUMN_NUMBER + " TEXT,"
+                    + Telephony.SimInfo.COLUMN_DISPLAY_NUMBER_FORMAT
                     + " INTEGER NOT NULL DEFAULT "
-                    + SubscriptionManager.DISPLAY_NUMBER_DEFAULT + ","
-                    + SubscriptionManager.DATA_ROAMING
-                    + " INTEGER DEFAULT " + SubscriptionManager.DATA_ROAMING_DEFAULT + ","
-                    + SubscriptionManager.MCC + " INTEGER DEFAULT 0,"
-                    + SubscriptionManager.MNC + " INTEGER DEFAULT 0,"
-                    + SubscriptionManager.MCC_STRING + " TEXT,"
-                    + SubscriptionManager.MNC_STRING + " TEXT,"
-                    + SubscriptionManager.EHPLMNS + " TEXT,"
-                    + SubscriptionManager.HPLMNS + " TEXT,"
-                    + SubscriptionManager.SIM_PROVISIONING_STATUS
-                    + " INTEGER DEFAULT " + SubscriptionManager.SIM_PROVISIONED + ","
-                    + SubscriptionManager.IS_EMBEDDED + " INTEGER DEFAULT 0,"
-                    + SubscriptionManager.CARD_ID + " TEXT NOT NULL,"
-                    + SubscriptionManager.ACCESS_RULES + " BLOB,"
-                    + SubscriptionManager.IS_REMOVABLE + " INTEGER DEFAULT 0,"
-                    + SubscriptionManager.CB_EXTREME_THREAT_ALERT + " INTEGER DEFAULT 1,"
-                    + SubscriptionManager.CB_SEVERE_THREAT_ALERT + " INTEGER DEFAULT 1,"
-                    + SubscriptionManager.CB_AMBER_ALERT + " INTEGER DEFAULT 1,"
-                    + SubscriptionManager.CB_EMERGENCY_ALERT + " INTEGER DEFAULT 1,"
-                    + SubscriptionManager.CB_ALERT_SOUND_DURATION + " INTEGER DEFAULT 4,"
-                    + SubscriptionManager.CB_ALERT_REMINDER_INTERVAL + " INTEGER DEFAULT 0,"
-                    + SubscriptionManager.CB_ALERT_VIBRATE + " INTEGER DEFAULT 1,"
-                    + SubscriptionManager.CB_ALERT_SPEECH + " INTEGER DEFAULT 1,"
-                    + SubscriptionManager.CB_ETWS_TEST_ALERT + " INTEGER DEFAULT 0,"
-                    + SubscriptionManager.CB_CHANNEL_50_ALERT + " INTEGER DEFAULT 1,"
-                    + SubscriptionManager.CB_CMAS_TEST_ALERT + " INTEGER DEFAULT 0,"
-                    + SubscriptionManager.CB_OPT_OUT_DIALOG + " INTEGER DEFAULT 1,"
-                    + SubscriptionManager.ENHANCED_4G_MODE_ENABLED + " INTEGER DEFAULT -1,"
-                    + SubscriptionManager.VT_IMS_ENABLED + " INTEGER DEFAULT -1,"
-                    + SubscriptionManager.WFC_IMS_ENABLED + " INTEGER DEFAULT -1,"
-                    + SubscriptionManager.WFC_IMS_MODE + " INTEGER DEFAULT -1,"
-                    + SubscriptionManager.WFC_IMS_ROAMING_MODE + " INTEGER DEFAULT -1,"
-                    + SubscriptionManager.WFC_IMS_ROAMING_ENABLED + " INTEGER DEFAULT -1,"
-                    + SubscriptionManager.IS_OPPORTUNISTIC + " INTEGER DEFAULT 0,"
-                    + SubscriptionManager.GROUP_UUID + " TEXT,"
-                    + SubscriptionManager.IS_METERED + " INTEGER DEFAULT 1,"
-                    + SubscriptionManager.ISO_COUNTRY_CODE + " TEXT,"
-                    + SubscriptionManager.CARRIER_ID + " INTEGER DEFAULT -1,"
-                    + SubscriptionManager.PROFILE_CLASS
-                    + " INTEGER DEFAULT " + SubscriptionManager.PROFILE_CLASS_DEFAULT + ","
-                    + SubscriptionManager.SUBSCRIPTION_TYPE + " INTEGER DEFAULT 0,"
-                    + SubscriptionManager.WHITE_LISTED_APN_DATA + " INTEGER DEFAULT 0,"
-                    + SubscriptionManager.GROUP_OWNER + " TEXT,"
-                    + SubscriptionManager.DATA_ENABLED_OVERRIDE_RULES + " TEXT,"
-                    + SubscriptionManager.IMSI + " TEXT,"
-                    + SubscriptionManager.ACCESS_RULES_FROM_CARRIER_CONFIGS + " BLOB"
+                    + Telephony.SimInfo.DISPLAY_NUMBER_DEFAULT + ","
+                    + Telephony.SimInfo.COLUMN_DATA_ROAMING
+                    + " INTEGER DEFAULT " + Telephony.SimInfo.DATA_ROAMING_DISABLE + ","
+                    + Telephony.SimInfo.COLUMN_MCC + " INTEGER DEFAULT 0,"
+                    + Telephony.SimInfo.COLUMN_MNC + " INTEGER DEFAULT 0,"
+                    + Telephony.SimInfo.COLUMN_MCC_STRING + " TEXT,"
+                    + Telephony.SimInfo.COLUMN_MNC_STRING + " TEXT,"
+                    + Telephony.SimInfo.COLUMN_EHPLMNS + " TEXT,"
+                    + Telephony.SimInfo.COLUMN_HPLMNS + " TEXT,"
+                    + Telephony.SimInfo.COLUMN_SIM_PROVISIONING_STATUS
+                    + " INTEGER DEFAULT " + Telephony.SimInfo.SIM_PROVISIONED + ","
+                    + Telephony.SimInfo.COLUMN_IS_EMBEDDED + " INTEGER DEFAULT 0,"
+                    + Telephony.SimInfo.COLUMN_CARD_ID + " TEXT NOT NULL,"
+                    + Telephony.SimInfo.COLUMN_ACCESS_RULES + " BLOB,"
+                    + Telephony.SimInfo.COLUMN_IS_REMOVABLE + " INTEGER DEFAULT 0,"
+                    + Telephony.SimInfo.COLUMN_CB_EXTREME_THREAT_ALERT + " INTEGER DEFAULT 1,"
+                    + Telephony.SimInfo.COLUMN_CB_SEVERE_THREAT_ALERT + " INTEGER DEFAULT 1,"
+                    + Telephony.SimInfo.COLUMN_CB_AMBER_ALERT + " INTEGER DEFAULT 1,"
+                    + Telephony.SimInfo.COLUMN_CB_EMERGENCY_ALERT + " INTEGER DEFAULT 1,"
+                    + Telephony.SimInfo.COLUMN_CB_ALERT_SOUND_DURATION + " INTEGER DEFAULT 4,"
+                    + Telephony.SimInfo.COLUMN_CB_ALERT_REMINDER_INTERVAL + " INTEGER DEFAULT 0,"
+                    + Telephony.SimInfo.COLUMN_CB_ALERT_VIBRATE + " INTEGER DEFAULT 1,"
+                    + Telephony.SimInfo.COLUMN_CB_ALERT_SPEECH + " INTEGER DEFAULT 1,"
+                    + Telephony.SimInfo.COLUMN_CB_ETWS_TEST_ALERT + " INTEGER DEFAULT 0,"
+                    + Telephony.SimInfo.COLUMN_CB_CHANNEL_50_ALERT + " INTEGER DEFAULT 1,"
+                    + Telephony.SimInfo.COLUMN_CB_CMAS_TEST_ALERT + " INTEGER DEFAULT 0,"
+                    + Telephony.SimInfo.COLUMN_CB_OPT_OUT_DIALOG + " INTEGER DEFAULT 1,"
+                    + Telephony.SimInfo.COLUMN_ENHANCED_4G_MODE_ENABLED + " INTEGER DEFAULT -1,"
+                    + Telephony.SimInfo.COLUMN_VT_IMS_ENABLED + " INTEGER DEFAULT -1,"
+                    + Telephony.SimInfo.COLUMN_WFC_IMS_ENABLED + " INTEGER DEFAULT -1,"
+                    + Telephony.SimInfo.COLUMN_WFC_IMS_MODE + " INTEGER DEFAULT -1,"
+                    + Telephony.SimInfo.COLUMN_WFC_IMS_ROAMING_MODE + " INTEGER DEFAULT -1,"
+                    + Telephony.SimInfo.COLUMN_WFC_IMS_ROAMING_ENABLED + " INTEGER DEFAULT -1,"
+                    + Telephony.SimInfo.COLUMN_IS_OPPORTUNISTIC + " INTEGER DEFAULT 0,"
+                    + Telephony.SimInfo.COLUMN_GROUP_UUID + " TEXT,"
+                    + Telephony.SimInfo.COLUMN_IS_METERED + " INTEGER DEFAULT 1,"
+                    + Telephony.SimInfo.COLUMN_ISO_COUNTRY_CODE + " TEXT,"
+                    + Telephony.SimInfo.COLUMN_CARRIER_ID + " INTEGER DEFAULT -1,"
+                    + Telephony.SimInfo.COLUMN_PROFILE_CLASS
+                    + " INTEGER DEFAULT " + Telephony.SimInfo.PROFILE_CLASS_UNSET + ","
+                    + Telephony.SimInfo.COLUMN_SUBSCRIPTION_TYPE + " INTEGER DEFAULT 0,"
+                    + Telephony.SimInfo.COLUMN_GROUP_OWNER + " TEXT,"
+                    + Telephony.SimInfo.COLUMN_DATA_ENABLED_OVERRIDE_RULES + " TEXT,"
+                    + Telephony.SimInfo.COLUMN_IMSI + " TEXT,"
+                    + Telephony.SimInfo.COLUMN_ACCESS_RULES_FROM_CARRIER_CONFIGS + " BLOB,"
+                    + Telephony.SimInfo.COLUMN_UICC_APPLICATIONS_ENABLED + " INTEGER DEFAULT 1,"
+                    + Telephony.SimInfo.COLUMN_ALLOWED_NETWORK_TYPES + " BIGINT DEFAULT -1, "
+                    + Telephony.SimInfo.COLUMN_IMS_RCS_UCE_ENABLED + " INTEGER DEFAULT 0"
                     + ");";
         }
 
@@ -134,7 +136,7 @@
     public Uri insert(Uri uri, ContentValues values) {
         SQLiteDatabase db = mDbHelper.getWritableDatabase();
         long id = db.insert("siminfo", null, values);
-        return ContentUris.withAppendedId(SubscriptionManager.CONTENT_URI, id);
+        return ContentUris.withAppendedId(Telephony.SimInfo.CONTENT_URI, id);
     }
 
     @Override
diff --git a/tests/telephonytests/src/com/android/internal/telephony/GsmCdmaCallTrackerTest.java b/tests/telephonytests/src/com/android/internal/telephony/GsmCdmaCallTrackerTest.java
index ecb6e63..c0b07a8 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/GsmCdmaCallTrackerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/GsmCdmaCallTrackerTest.java
@@ -15,8 +15,6 @@
  */
 package com.android.internal.telephony;
 
-import static com.android.internal.telephony.TelephonyTestUtils.waitForMs;
-
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.fail;
@@ -30,16 +28,16 @@
 
 import android.os.Bundle;
 import android.os.Handler;
-import android.os.HandlerThread;
 import android.os.Message;
 import android.telephony.DisconnectCause;
 import android.telephony.PhoneNumberUtils;
 import android.telephony.ServiceState;
 import android.test.suitebuilder.annotation.MediumTest;
 import android.test.suitebuilder.annotation.SmallTest;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
 
 import androidx.test.filters.FlakyTest;
-import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.After;
 import org.junit.Assert;
@@ -50,53 +48,36 @@
 import org.mockito.ArgumentCaptor;
 import org.mockito.Mock;
 
-@RunWith(AndroidJUnit4.class)
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper
 public class GsmCdmaCallTrackerTest extends TelephonyTest {
     private static final int VOICE_CALL_STARTED_EVENT = 0;
     private static final int VOICE_CALL_ENDED_EVENT = 1;
-    private static final int TEST_TIMEOUT = 5000;
     private String mDialString = PhoneNumberUtils.stripSeparators("+17005554141");
     /* Handler class initiated at the HandlerThread */
     private GsmCdmaCallTracker mCTUT;
-    private GsmCdmaCTHandlerThread mGsmCdmaCTHandlerThread;
     @Mock
     GsmCdmaConnection mConnection;
     @Mock
     private Handler mHandler;
 
-    private class GsmCdmaCTHandlerThread extends HandlerThread {
-
-        private GsmCdmaCTHandlerThread(String name) {
-            super(name);
-        }
-        @Override
-        public void onLooperPrepared() {
-            mCTUT = new GsmCdmaCallTracker(mPhone);
-            setReady(true);
-        }
-    }
-
     @Before
     public void setUp() throws Exception {
         super.setUp(this.getClass().getSimpleName());
         mSimulatedCommands.setRadioPower(true, null);
         mPhone.mCi = this.mSimulatedCommands;
 
-        mGsmCdmaCTHandlerThread = new GsmCdmaCTHandlerThread(TAG);
-        mGsmCdmaCTHandlerThread.start();
-
-        waitUntilReady();
+        mCTUT = new GsmCdmaCallTracker(mPhone);
         logd("GsmCdmaCallTracker initiated, waiting for Power on");
         /* Make sure radio state is power on before dial.
          * When radio state changed from off to on, CallTracker
          * will poll result from RIL. Avoid dialing triggered at the same*/
-        waitForMs(100);
+        processAllMessages();
     }
 
     @After
     public void tearDown() throws Exception {
         mCTUT = null;
-        mGsmCdmaCTHandlerThread.quit();
         super.tearDown();
     }
 
@@ -110,7 +91,7 @@
         assertEquals(0, mCTUT.mForegroundCall.getConnections().size());
         try {
             mCTUT.dial(mDialString, new Bundle());
-            waitForMs(100);
+            processAllMessages();
         } catch(Exception ex) {
             ex.printStackTrace();
             Assert.fail("unexpected exception thrown"+ex.getMessage()+ex.getStackTrace());
@@ -130,10 +111,10 @@
     public void testMOCallPickUp() {
         testMOCallDial();
         logd("Waiting for POLL CALL response from RIL");
-        TelephonyTestUtils.waitForMs(50);
+        processAllMessages();
         logd("Pick Up MO call, expecting call state change event ");
         mSimulatedCommands.progressConnectingToActive();
-        waitForMs(100);
+        processAllMessages();
         assertEquals(GsmCdmaCall.State.ACTIVE, mCTUT.mForegroundCall.getState());
         assertEquals(GsmCdmaCall.State.IDLE, mCTUT.mBackgroundCall.getState());
     }
@@ -145,7 +126,7 @@
     public void testMOCallHangup() {
         testMOCallDial();
         logd("Waiting for POLL CALL response from RIL ");
-        TelephonyTestUtils.waitForMs(50);
+        processAllMessages();
         assertEquals(GsmCdmaCall.State.DIALING, mCTUT.mForegroundCall.getState());
         assertEquals(PhoneConstants.State.OFFHOOK, mCTUT.getState());
         assertEquals(1, mCTUT.mForegroundCall.getConnections().size());
@@ -156,7 +137,7 @@
             ex.printStackTrace();
             Assert.fail("unexpected exception thrown" + ex.getMessage());
         }
-        waitForMs(300);
+        processAllMessages();
         assertEquals(GsmCdmaCall.State.IDLE, mCTUT.mForegroundCall.getState());
         assertEquals(0, mCTUT.mForegroundCall.getConnections().size());
         assertEquals(PhoneConstants.State.IDLE, mCTUT.getState());
@@ -181,8 +162,7 @@
             ex.printStackTrace();
             Assert.fail("unexpected exception thrown" + ex.getMessage());
         }
-        /* request send to RIL still in disconnecting state */
-        waitForMs(300);
+        processAllMessages();
         assertEquals(GsmCdmaCall.State.IDLE, mCTUT.mForegroundCall.getState());
         assertEquals(0, mCTUT.mForegroundCall.getConnections().size());
         assertEquals(PhoneConstants.State.IDLE, mCTUT.getState());
@@ -205,7 +185,7 @@
             ex.printStackTrace();
             Assert.fail("unexpected exception thrown" + ex.getMessage());
         }
-        waitForMs(300);
+        processAllMessages();
         assertEquals(GsmCdmaCall.State.IDLE, mCTUT.mForegroundCall.getState());
         assertEquals(0, mCTUT.mForegroundCall.getConnections().size());
         assertEquals(PhoneConstants.State.IDLE, mCTUT.getState());
@@ -228,7 +208,7 @@
             ex.printStackTrace();
             Assert.fail("unexpected exception thrown" + ex.getMessage());
         }
-        waitForMs(200);
+        processAllMessages();
         assertEquals(GsmCdmaCall.State.DIALING, mCTUT.mForegroundCall.getState());
         assertEquals(GsmCdmaCall.State.HOLDING, mCTUT.mBackgroundCall.getState());
         assertEquals(1, mCTUT.mForegroundCall.getConnections().size());
@@ -254,10 +234,8 @@
         String mDialString = PhoneNumberUtils.stripSeparators("+17005554141");
         logd("MT call Ringing");
         mSimulatedCommands.triggerRing(mDialString);
-        // handle EVENT_CALL_STATE_CHANGE
-        waitForHandlerAction(mCTUT, TEST_TIMEOUT);
-        // handle EVENT_POLL_CALLS_RESULT
-        waitForHandlerAction(mCTUT, TEST_TIMEOUT);
+        // handle EVENT_CALL_STATE_CHANGE, EVENT_POLL_CALLS_RESULT
+        processAllMessages();
         assertEquals(PhoneConstants.State.RINGING, mCTUT.getState());
         assertEquals(1, mCTUT.mRingingCall.getConnections().size());
     }
@@ -281,9 +259,7 @@
         /* send to the RIL */
         verify(mSimulatedCommandsVerifier).acceptCall(isA(Message.class));
         // handle EVENT_OPERATION_COMPLETE
-        waitForHandlerAction(mCTUT, TEST_TIMEOUT);
-        // handle waitForHandlerAction
-        waitForHandlerAction(mCTUT, TEST_TIMEOUT);
+        processAllMessages();
         assertEquals(PhoneConstants.State.OFFHOOK, mCTUT.getState());
         assertEquals(GsmCdmaCall.State.ACTIVE, mCTUT.mForegroundCall.getState());
         assertEquals(1, mCTUT.mForegroundCall.getConnections().size());
@@ -309,14 +285,12 @@
             ex.printStackTrace();
             Assert.fail("unexpected exception thrown" + ex.getMessage());
         }
-        // handle EVENT_OPERATION_COMPLETE
-        waitForHandlerAction(mCTUT, TEST_TIMEOUT);
-        // handle EVENT_POLL_CALLS_RESULT
-        waitForHandlerAction(mCTUT, TEST_TIMEOUT);
+        // handle EVENT_OPERATION_COMPLETE, EVENT_POLL_CALLS_RESULT
+        processAllMessages();
         assertEquals(PhoneConstants.State.IDLE, mCTUT.getState());
         assertEquals(GsmCdmaCall.State.IDLE, mCTUT.mForegroundCall.getState());
         assertEquals(0, mCTUT.mForegroundCall.getConnections().size());
-        /* ? why rejectCall didnt -> hang up locally to set the cause to LOCAL? */
+        /* ? why rejectCall didn't -> hang up locally to set the cause to LOCAL? */
         assertEquals(DisconnectCause.INCOMING_MISSED, connection.getDisconnectCause());
 
     }
@@ -334,7 +308,7 @@
             ex.printStackTrace();
             Assert.fail("unexpected exception thrown" + ex.getMessage());
         }
-        waitForMs(300);
+        processAllMessages();
         logd(" Foreground Call is IDLE and BackGround Call is still HOLDING ");
         /* if we want to hang up foreground call which is alerting state, hangup all */
         assertEquals(GsmCdmaCall.State.IDLE, mCTUT.mForegroundCall.getState());
@@ -355,7 +329,7 @@
             Assert.fail("unexpected exception thrown" + ex.getMessage());
         }
 
-        waitForMs(200);
+        processAllMessages();
         assertEquals(GsmCdmaCall.State.ACTIVE, mCTUT.mForegroundCall.getState());
         assertEquals(GsmCdmaCall.State.HOLDING, mCTUT.mBackgroundCall.getState());
 
@@ -366,8 +340,7 @@
             ex.printStackTrace();
             Assert.fail("unexpected exception thrown" + ex.getMessage());
         }
-
-        waitForMs(300);
+        processAllMessages();
         logd(" BackGround Call switch to ForeGround Call ");
         assertEquals(GsmCdmaCall.State.ACTIVE, mCTUT.mForegroundCall.getState());
         assertEquals(GsmCdmaCall.State.IDLE, mCTUT.mBackgroundCall.getState());
@@ -407,14 +380,8 @@
         verify(mSimulatedCommandsVerifier).getCurrentCalls(any(Message.class));
 
         // update phone type (call the function on same thread as the call tracker)
-        Handler updatePhoneTypeHandler = new Handler(mCTUT.getLooper()) {
-            @Override
-            public void handleMessage(Message msg) {
-                mCTUT.updatePhoneType();
-            }
-        };
-        updatePhoneTypeHandler.sendEmptyMessage(0);
-        waitForMs(100);
+        mCTUT.updatePhoneType();
+        processAllMessages();
 
         // verify getCurrentCalls is called on updating phone type
         verify(mSimulatedCommandsVerifier, times(2)).getCurrentCalls(any(Message.class));
@@ -428,8 +395,8 @@
 
         // update phone type - call tracker goes to IDLE and then due to getCurrentCalls(),
         // goes back to OFFHOOK
-        updatePhoneTypeHandler.sendEmptyMessage(0);
-        waitForMs(100);
+        mCTUT.updatePhoneType();
+        processAllMessages();
 
         // verify CT and calls go to idle
         assertEquals(PhoneConstants.State.OFFHOOK, mCTUT.getState());
@@ -448,14 +415,8 @@
         mCTUT.mConnections[0] = mConnection;
 
         // update phone type (call the function on same thread as the call tracker)
-        Handler updatePhoneTypeHandler = new Handler(mCTUT.getLooper()) {
-            @Override
-            public void handleMessage(Message msg) {
-                mCTUT.updatePhoneType();
-            }
-        };
-        updatePhoneTypeHandler.sendEmptyMessage(0);
-        waitForMs(100);
+        mCTUT.updatePhoneType();
+        processAllMessages();
 
         // verify that the active call is disconnected
         verify(mConnection).onDisconnect(DisconnectCause.ERROR_UNSPECIFIED);
@@ -483,9 +444,9 @@
     public void testCantCallOtaspInProgress() {
         mDialString = "*22899";
         testMOCallDial();
-        waitForHandlerAction(mSimulatedCommands.getHandler(), 5000);
+        processAllMessages();
         mSimulatedCommands.progressConnectingToActive();
-        waitForHandlerAction(mSimulatedCommands.getHandler(), 5000);
+        processAllMessages();
         // Try to place another call.
         try {
             mCTUT.dial("650-555-1212", new Bundle());
diff --git a/tests/telephonytests/src/com/android/internal/telephony/GsmCdmaConnectionTest.java b/tests/telephonytests/src/com/android/internal/telephony/GsmCdmaConnectionTest.java
index 6fc9156..93e3d87 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/GsmCdmaConnectionTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/GsmCdmaConnectionTest.java
@@ -16,21 +16,28 @@
 
 package com.android.internal.telephony;
 
-import android.os.Handler;
-import android.os.Looper;
-import android.telephony.PhoneNumberUtils;
-import android.test.suitebuilder.annotation.SmallTest;
-import android.test.suitebuilder.annotation.MediumTest;
-import android.telephony.DisconnectCause;
+import static com.android.internal.telephony.TelephonyTestUtils.waitForMs;
 
 import static org.junit.Assert.*;
 import static org.mockito.Mockito.*;
+
+import android.os.Handler;
+import android.os.Looper;
+import android.telephony.DisconnectCause;
+import android.telephony.PhoneNumberUtils;
+import android.test.suitebuilder.annotation.MediumTest;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
+import org.junit.runner.RunWith;
 import org.mockito.Mock;
-import static com.android.internal.telephony.TelephonyTestUtils.waitForMs;
 
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper
 public class GsmCdmaConnectionTest extends TelephonyTest {
 
     private GsmCdmaConnection connection;
@@ -41,7 +48,7 @@
     @Before
     public void setUp() throws Exception {
         super.setUp(getClass().getSimpleName());
-        replaceInstance(Handler.class, "mLooper", mCT, Looper.getMainLooper());
+        replaceInstance(Handler.class, "mLooper", mCT, Looper.myLooper());
 
         mCT.mForegroundCall = new GsmCdmaCall(mCT);
         mCT.mBackgroundCall = new GsmCdmaCall(mCT);
@@ -131,7 +138,9 @@
         mDC.state = DriverCall.State.HOLDING;
         connection.update(mDC);
         assertEquals(GsmCdmaCall.State.HOLDING, connection.getState());
+        // getHoldDurationMillis() calculated using System.currentTimeMillis()
         waitForMs(50);
+        processAllMessages();
         assertTrue(connection.getHoldDurationMillis() >= 50);
     }
 
@@ -148,8 +157,9 @@
         connection.update(mDC);
         logd("process post dail sequence with pause");
         assertEquals(Connection.PostDialState.PAUSE, connection.getPostDialState());
-        /* pause for 2000 ms + 50ms margin */
-        waitForMs(GsmCdmaConnection.PAUSE_DELAY_MILLIS_CDMA + 100);
+        /* pause for 2000 ms */
+        moveTimeForward(GsmCdmaConnection.PAUSE_DELAY_MILLIS_CDMA);
+        processAllMessages();
         assertEquals(Connection.PostDialState.COMPLETE, connection.getPostDialState());
     }
 
@@ -165,8 +175,9 @@
         connection.update(mDC);
         logd("process post dail sequence with pause");
         assertEquals(Connection.PostDialState.STARTED, connection.getPostDialState());
-        /* pause for 2000 ms + 50ms margin */
-        waitForMs(GsmCdmaConnection.PAUSE_DELAY_MILLIS_GSM + 100);
+        /* pause for 2000 ms */
+        moveTimeForward(GsmCdmaConnection.PAUSE_DELAY_MILLIS_GSM);
+        processAllMessages();
         assertEquals(Connection.PostDialState.COMPLETE, connection.getPostDialState());
     }
 
@@ -185,7 +196,7 @@
         logd("Process the post dial sequence with wait ");
         assertEquals(Connection.PostDialState.WAIT, connection.getPostDialState());
         connection.proceedAfterWaitChar();
-        waitForMs(50);
+        processAllMessages();
         assertEquals(Connection.PostDialState.COMPLETE, connection.getPostDialState());
     }
 
diff --git a/tests/telephonytests/src/com/android/internal/telephony/GsmCdmaPhoneTest.java b/tests/telephonytests/src/com/android/internal/telephony/GsmCdmaPhoneTest.java
index a26967c..562a608 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/GsmCdmaPhoneTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/GsmCdmaPhoneTest.java
@@ -18,52 +18,59 @@
 
 import static com.android.internal.telephony.CommandsInterface.CF_ACTION_ENABLE;
 import static com.android.internal.telephony.CommandsInterface.CF_REASON_UNCONDITIONAL;
+import static com.android.internal.telephony.Phone.EVENT_ICC_CHANGED;
+import static com.android.internal.telephony.Phone.EVENT_SRVCC_STATE_CHANGED;
+import static com.android.internal.telephony.Phone.EVENT_UICC_APPS_ENABLEMENT_STATUS_CHANGED;
 import static com.android.internal.telephony.TelephonyTestUtils.waitForMs;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
+import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.Matchers.anyLong;
 import static org.mockito.Matchers.nullable;
 import static org.mockito.Mockito.anyBoolean;
 import static org.mockito.Mockito.anyInt;
 import static org.mockito.Mockito.atLeast;
+import static org.mockito.Mockito.clearInvocations;
 import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.doThrow;
 import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.inOrder;
 import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 
-import android.app.Activity;
-import android.app.IApplicationThread;
-import android.content.IIntentReceiver;
 import android.content.Intent;
 import android.content.SharedPreferences;
 import android.os.AsyncResult;
-import android.os.Bundle;
 import android.os.Handler;
-import android.os.HandlerThread;
 import android.os.Message;
 import android.os.Process;
 import android.os.WorkSource;
 import android.preference.PreferenceManager;
+import android.telecom.VideoProfile;
 import android.telephony.AccessNetworkConstants;
 import android.telephony.CarrierConfigManager;
-import android.telephony.CellLocation;
+import android.telephony.CellIdentity;
+import android.telephony.CellIdentityCdma;
+import android.telephony.CellIdentityGsm;
 import android.telephony.NetworkRegistrationInfo;
 import android.telephony.ServiceState;
 import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyManager;
-import android.telephony.cdma.CdmaCellLocation;
-import android.telephony.gsm.GsmCellLocation;
 import android.test.suitebuilder.annotation.SmallTest;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
 
 import androidx.test.filters.FlakyTest;
 
 import com.android.internal.telephony.test.SimulatedCommands;
 import com.android.internal.telephony.uicc.IccCardApplicationStatus;
+import com.android.internal.telephony.uicc.IccCardStatus;
 import com.android.internal.telephony.uicc.IccRecords;
 import com.android.internal.telephony.uicc.IccVmNotSupportedException;
 import com.android.internal.telephony.uicc.UiccController;
@@ -74,44 +81,36 @@
 import org.junit.Before;
 import org.junit.Ignore;
 import org.junit.Test;
+import org.junit.runner.RunWith;
 import org.mockito.ArgumentCaptor;
+import org.mockito.InOrder;
 import org.mockito.Mock;
 import org.mockito.Mockito;
 
 import java.util.List;
 
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper
 public class GsmCdmaPhoneTest extends TelephonyTest {
     @Mock
     private Handler mTestHandler;
+    @Mock
+    private UiccSlot mUiccSlot;
+    @Mock
+    private CommandsInterface mMockCi;
 
     //mPhoneUnderTest
     private GsmCdmaPhone mPhoneUT;
-    private GsmCdmaPhoneTestHandler mGsmCdmaPhoneTestHandler;
 
     private static final int EVENT_EMERGENCY_CALLBACK_MODE_EXIT = 1;
     private static final int EVENT_EMERGENCY_CALL_TOGGLE = 2;
     private static final int EVENT_SET_ICC_LOCK_ENABLED = 3;
 
-    private class GsmCdmaPhoneTestHandler extends HandlerThread {
-
-        private GsmCdmaPhoneTestHandler(String name) {
-            super(name);
-        }
-
-        @Override
-        public void onLooperPrepared() {
-            mPhoneUT = new GsmCdmaPhone(mContext, mSimulatedCommands, mNotifier, true, 0,
-                    PhoneConstants.PHONE_TYPE_GSM, mTelephonyComponentFactory);
-            setReady(true);
-        }
-    }
-
     private void switchToGsm() {
         mSimulatedCommands.setVoiceRadioTech(ServiceState.RIL_RADIO_TECHNOLOGY_GSM);
         mPhoneUT.sendMessage(mPhoneUT.obtainMessage(GsmCdmaPhone.EVENT_VOICE_RADIO_TECH_CHANGED,
                 new AsyncResult(null, new int[]{ServiceState.RIL_RADIO_TECHNOLOGY_GSM}, null)));
-        //wait for voice RAT to be updated
-        waitForMs(50);
+        processAllMessages();
         assertEquals(PhoneConstants.PHONE_TYPE_GSM, mPhoneUT.getPhoneType());
     }
 
@@ -119,8 +118,7 @@
         mSimulatedCommands.setVoiceRadioTech(ServiceState.RIL_RADIO_TECHNOLOGY_IS95A);
         mPhoneUT.sendMessage(mPhoneUT.obtainMessage(GsmCdmaPhone.EVENT_VOICE_RADIO_TECH_CHANGED,
                 new AsyncResult(null, new int[]{ServiceState.RIL_RADIO_TECHNOLOGY_IS95A}, null)));
-        //wait for voice RAT to be updated
-        waitForMs(100);
+        processAllMessages();
         assertEquals(PhoneConstants.PHONE_TYPE_CDMA, mPhoneUT.getPhoneType());
     }
 
@@ -130,24 +128,22 @@
 
         doReturn(false).when(mSST).isDeviceShuttingDown();
 
-        mGsmCdmaPhoneTestHandler = new GsmCdmaPhoneTestHandler(TAG);
-        mGsmCdmaPhoneTestHandler.start();
-        waitUntilReady();
+        mPhoneUT = new GsmCdmaPhone(mContext, mSimulatedCommands, mNotifier, true, 0,
+            PhoneConstants.PHONE_TYPE_GSM, mTelephonyComponentFactory);
+        mPhoneUT.setVoiceCallSessionStats(mVoiceCallSessionStats);
         ArgumentCaptor<Integer> integerArgumentCaptor = ArgumentCaptor.forClass(Integer.class);
         verify(mUiccController).registerForIccChanged(eq(mPhoneUT), integerArgumentCaptor.capture(),
                 nullable(Object.class));
         Message msg = Message.obtain();
         msg.what = integerArgumentCaptor.getValue();
         mPhoneUT.sendMessage(msg);
-        waitForMs(50);
+        processAllMessages();
     }
 
     @After
     public void tearDown() throws Exception {
         mPhoneUT.removeCallbacksAndMessages(null);
         mPhoneUT = null;
-        mGsmCdmaPhoneTestHandler.quit();
-        mGsmCdmaPhoneTestHandler.join();
         super.tearDown();
     }
 
@@ -172,14 +168,6 @@
     public void testGetMergedServiceState() throws Exception {
         ServiceState imsServiceState = new ServiceState();
 
-        NetworkRegistrationInfo imsCsWwanRegInfo = new NetworkRegistrationInfo.Builder()
-                .setDomain(NetworkRegistrationInfo.DOMAIN_CS)
-                .setTransportType(AccessNetworkConstants.TRANSPORT_TYPE_WWAN)
-                .setAccessNetworkTechnology(TelephonyManager.NETWORK_TYPE_LTE)
-                .setRegistrationState(
-                        NetworkRegistrationInfo.REGISTRATION_STATE_HOME)
-                .build();
-
         NetworkRegistrationInfo imsPsWwanRegInfo = new NetworkRegistrationInfo.Builder()
                 .setDomain(NetworkRegistrationInfo.DOMAIN_PS)
                 .setTransportType(AccessNetworkConstants.TRANSPORT_TYPE_WWAN)
@@ -196,10 +184,11 @@
                         NetworkRegistrationInfo.REGISTRATION_STATE_HOME)
                 .build();
 
-        imsServiceState.addNetworkRegistrationInfo(imsCsWwanRegInfo);
+        // Only PS states are tracked for IMS.
         imsServiceState.addNetworkRegistrationInfo(imsPsWwanRegInfo);
         imsServiceState.addNetworkRegistrationInfo(imsPsWlanRegInfo);
 
+        // Voice reg state in this case is whether or not IMS is registered.
         imsServiceState.setVoiceRegState(ServiceState.STATE_IN_SERVICE);
         imsServiceState.setDataRegState(ServiceState.STATE_IN_SERVICE);
         imsServiceState.setIwlanPreferred(true);
@@ -245,11 +234,93 @@
 
         ServiceState mergedServiceState = mPhoneUT.getServiceState();
 
-        assertEquals(ServiceState.STATE_IN_SERVICE, mergedServiceState.getVoiceRegState());
-        assertEquals(ServiceState.STATE_IN_SERVICE, mergedServiceState.getDataRegState());
+        assertEquals(ServiceState.STATE_IN_SERVICE, mergedServiceState.getState());
+        assertEquals(ServiceState.STATE_IN_SERVICE, mergedServiceState.getDataRegistrationState());
         assertEquals(TelephonyManager.NETWORK_TYPE_IWLAN, mergedServiceState.getDataNetworkType());
     }
 
+    /**
+     * Some vendors do not provide a voice registration for LTE when attached to LTE only (no CSFB
+     * available). In this case, we should still get IN_SERVICE for voice service state, since
+     * IMS is registered.
+     */
+    @Test
+    @SmallTest
+    public void testGetMergedServiceStateNoCsfb() throws Exception {
+        ServiceState imsServiceState = new ServiceState();
+
+        NetworkRegistrationInfo imsPsWwanRegInfo = new NetworkRegistrationInfo.Builder()
+                .setDomain(NetworkRegistrationInfo.DOMAIN_PS)
+                .setTransportType(AccessNetworkConstants.TRANSPORT_TYPE_WWAN)
+                .setAccessNetworkTechnology(TelephonyManager.NETWORK_TYPE_LTE)
+                .setRegistrationState(
+                        NetworkRegistrationInfo.REGISTRATION_STATE_HOME)
+                .build();
+
+        NetworkRegistrationInfo imsPsWlanRegInfo = new NetworkRegistrationInfo.Builder()
+                .setDomain(NetworkRegistrationInfo.DOMAIN_PS)
+                .setTransportType(AccessNetworkConstants.TRANSPORT_TYPE_WLAN)
+                .setAccessNetworkTechnology(TelephonyManager.NETWORK_TYPE_IWLAN)
+                .setRegistrationState(
+                        NetworkRegistrationInfo.REGISTRATION_STATE_NOT_REGISTERED_OR_SEARCHING)
+                .build();
+
+        // Only PS states are tracked for IMS.
+        imsServiceState.addNetworkRegistrationInfo(imsPsWwanRegInfo);
+        imsServiceState.addNetworkRegistrationInfo(imsPsWlanRegInfo);
+
+        // Voice reg state in this case is whether or not IMS is registered.
+        imsServiceState.setVoiceRegState(ServiceState.STATE_IN_SERVICE);
+        imsServiceState.setDataRegState(ServiceState.STATE_IN_SERVICE);
+        imsServiceState.setIwlanPreferred(true);
+        doReturn(imsServiceState).when(mImsPhone).getServiceState();
+
+        replaceInstance(Phone.class, "mImsPhone", mPhoneUT, mImsPhone);
+
+        ServiceState serviceState = new ServiceState();
+
+        NetworkRegistrationInfo csWwanRegInfo = new NetworkRegistrationInfo.Builder()
+                .setDomain(NetworkRegistrationInfo.DOMAIN_CS)
+                .setTransportType(AccessNetworkConstants.TRANSPORT_TYPE_WWAN)
+                .setAccessNetworkTechnology(TelephonyManager.NETWORK_TYPE_UNKNOWN)
+                .setRegistrationState(
+                        NetworkRegistrationInfo.REGISTRATION_STATE_NOT_REGISTERED_OR_SEARCHING)
+                .build();
+
+        NetworkRegistrationInfo psWwanRegInfo = new NetworkRegistrationInfo.Builder()
+                .setDomain(NetworkRegistrationInfo.DOMAIN_PS)
+                .setTransportType(AccessNetworkConstants.TRANSPORT_TYPE_WWAN)
+                .setAccessNetworkTechnology(TelephonyManager.NETWORK_TYPE_LTE)
+                .setRegistrationState(
+                        NetworkRegistrationInfo.REGISTRATION_STATE_HOME)
+                .build();
+
+        NetworkRegistrationInfo psWlanRegInfo = new NetworkRegistrationInfo.Builder()
+                .setDomain(NetworkRegistrationInfo.DOMAIN_PS)
+                .setTransportType(AccessNetworkConstants.TRANSPORT_TYPE_WLAN)
+                .setAccessNetworkTechnology(TelephonyManager.NETWORK_TYPE_IWLAN)
+                .setRegistrationState(
+                        NetworkRegistrationInfo.REGISTRATION_STATE_NOT_REGISTERED_OR_SEARCHING)
+                .build();
+
+        serviceState.addNetworkRegistrationInfo(csWwanRegInfo);
+        serviceState.addNetworkRegistrationInfo(psWwanRegInfo);
+        serviceState.addNetworkRegistrationInfo(psWlanRegInfo);
+        // No CSFB, voice is OOS for LTE only attach
+        serviceState.setVoiceRegState(ServiceState.STATE_OUT_OF_SERVICE);
+        serviceState.setDataRegState(ServiceState.STATE_IN_SERVICE);
+        serviceState.setIwlanPreferred(true);
+
+        mSST.mSS = serviceState;
+        mPhoneUT.mSST = mSST;
+
+        ServiceState mergedServiceState = mPhoneUT.getServiceState();
+
+        assertEquals(ServiceState.STATE_IN_SERVICE, mergedServiceState.getState());
+        assertEquals(ServiceState.STATE_IN_SERVICE, mergedServiceState.getDataRegistrationState());
+        assertEquals(TelephonyManager.NETWORK_TYPE_LTE, mergedServiceState.getDataNetworkType());
+    }
+
     @Test
     @SmallTest
     public void testGetSubscriberIdForGsmPhone() {
@@ -305,20 +376,20 @@
     @SmallTest
     public void testGetCellLocation() {
         // GSM
-        CellLocation cellLocation = new GsmCellLocation();
+        CellIdentity cellLocation = new CellIdentityGsm();
         WorkSource workSource = new WorkSource(Process.myUid(),
             mContext.getPackageName());
-        doReturn(cellLocation).when(mSST).getCellLocation();
-        assertEquals(cellLocation, mPhoneUT.getCellLocation());
+        doReturn(cellLocation).when(mSST).getCellIdentity();
+        assertEquals(cellLocation, mPhoneUT.getCellIdentity());
 
         // Switch to CDMA
         switchToCdma();
 
-        CdmaCellLocation cdmaCellLocation = new CdmaCellLocation();
-        doReturn(cdmaCellLocation).when(mSST).getCellLocation();
+        CellIdentityCdma cdmaCellLocation = new CellIdentityCdma();
+        doReturn(cdmaCellLocation).when(mSST).getCellIdentity();
 
-        CdmaCellLocation actualCellLocation =
-                (CdmaCellLocation) mPhoneUT.getCellLocation();
+        CellIdentityCdma actualCellLocation =
+                (CellIdentityCdma) mPhoneUT.getCellIdentity();
 
         assertEquals(actualCellLocation, cdmaCellLocation);
     }
@@ -401,7 +472,7 @@
 
     @Test
     @SmallTest
-    public void testDial() {
+    public void testDial() throws Exception {
         try {
             mSST.mSS = mServiceState;
             doReturn(ServiceState.STATE_IN_SERVICE).when(mServiceState).getState();
@@ -411,6 +482,8 @@
             mCT.mRingingCall = mGsmCdmaCall;
             doReturn(GsmCdmaCall.State.IDLE).when(mGsmCdmaCall).getState();
 
+            replaceInstance(Phone.class, "mImsPhone", mPhoneUT, mImsPhone);
+
             Connection connection = mPhoneUT.dial("1234567890",
                     new PhoneInternalInterface.DialArgs.Builder().build());
             verify(mCT).dialGsm("1234567890", null, null);
@@ -430,25 +503,28 @@
     public void testEmergencySmsMode() {
         String emergencyNumber = "111";
         String nonEmergencyNumber = "222";
+        int timeout = 200;
         mContextFixture.getCarrierConfigBundle().putInt(
-                CarrierConfigManager.KEY_EMERGENCY_SMS_MODE_TIMER_MS_INT, 200);
+                CarrierConfigManager.KEY_EMERGENCY_SMS_MODE_TIMER_MS_INT, timeout);
         doReturn(true).when(mTelephonyManager).isEmergencyNumber(emergencyNumber);
 
         mPhoneUT.notifySmsSent(nonEmergencyNumber);
-        waitForMs(50);
+        processAllMessages();
         assertFalse(mPhoneUT.isInEmergencySmsMode());
 
         mPhoneUT.notifySmsSent(emergencyNumber);
-        waitForMs(50);
+        processAllMessages();
         assertTrue(mPhoneUT.isInEmergencySmsMode());
-        waitForMs(200);
+        // mTimeLastEmergencySmsSentMs uses System.currentTimeMillis()
+        waitForMs(timeout + 5);
+        processAllMessages();
         assertFalse(mPhoneUT.isInEmergencySmsMode());
 
         // Feature not supported
         mContextFixture.getCarrierConfigBundle().putInt(
                 CarrierConfigManager.KEY_EMERGENCY_SMS_MODE_TIMER_MS_INT, 0);
         mPhoneUT.notifySmsSent(emergencyNumber);
-        waitForMs(50);
+        processAllMessages();
         assertFalse(mPhoneUT.isInEmergencySmsMode());
     }
 
@@ -510,11 +586,25 @@
         mContextFixture.getCarrierConfigBundle()
                 .putString(CarrierConfigManager.KEY_DEFAULT_VM_NUMBER_ROAMING_STRING,
                         voiceMailNumberForRoaming);
+
+        // voicemail number from config for roaming network and ims unregistered
+        String voiceMailNumberForImsRoamingAndUnregistered = "1234567893";
+        mContextFixture.getCarrierConfigBundle().putString(
+                CarrierConfigManager.KEY_DEFAULT_VM_NUMBER_ROAMING_AND_IMS_UNREGISTERED_STRING,
+                        voiceMailNumberForImsRoamingAndUnregistered);
+
         //Verify voicemail number for home
         doReturn(false).when(mSST.mSS).getRoaming();
+        doReturn(true).when(mSST).isImsRegistered();
+        assertEquals(voiceMailNumber, mPhoneUT.getVoiceMailNumber());
+        //Move to ims condition, verify voicemail number for ims unregistered
+        doReturn(false).when(mSST).isImsRegistered();
         assertEquals(voiceMailNumber, mPhoneUT.getVoiceMailNumber());
         //Move to roaming condition, verify voicemail number for roaming
         doReturn(true).when(mSST.mSS).getRoaming();
+        assertEquals(voiceMailNumberForImsRoamingAndUnregistered, mPhoneUT.getVoiceMailNumber());
+        //Move to ims condition, verify voicemail number for roaming
+        doReturn(true).when(mSST).isImsRegistered();
         assertEquals(voiceMailNumberForRoaming, mPhoneUT.getVoiceMailNumber());
         //Move to home condition, verify voicemail number for home
         doReturn(false).when(mSST.mSS).getRoaming();
@@ -554,11 +644,25 @@
         mContextFixture.getCarrierConfigBundle()
                 .putString(CarrierConfigManager.KEY_DEFAULT_VM_NUMBER_ROAMING_STRING,
                         voiceMailNumberForRoaming);
+
+        // voicemail number from config for roaming network and ims unregistered
+        String voiceMailNumberForImsRoamingAndUnregistered = "1234567893";
+        mContextFixture.getCarrierConfigBundle().putString(
+                CarrierConfigManager.KEY_DEFAULT_VM_NUMBER_ROAMING_AND_IMS_UNREGISTERED_STRING,
+                        voiceMailNumberForImsRoamingAndUnregistered);
+
         //Verify voicemail number for home
         doReturn(false).when(mSST.mSS).getRoaming();
+        doReturn(true).when(mSST).isImsRegistered();
+        assertEquals(voiceMailNumber, mPhoneUT.getVoiceMailNumber());
+        //Move to ims condition, verify voicemail number for ims unregistered
+        doReturn(false).when(mSST).isImsRegistered();
         assertEquals(voiceMailNumber, mPhoneUT.getVoiceMailNumber());
         //Move to roaming condition, verify voicemail number for roaming
         doReturn(true).when(mSST.mSS).getRoaming();
+        assertEquals(voiceMailNumberForImsRoamingAndUnregistered, mPhoneUT.getVoiceMailNumber());
+        //Move to ims condition, verify voicemail number for roaming
+        doReturn(true).when(mSST).isImsRegistered();
         assertEquals(voiceMailNumberForRoaming, mPhoneUT.getVoiceMailNumber());
         //Move to home condition, verify voicemail number for home
         doReturn(false).when(mSST.mSS).getRoaming();
@@ -568,7 +672,8 @@
         voiceMailNumber = "1234567893";
         mPhoneUT.setVoiceMailNumber("alphaTag", voiceMailNumber, null);
         ArgumentCaptor<Message> messageArgumentCaptor = ArgumentCaptor.forClass(Message.class);
-        verify(mSimRecords).setVoiceMailNumber(eq("alphaTag"), eq(voiceMailNumber),
+        InOrder inOrder = inOrder(mSimRecords);
+        inOrder.verify(mSimRecords).setVoiceMailNumber(eq("alphaTag"), eq(voiceMailNumber),
                 messageArgumentCaptor.capture());
 
         // SIM does not support voicemail number (IccVmNotSupportedException) so should be saved in
@@ -577,7 +682,7 @@
         AsyncResult.forMessage(msg).exception =
                 new IccVmNotSupportedException("setVoiceMailNumber not implemented");
         msg.sendToTarget();
-        waitForMs(50);
+        processAllMessages();
 
         assertEquals(voiceMailNumber, mPhoneUT.getVoiceMailNumber());
 
@@ -585,14 +690,14 @@
         voiceMailNumber = "1234567894";
         mPhoneUT.setVoiceMailNumber("alphaTag", voiceMailNumber, null);
         messageArgumentCaptor = ArgumentCaptor.forClass(Message.class);
-        verify(mSimRecords).setVoiceMailNumber(eq("alphaTag"), eq(voiceMailNumber),
+        inOrder.verify(mSimRecords).setVoiceMailNumber(eq("alphaTag"), eq(voiceMailNumber),
                 messageArgumentCaptor.capture());
 
         // successfully saved on SIM
         msg = messageArgumentCaptor.getValue();
         AsyncResult.forMessage(msg);
         msg.sendToTarget();
-        waitForMs(50);
+        processAllMessages();
 
         doReturn(voiceMailNumber).when(mSimRecords).getVoiceMailNumber();
 
@@ -666,7 +771,7 @@
         verify(mSimulatedCommandsVerifier).queryCallForwardStatus(
                 eq(CF_REASON_UNCONDITIONAL), eq(CommandsInterface.SERVICE_CLASS_VOICE),
                 nullable(String.class), nullable(Message.class));
-        waitForMs(50);
+        processAllMessages();
         verify(mSimRecords).setVoiceCallForwardingFlag(anyInt(), anyBoolean(),
                 nullable(String.class));
 
@@ -700,7 +805,7 @@
         verify(mSimulatedCommandsVerifier).setCallForward(eq(CF_ACTION_ENABLE),
                 eq(CF_REASON_UNCONDITIONAL), anyInt(), eq(cfNumber), eq(0),
                 nullable(Message.class));
-        waitForMs(50);
+        processAllMessages();
         verify(mSimRecords).setVoiceCallForwardingFlag(anyInt(), anyBoolean(), eq(cfNumber));
     }
 
@@ -765,7 +870,7 @@
 
     @Test
     @SmallTest
-    public void testEmergencyCallbackMessages() {
+    public void testEmergencyCallbackMessages() throws Exception {
         verify(mSimulatedCommandsVerifier).setEmergencyCallbackMode(eq(mPhoneUT), anyInt(),
                 nullable(Object.class));
         verify(mSimulatedCommandsVerifier).registerForExitEmergencyCallbackMode(eq(mPhoneUT),
@@ -773,66 +878,23 @@
 
         // verify handling of emergency callback mode
         mSimulatedCommands.notifyEmergencyCallbackMode();
-        waitForMs(50);
+        processAllMessages();
 
-        // verify ACTION_EMERGENCY_CALLBACK_MODE_CHANGED
-        ArgumentCaptor<Intent> intentArgumentCaptor = ArgumentCaptor.forClass(Intent.class);
-        try {
-            verify(mIActivityManager, atLeast(1)).broadcastIntent(eq((IApplicationThread) null),
-                    intentArgumentCaptor.capture(),
-                    eq((String) null),
-                    eq((IIntentReceiver) null),
-                    eq(Activity.RESULT_OK),
-                    eq((String) null),
-                    eq((Bundle) null),
-                    eq((String[]) null),
-                    anyInt(),
-                    eq((Bundle) null),
-                    eq(false),
-                    eq(true),
-                    anyInt());
-        } catch(Exception e) {
-            fail("Unexpected exception: " + e.getStackTrace());
-        }
-
-        Intent intent = intentArgumentCaptor.getValue();
-        assertEquals(TelephonyIntents.ACTION_EMERGENCY_CALLBACK_MODE_CHANGED, intent.getAction());
-        assertEquals(true, intent.getBooleanExtra(PhoneConstants.PHONE_IN_ECM_STATE, false));
-        assertEquals(true, mPhoneUT.isInEcm());
+        verifyEcbmIntentSent(1 /*times*/, true /*isInEcm*/);
+        assertTrue(mPhoneUT.isInEcm());
 
         // verify that wakeLock is acquired in ECM
-        assertEquals(true, mPhoneUT.getWakeLock().isHeld());
+        assertTrue(mPhoneUT.getWakeLock().isHeld());
 
         mPhoneUT.setOnEcbModeExitResponse(mTestHandler, EVENT_EMERGENCY_CALLBACK_MODE_EXIT, null);
         mPhoneUT.registerForEmergencyCallToggle(mTestHandler, EVENT_EMERGENCY_CALL_TOGGLE, null);
 
         // verify handling of emergency callback mode exit
         mSimulatedCommands.notifyExitEmergencyCallbackMode();
-        waitForMs(50);
+        processAllMessages();
 
-        // verify ACTION_EMERGENCY_CALLBACK_MODE_CHANGED
-        try {
-            verify(mIActivityManager, atLeast(2)).broadcastIntent(eq((IApplicationThread) null),
-                    intentArgumentCaptor.capture(),
-                    eq((String) null),
-                    eq((IIntentReceiver) null),
-                    eq(Activity.RESULT_OK),
-                    eq((String) null),
-                    eq((Bundle) null),
-                    eq((String[]) null),
-                    anyInt(),
-                    eq((Bundle) null),
-                    eq(false),
-                    eq(true),
-                    anyInt());
-        } catch(Exception e) {
-            fail("Unexpected exception: " + e.getStackTrace());
-        }
-
-        intent = intentArgumentCaptor.getValue();
-        assertEquals(TelephonyIntents.ACTION_EMERGENCY_CALLBACK_MODE_CHANGED, intent.getAction());
-        assertEquals(false, intent.getBooleanExtra(PhoneConstants.PHONE_IN_ECM_STATE, true));
-        assertEquals(false, mPhoneUT.isInEcm());
+        verifyEcbmIntentSent(2 /*times*/, false /*isInEcm*/);
+        assertFalse(mPhoneUT.isInEcm());
 
         ArgumentCaptor<Message> messageArgumentCaptor = ArgumentCaptor.forClass(Message.class);
 
@@ -847,7 +909,39 @@
         verify(mDataEnabledSettings).setInternalDataEnabled(true);
 
         // verify wakeLock released
-        assertEquals(false, mPhoneUT.getWakeLock().isHeld());
+        assertFalse(mPhoneUT.getWakeLock().isHeld());
+    }
+
+    private void verifyEcbmIntentSent(int times, boolean isInEcm) throws Exception {
+        ArgumentCaptor<Intent> intentArgumentCaptor = ArgumentCaptor.forClass(Intent.class);
+        verify(mContext, atLeast(times)).sendStickyBroadcastAsUser(intentArgumentCaptor.capture(),
+                any());
+        Intent intent = intentArgumentCaptor.getValue();
+        assertEquals(TelephonyIntents.ACTION_EMERGENCY_CALLBACK_MODE_CHANGED, intent.getAction());
+        assertEquals(isInEcm, intent.getBooleanExtra(
+                TelephonyManager.EXTRA_PHONE_IN_ECM_STATE, false));
+    }
+
+    @Test
+    @SmallTest
+    public void testEcmCancelledPreservedThroughSrvcc() throws Exception {
+        replaceInstance(Phone.class, "mImsPhone", mPhoneUT, mImsPhone);
+        assertFalse(mPhoneUT.isEcmCanceledForEmergency());
+        // Set ECM cancelled state on ImsPhone to be transferred via migrateFrom
+        doReturn(true).when(mImsPhone).isEcmCanceledForEmergency();
+        verify(mSimulatedCommandsVerifier).registerForSrvccStateChanged(any(),
+                eq(EVENT_SRVCC_STATE_CHANGED), any());
+
+        // Start SRVCC
+        Message msg = Message.obtain();
+        msg.what = EVENT_SRVCC_STATE_CHANGED;
+        msg.obj = new AsyncResult(null, new int[]{TelephonyManager.SRVCC_STATE_HANDOVER_STARTED},
+                null);
+        mPhoneUT.sendMessage(msg);
+        processAllMessages();
+
+        // verify ECM cancelled is transferred correctly.
+        assertTrue(mPhoneUT.isEcmCanceledForEmergency());
     }
 
     @Test
@@ -861,31 +955,21 @@
         switchToCdma();
         // verify handling of emergency callback mode
         mSimulatedCommands.notifyEmergencyCallbackMode();
-        waitForMs(50);
+        processAllMessages();
 
         // verify ACTION_EMERGENCY_CALLBACK_MODE_CHANGED
         ArgumentCaptor<Intent> intentArgumentCaptor = ArgumentCaptor.forClass(Intent.class);
         try {
-            verify(mIActivityManager, atLeast(1)).broadcastIntent(eq((IApplicationThread) null),
-                    intentArgumentCaptor.capture(),
-                    eq((String) null),
-                    eq((IIntentReceiver) null),
-                    eq(Activity.RESULT_OK),
-                    eq((String) null),
-                    eq((Bundle) null),
-                    eq((String[]) null),
-                    anyInt(),
-                    eq((Bundle) null),
-                    eq(false),
-                    eq(true),
-                    anyInt());
+            verify(mContext, atLeast(1)).sendStickyBroadcastAsUser(intentArgumentCaptor.capture(),
+                    any());
         } catch (Exception e) {
             fail("Unexpected exception: " + e.getStackTrace());
         }
 
         Intent intent = intentArgumentCaptor.getValue();
         assertEquals(TelephonyIntents.ACTION_EMERGENCY_CALLBACK_MODE_CHANGED, intent.getAction());
-        assertEquals(true, intent.getBooleanExtra(PhoneConstants.PHONE_IN_ECM_STATE, false));
+        assertEquals(true, intent.getBooleanExtra(
+                TelephonyManager.EXTRA_PHONE_IN_ECM_STATE, false));
         assertEquals(true, mPhoneUT.isInEcm());
 
         // verify that wakeLock is acquired in ECM
@@ -896,30 +980,20 @@
 
         // verify handling of emergency callback mode exit when modem resets
         mSimulatedCommands.notifyModemReset();
-        waitForMs(50);
+        processAllMessages();
 
         // verify ACTION_EMERGENCY_CALLBACK_MODE_CHANGED
         try {
-            verify(mIActivityManager, atLeast(2)).broadcastIntent(eq((IApplicationThread) null),
-                    intentArgumentCaptor.capture(),
-                    eq((String) null),
-                    eq((IIntentReceiver) null),
-                    eq(Activity.RESULT_OK),
-                    eq((String) null),
-                    eq((Bundle) null),
-                    eq((String[]) null),
-                    anyInt(),
-                    eq((Bundle) null),
-                    eq(false),
-                    eq(true),
-                    anyInt());
+            verify(mContext, atLeast(2)).sendStickyBroadcastAsUser(intentArgumentCaptor.capture(),
+                    any());
         } catch (Exception e) {
             fail("Unexpected exception: " + e.getStackTrace());
         }
 
         intent = intentArgumentCaptor.getValue();
         assertEquals(TelephonyIntents.ACTION_EMERGENCY_CALLBACK_MODE_CHANGED, intent.getAction());
-        assertEquals(false, intent.getBooleanExtra(PhoneConstants.PHONE_IN_ECM_STATE, true));
+        assertEquals(false, intent.getBooleanExtra(
+                TelephonyManager.EXTRA_PHONE_IN_ECM_STATE, true));
         assertEquals(false, mPhoneUT.isInEcm());
 
         ArgumentCaptor<Message> messageArgumentCaptor = ArgumentCaptor.forClass(Message.class);
@@ -1036,7 +1110,7 @@
         Message onComplete = mTestHandler.obtainMessage(EVENT_SET_ICC_LOCK_ENABLED);
         iccCard.setIccLockEnabled(true, "password", onComplete);
 
-        waitForMs(100);
+        processAllMessages();
 
         ArgumentCaptor<Message> messageArgumentCaptor = ArgumentCaptor.forClass(Message.class);
         // Verify that message is sent back with exception.
@@ -1104,5 +1178,201 @@
                 CarrierConfigManager.KEY_USE_USIM_BOOL, true);
         assertEquals(msisdn, mPhoneUT.getLine1Number());
     }
-}
 
+    @Test
+    @SmallTest
+    public void testEnableUiccApplications() throws Exception {
+        mPhoneUT.mCi = mMockCi;
+        // UiccSlot is null. Doing nothing.
+        mPhoneUT.enableUiccApplications(true, null);
+        verify(mMockCi, never()).enableUiccApplications(anyBoolean(), any());
+
+        // Card state is not PRESENT. Doing nothing.
+        doReturn(mUiccSlot).when(mUiccController).getUiccSlotForPhone(anyInt());
+        doReturn(IccCardStatus.CardState.CARDSTATE_ABSENT).when(mUiccSlot).getCardState();
+        mPhoneUT.enableUiccApplications(true, null);
+        verify(mMockCi, never()).enableUiccApplications(anyBoolean(), any());
+
+        doReturn(IccCardStatus.CardState.CARDSTATE_PRESENT).when(mUiccSlot).getCardState();
+        Message message = Message.obtain();
+        mPhoneUT.enableUiccApplications(true, message);
+        verify(mMockCi).enableUiccApplications(eq(true), eq(message));
+    }
+
+    @Test
+    @SmallTest
+    public void testReapplyUiccApplicationEnablementNotNeeded() throws Exception {
+        mPhoneUT.mCi = mMockCi;
+        // UiccSlot is null, or not present, or mUiccApplicationsEnabled is not available, or IccId
+        // is not available, Doing nothing.
+        doReturn(mUiccSlot).when(mUiccController).getUiccSlotForPhone(anyInt());
+        Message.obtain(mPhoneUT, EVENT_ICC_CHANGED, null).sendToTarget();
+        processAllMessages();
+        doReturn(IccCardStatus.CardState.CARDSTATE_ABSENT).when(mUiccSlot).getCardState();
+        Message.obtain(mPhoneUT, EVENT_ICC_CHANGED, null).sendToTarget();
+        processAllMessages();
+        doReturn(IccCardStatus.CardState.CARDSTATE_PRESENT).when(mUiccSlot).getCardState();
+        Message.obtain(mPhoneUT, EVENT_ICC_CHANGED, null).sendToTarget();
+        Message.obtain(mPhoneUT, EVENT_UICC_APPS_ENABLEMENT_STATUS_CHANGED,
+                new AsyncResult(null, true, null)).sendToTarget();
+        processAllMessages();
+        verify(mSubscriptionController, never()).getSubInfoForIccId(any());
+
+        // Have IccId defined. But expected value and current value are the same. So no RIL command
+        // should be sent.
+        String iccId = "Fake iccId";
+        doReturn(iccId).when(mUiccSlot).getIccId();
+        Message.obtain(mPhoneUT, EVENT_ICC_CHANGED, null).sendToTarget();
+        processAllMessages();
+        verify(mSubscriptionController).getSubInfoForIccId(iccId);
+        verify(mMockCi, never()).enableUiccApplications(anyBoolean(), any());
+    }
+
+    @Test
+    @SmallTest
+    public void testReapplyUiccApplicationEnablementSuccess() throws Exception {
+        mPhoneUT.mCi = mMockCi;
+        // Set SIM to be present, with a fake iccId, and notify enablement being false.
+        doReturn(mUiccSlot).when(mUiccController).getUiccSlotForPhone(anyInt());
+        doReturn(IccCardStatus.CardState.CARDSTATE_PRESENT).when(mUiccSlot).getCardState();
+        String iccId = "Fake iccId";
+        doReturn(iccId).when(mUiccSlot).getIccId();
+        Message.obtain(mPhoneUT, EVENT_UICC_APPS_ENABLEMENT_STATUS_CHANGED,
+                new AsyncResult(null, false, null)).sendToTarget();
+        processAllMessages();
+
+        // Should try to enable uicc applications as by default hey are expected to be true.
+        ArgumentCaptor<Message> messageCaptor = ArgumentCaptor.forClass(Message.class);
+        verify(mMockCi, times(1)).enableUiccApplications(eq(true), messageCaptor.capture());
+        // Send message back with no exception.
+        AsyncResult.forMessage(messageCaptor.getValue(), null, null);
+        messageCaptor.getValue().sendToTarget();
+        processAllMessages();
+
+        // There shouldn't be any retry message.
+        moveTimeForward(5000);
+        processAllMessages();
+        verify(mMockCi, times(1)).enableUiccApplications(eq(true), any());
+    }
+
+    @Test
+    @SmallTest
+    public void testSetRadioPower() throws Exception {
+        mPhoneUT.setRadioPower(false);
+        verify(mSST).setRadioPower(false, false, false, false);
+
+        // Turn on radio for emergency call.
+        mPhoneUT.setRadioPower(true, true, false, true);
+        verify(mSST).setRadioPower(true, true, false, true);
+    }
+
+    @Test
+    @SmallTest
+    public void testReapplyUiccApplicationEnablementRetry() throws Exception {
+        mPhoneUT.mCi = mMockCi;
+        // Set SIM to be present, with a fake iccId, and notify enablement being false.
+        doReturn(mUiccSlot).when(mUiccController).getUiccSlotForPhone(anyInt());
+        doReturn(IccCardStatus.CardState.CARDSTATE_PRESENT).when(mUiccSlot).getCardState();
+        String iccId = "Fake iccId";
+        doReturn(iccId).when(mUiccSlot).getIccId();
+        Message.obtain(mPhoneUT, EVENT_UICC_APPS_ENABLEMENT_STATUS_CHANGED,
+                new AsyncResult(null, false, null)).sendToTarget();
+        processAllMessages();
+
+        // Should try to enable uicc applications as by default hey are expected to be true.
+        ArgumentCaptor<Message> messageCaptor = ArgumentCaptor.forClass(Message.class);
+        verify(mMockCi).enableUiccApplications(eq(true), messageCaptor.capture());
+        clearInvocations(mMockCi);
+        for (int i = 0; i < GsmCdmaPhone.ENABLE_UICC_APPS_MAX_RETRIES; i++) {
+            // Send message back with SIM_BUSY exception. Should retry.
+            AsyncResult.forMessage(messageCaptor.getValue(), null, new CommandException(
+                    CommandException.Error.SIM_BUSY));
+            messageCaptor.getValue().sendToTarget();
+            processAllMessages();
+            // There should be a retry message.
+            moveTimeForward(5000);
+            processAllMessages();
+            verify(mMockCi).enableUiccApplications(eq(true), messageCaptor.capture());
+            clearInvocations(mMockCi);
+        }
+
+        // Reaches max retries. Should NOT retry.
+        AsyncResult.forMessage(messageCaptor.getValue(), null, new CommandException(
+                CommandException.Error.SIM_BUSY));
+        messageCaptor.getValue().sendToTarget();
+        processAllMessages();
+        // There should NOT be a retry message.
+        moveTimeForward(5000);
+        processAllMessages();
+        verify(mMockCi, never()).enableUiccApplications(eq(true), messageCaptor.capture());
+        clearInvocations(mMockCi);
+    }
+
+    @Test
+    @SmallTest
+    public void testSendUssdInService() throws Exception {
+        PhoneInternalInterface.DialArgs dialArgs = new PhoneInternalInterface.DialArgs
+                .Builder().setVideoState(VideoProfile.STATE_AUDIO_ONLY).build();
+
+        setupTestSendUssd(dialArgs);
+
+        // ServiceState is in service.
+        doReturn(ServiceState.STATE_IN_SERVICE).when(mSST.mSS).getState();
+        mPhoneUT.dial("*135#", dialArgs);
+        verify(mMockCi).sendUSSD(eq("*135#"), any());
+    }
+
+    @Test
+    @SmallTest
+    public void testSendUssdInOutOfService() throws Exception {
+        PhoneInternalInterface.DialArgs dialArgs = new PhoneInternalInterface.DialArgs
+                .Builder().setVideoState(VideoProfile.STATE_AUDIO_ONLY).build();
+
+        setupTestSendUssd(dialArgs);
+
+        // ServiceState is out of service
+        doReturn(ServiceState.STATE_OUT_OF_SERVICE).when(mSST.mSS)
+                .getState(); /* CS out of service */
+        doReturn(ServiceState.STATE_IN_SERVICE).when(mSST.mSS).getDataRegState();
+        doReturn(ServiceState.RIL_RADIO_TECHNOLOGY_GSM).when(mSST.mSS)
+                .getRilDataRadioTechnology(); /* PS not in LTE */
+        mPhoneUT.dial("*135#", dialArgs);
+        verify(mMockCi).sendUSSD(eq("*135#"), any());
+    }
+
+    @Test
+    @SmallTest
+    public void testSendUssdInAirplaneMode() throws Exception {
+        PhoneInternalInterface.DialArgs dialArgs = new PhoneInternalInterface.DialArgs
+                .Builder().setVideoState(VideoProfile.STATE_AUDIO_ONLY).build();
+
+        setupTestSendUssd(dialArgs);
+
+        // ServiceState is airplane mode.
+        doReturn(ServiceState.STATE_POWER_OFF).when(mSST.mSS).getState(); /* CS POWER_OFF */
+        mPhoneUT.dial("*135#", dialArgs);
+        verify(mMockCi).sendUSSD(eq("*135#"), any());
+    }
+
+    private void setupTestSendUssd(PhoneInternalInterface.DialArgs dialArgs) throws Exception {
+        mPhoneUT.mCi = mMockCi;
+        ServiceState mImsServiceState = Mockito.mock(ServiceState.class);
+        CallStateException callStateException = Mockito.mock(CallStateException.class);
+
+        // Enable VoWiFi
+        doReturn(true).when(mImsManager).isVolteEnabledByPlatform();
+        doReturn(true).when(mImsManager).isEnhanced4gLteModeSettingEnabledByUser();
+        doReturn(mImsServiceState).when(mImsPhone).getServiceState();
+        doReturn(ServiceState.STATE_IN_SERVICE).when(mImsServiceState).getState();
+        doReturn(true).when(mImsPhone).isWifiCallingEnabled();
+
+        // Disable UT/XCAP
+        doReturn(false).when(mImsPhone).isUtEnabled();
+
+        // Throw CallStateException(Phone.CS_FALLBACK) from ImsPhone.dial().
+        doReturn(Phone.CS_FALLBACK).when(callStateException).getMessage();
+        doThrow(callStateException).when(mImsPhone).dial("*135#", dialArgs);
+
+        replaceInstance(Phone.class, "mImsPhone", mPhoneUT, mImsPhone);
+    }
+}
diff --git a/tests/telephonytests/src/com/android/internal/telephony/GsmSmsTest.java b/tests/telephonytests/src/com/android/internal/telephony/GsmSmsTest.java
index ad5d0ab..3d4ef03 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/GsmSmsTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/GsmSmsTest.java
@@ -56,6 +56,15 @@
     }
 
     @SmallTest
+    public void testIsEmailAddress() throws Exception {
+        assertTrue(SmsMessage.isEmailAddress("\"First Last\" <name@example.com>"));
+        assertTrue(SmsMessage.isEmailAddress("FirstLast <name@sub.example.com>"));
+        assertFalse(SmsMessage.isEmailAddress("FirstLast 1112223333"));
+        assertFalse(SmsMessage.isEmailAddress(""));
+        assertFalse(SmsMessage.isEmailAddress("nmg5gj945j"));
+    }
+
+    @SmallTest
     public void testUdh() throws Exception {
         String pdu = "07914140279510F6440A8111110301003BF56080207130138A8C0B05040B8423F"
                 + "000032A02010106276170706C69636174696F6E2F766E642E7761702E6D6D732D"
diff --git a/tests/telephonytests/src/com/android/internal/telephony/ImsSmsDispatcherTest.java b/tests/telephonytests/src/com/android/internal/telephony/ImsSmsDispatcherTest.java
index 76ead95..d13152e 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/ImsSmsDispatcherTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/ImsSmsDispatcherTest.java
@@ -19,6 +19,7 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.ArgumentMatchers.nullable;
@@ -26,10 +27,14 @@
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
+import android.content.Context;
 import android.os.Looper;
 import android.telephony.SmsMessage;
 import android.telephony.ims.stub.ImsSmsImplBase;
 import android.test.suitebuilder.annotation.SmallTest;
+import android.util.Pair;
+
+import com.android.internal.util.HexDump;
 
 import org.junit.After;
 import org.junit.Before;
@@ -98,7 +103,7 @@
 
         // Fallback over GSM
         mImsSmsDispatcher.getSmsListener().onSendSmsResult(token, 0,
-                ImsSmsImplBase.SEND_STATUS_ERROR_FALLBACK, 0);
+                ImsSmsImplBase.SEND_STATUS_ERROR_FALLBACK, 0, SmsResponse.NO_ERROR_CODE);
         ArgumentCaptor<SMSDispatcher.SmsTracker> captor =
                 ArgumentCaptor.forClass(SMSDispatcher.SmsTracker.class);
         // Ensure GsmSmsDispatcher calls sendSms
@@ -125,7 +130,7 @@
 
         // Fallback over GSM
         mImsSmsDispatcher.getSmsListener().onSendSmsResult(token, 0,
-                ImsSmsImplBase.SEND_STATUS_ERROR_RETRY, 0);
+                ImsSmsImplBase.SEND_STATUS_ERROR_RETRY, 0, SmsResponse.NO_ERROR_CODE);
 
         // Make sure retry bit set
         ArgumentCaptor<byte[]> byteCaptor = ArgumentCaptor.forClass(byte[].class);
@@ -137,6 +142,64 @@
         assertEquals(0x04, (pdu[0] & 0x04));
     }
 
+    /**
+     * Ensure that when a GSM status report is received, it calls acknowledgeSmsReport with correct
+     * token and message reference.
+     */
+    @Test
+    @SmallTest
+    public void testReceiveGsmSmsStatusReport() throws Exception {
+        int sentSmsToken = mImsSmsDispatcher.mNextToken.get();
+        int statusReportToken = 456; // Generated by IMS providers
+        int messageRef = 123; // TP-MR for sent SMS
+        int trackersSize = mImsSmsDispatcher.mTrackers.size();
+        // PDU for SMS-STATUS-REPORT
+        byte[] pdu = HexDump.hexStringToByteArray("0006000681214365919061800000639190618000006300");
+
+        // Set TP-MR
+        pdu[2] = (byte) messageRef;
+        mSmsTracker.mMessageRef = messageRef;
+
+        mImsSmsDispatcher.mTrackers.put(sentSmsToken, mSmsTracker);
+        when(mPhone.getPhoneType()).thenReturn(PhoneConstants.PHONE_TYPE_GSM);
+        when(mSmsDispatchersController.handleSmsStatusReport(
+                    eq(mSmsTracker), eq(SmsMessage.FORMAT_3GPP), eq(pdu)))
+                .thenReturn(new Pair(true, true));
+
+        // Receive the status report
+        mImsSmsDispatcher
+                .getSmsListener()
+                .onSmsStatusReportReceived(statusReportToken, SmsMessage.FORMAT_3GPP, pdu);
+
+        // Ensure it calls acknowledgeSmsReport with correct token and message reference
+        verify(mImsManager)
+                .acknowledgeSmsReport(
+                        eq(statusReportToken),
+                        eq(messageRef),
+                        eq(ImsSmsImplBase.STATUS_REPORT_STATUS_OK));
+        assertEquals(trackersSize, mImsSmsDispatcher.mTrackers.size());
+    }
+
+    /**
+     * Ensure that when an outgoing SMS has failed over IMS with SEND_STATUS_ERROR and an associated
+     * networkErrorCode, the error is sent to the tracker properly.
+     */
+    @Test
+    @SmallTest
+    public void testNetworkError() throws Exception {
+        int token = mImsSmsDispatcher.mNextToken.get();
+        mTrackerData.put("pdu", com.android.internal.telephony.gsm.SmsMessage.getSubmitPdu(null,
+                "+15555551212", "Test", false).encodedMessage);
+        when(mImsManager.getSmsFormat()).thenReturn(SmsMessage.FORMAT_3GPP);
+        mImsSmsDispatcher.mTrackers.put(token, mSmsTracker);
+        when(mPhone.getPhoneType()).thenReturn(PhoneConstants.PHONE_TYPE_GSM);
+
+        // network error 41
+        mImsSmsDispatcher.getSmsListener().onSendSmsResult(token, 0,
+                ImsSmsImplBase.SEND_STATUS_ERROR, 0, 41);
+        verify(mSmsTracker).onFailed(any(Context.class), anyInt(), eq(41));
+    }
+
     @After
     public void tearDown() throws Exception {
         mImsSmsDispatcher = null;
diff --git a/tests/telephonytests/src/com/android/internal/telephony/InboundSmsTrackerTest.java b/tests/telephonytests/src/com/android/internal/telephony/InboundSmsTrackerTest.java
index e072a62..cd97bdc 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/InboundSmsTrackerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/InboundSmsTrackerTest.java
@@ -23,6 +23,8 @@
 import android.database.MatrixCursor;
 import android.test.suitebuilder.annotation.SmallTest;
 
+import androidx.test.InstrumentationRegistry;
+
 import com.android.internal.util.HexDump;
 
 import org.junit.Before;
@@ -46,7 +48,8 @@
 
     @Before
     public void setUp() throws Exception {
-        mInboundSmsTracker = new InboundSmsTracker(FAKE_PDU, FAKE_TIMESTAMP, FAKE_DEST_PORT, false,
+        mInboundSmsTracker = new InboundSmsTracker(InstrumentationRegistry.getContext(),
+                FAKE_PDU, FAKE_TIMESTAMP, FAKE_DEST_PORT, false,
                 FAKE_ADDRESS, FAKE_DISPLAY_ADDRESS, FAKE_REFERENCE_NUMBER, FAKE_SEQUENCE_NUMBER,
                 FAKE_MESSAGE_COUNT, false, FAKE_MESSAGE_BODY, false /* isClass0 */, FAKE_SUBID);
     }
@@ -84,6 +87,7 @@
         assertEquals(FAKE_DISPLAY_ADDRESS, mInboundSmsTracker.getDisplayAddress());
         assertEquals(false, mInboundSmsTracker.isClass0());
         assertEquals(FAKE_SUBID, mInboundSmsTracker.getSubId());
+//        assertNotEquals(0L, mInboundSmsTracker.getMessageId());
 
         String[] args = new String[]{"123"};
         mInboundSmsTracker.setDeleteWhere(InboundSmsHandler.SELECT_BY_ID, args);
@@ -94,7 +98,8 @@
     @Test
     @SmallTest
     public void testInitializationFromDb() {
-        mInboundSmsTracker = new InboundSmsTracker(createFakeCursor(), false);
+        mInboundSmsTracker = new InboundSmsTracker(InstrumentationRegistry.getContext(),
+                createFakeCursor(), false);
         testInitialization();
     }
 }
\ No newline at end of file
diff --git a/tests/telephonytests/src/com/android/internal/telephony/LocaleTrackerTest.java b/tests/telephonytests/src/com/android/internal/telephony/LocaleTrackerTest.java
index d161246..5b147c0 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/LocaleTrackerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/LocaleTrackerTest.java
@@ -25,11 +25,9 @@
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 
-import android.content.Context;
 import android.content.Intent;
-import android.net.wifi.WifiManager;
 import android.os.AsyncResult;
-import android.os.HandlerThread;
+import android.os.Looper;
 import android.os.Message;
 import android.telephony.CellIdentityGsm;
 import android.telephony.CellInfo;
@@ -37,20 +35,26 @@
 import android.telephony.ServiceState;
 import android.telephony.TelephonyManager;
 import android.test.suitebuilder.annotation.SmallTest;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
 
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
+import org.junit.runner.RunWith;
 import org.mockito.ArgumentCaptor;
 
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.List;
 
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper
 public class LocaleTrackerTest extends TelephonyTest {
 
     private static final String US_MCC = "310";
     private static final String LIECHTENSTEIN_MCC = "295";
+    private static final String TEST_CELL_MCC = "001";
 
     private static final String FAKE_MNC = "123";
 
@@ -61,55 +65,34 @@
     private LocaleTracker mLocaleTracker;
 
     private CellInfoGsm mCellInfo;
-    private WifiManager mWifiManager;
-
-    private class LocaleTrackerTestHandler extends HandlerThread {
-
-        private LocaleTrackerTestHandler(String name) {
-            super(name);
-        }
-
-        @Override
-        public void onLooperPrepared() {
-            mLocaleTracker = new LocaleTracker(mPhone, mNitzStateMachine, this.getLooper());
-            setReady(true);
-        }
-    }
-
-    private LocaleTrackerTestHandler mHandlerThread;
 
     @Before
     public void setUp() throws Exception {
         logd("LocaleTrackerTest +Setup!");
         super.setUp(getClass().getSimpleName());
 
-        mHandlerThread = new LocaleTrackerTestHandler("LocaleTrackerTestHandler");
-        mHandlerThread.start();
-        waitUntilReady();
-
+        mLocaleTracker = new LocaleTracker(mPhone, mNitzStateMachine, Looper.myLooper());
 
         // This is a workaround to bypass setting system properties, which causes access violation.
         doReturn(-1).when(mPhone).getPhoneId();
-        mWifiManager = (WifiManager) mContext.getSystemService(Context.WIFI_SERVICE);
 
         mCellInfo = new CellInfoGsm();
         mCellInfo.setCellIdentity(new CellIdentityGsm(
                     CellInfo.UNAVAILABLE, CellInfo.UNAVAILABLE,
                     CellInfo.UNAVAILABLE, CellInfo.UNAVAILABLE,
-                    US_MCC, FAKE_MNC, null, null));
+                    US_MCC, FAKE_MNC, null, null, Collections.emptyList()));
         doAnswer(invocation -> {
             Message m = invocation.getArgument(1);
             AsyncResult.forMessage(m, Arrays.asList(mCellInfo), null);
             m.sendToTarget();
             return null; }).when(mPhone).requestCellInfoUpdate(any(), any());
 
+        processAllMessages();
         logd("LocaleTrackerTest -Setup!");
     }
 
     @After
     public void tearDown() throws Exception {
-        mHandlerThread.quit();
-        mHandlerThread.join();
         super.tearDown();
     }
 
@@ -119,7 +102,7 @@
         AsyncResult ar = new AsyncResult(null, ss, null);
         mLocaleTracker.sendMessage(
                 mLocaleTracker.obtainMessage(2 /*SERVICE_STATE_CHANGED*/, ar));
-        waitForHandlerAction(mLocaleTracker, 100);
+        processAllMessages();
     }
 
     private void sendGsmCellInfo() {
@@ -128,21 +111,20 @@
                 .obtainMessage(4 /*UNSOL_CELL_INFO*/,
                         new AsyncResult(null, Arrays.asList(mCellInfo), null))
                 .sendToTarget();
-        waitForHandlerAction(mLocaleTracker, 100);
+        processAllMessages();
+    }
+
+    private void sendOperatorLost() {
+        mLocaleTracker.sendMessage(mLocaleTracker.obtainMessage(6 /* EVENT_OPERATOR_LOST */));
+        processAllMessages();
     }
 
     private void verifyCountryCodeNotified(String[] countryCodes) {
-        ArgumentCaptor<String> stringArgumentCaptor = ArgumentCaptor.forClass(String.class);
-        verify(mWifiManager, times(countryCodes.length)).setCountryCode(
-                stringArgumentCaptor.capture());
-        List<String> strs = stringArgumentCaptor.getAllValues();
-
         ArgumentCaptor<Intent> intentArgumentCaptor = ArgumentCaptor.forClass(Intent.class);
         verify(mContext, times(countryCodes.length)).sendBroadcast(intentArgumentCaptor.capture());
         List<Intent> intents = intentArgumentCaptor.getAllValues();
 
         for (int i = 0; i < countryCodes.length; i++) {
-            assertEquals(countryCodes[i], strs.get(i));
             assertEquals(TelephonyManager.ACTION_NETWORK_COUNTRY_CHANGED,
                     intents.get(i).getAction());
             assertEquals(countryCodes[i], intents.get(i).getStringExtra(
@@ -154,19 +136,24 @@
     @SmallTest
     public void testUpdateOperatorNumericSync() throws Exception {
         mLocaleTracker.updateOperatorNumeric(US_MCC + FAKE_MNC);
-        assertEquals(US_COUNTRY_CODE, mLocaleTracker.getCurrentCountry());
-        verifyCountryCodeNotified(new String[]{US_COUNTRY_CODE});
+        // Because the service state is in APM, the country ISO should be set empty.
+        assertEquals(COUNTRY_CODE_UNAVAILABLE, mLocaleTracker.getCurrentCountry());
     }
 
     @Test
     @SmallTest
     public void testNoSim() throws Exception {
+        // Set the state as STATE_OUT_OF_SERVICE. This will trigger an country change to US.
+        sendServiceState(ServiceState.STATE_OUT_OF_SERVICE);
+        assertEquals(US_COUNTRY_CODE, mLocaleTracker.getCurrentCountry());
+        verifyCountryCodeNotified(new String[]{COUNTRY_CODE_UNAVAILABLE, US_COUNTRY_CODE});
+
         // updateOperatorNumeric("") will not trigger an instantaneous country change
         mLocaleTracker.updateOperatorNumeric("");
         sendGsmCellInfo();
         sendServiceState(ServiceState.STATE_EMERGENCY_ONLY);
         assertEquals(US_COUNTRY_CODE, mLocaleTracker.getCurrentCountry());
-        verifyCountryCodeNotified(new String[]{US_COUNTRY_CODE});
+        verifyCountryCodeNotified(new String[]{COUNTRY_CODE_UNAVAILABLE, US_COUNTRY_CODE});
         assertTrue(mLocaleTracker.isTracking());
     }
 
@@ -186,16 +173,26 @@
         sendServiceState(ServiceState.STATE_IN_SERVICE);
         mLocaleTracker.updateOperatorNumeric(US_MCC + FAKE_MNC);
         assertEquals(US_COUNTRY_CODE, mLocaleTracker.getCurrentCountry());
+        assertEquals(US_COUNTRY_CODE, mLocaleTracker.getLastKnownCountryIso());
         verifyCountryCodeNotified(new String[]{COUNTRY_CODE_UNAVAILABLE, US_COUNTRY_CODE});
         assertFalse(mLocaleTracker.isTracking());
 
         // updateOperatorNumeric("") will not trigger an instantaneous country change
         mLocaleTracker.updateOperatorNumeric("");
-        waitForHandlerAction(mLocaleTracker, 100);
+        processAllMessages();
         assertEquals(US_COUNTRY_CODE, mLocaleTracker.getCurrentCountry());
+        assertEquals(US_COUNTRY_CODE, mLocaleTracker.getLastKnownCountryIso());
         verifyCountryCodeNotified(new String[]{COUNTRY_CODE_UNAVAILABLE, US_COUNTRY_CODE});
         sendServiceState(ServiceState.STATE_POWER_OFF);
         assertFalse(mLocaleTracker.isTracking());
+
+        // updateOperatorNumeric("") will trigger a country change in APM
+        mLocaleTracker.updateOperatorNumeric("");
+        processAllMessages();
+        assertEquals(COUNTRY_CODE_UNAVAILABLE, mLocaleTracker.getCurrentCountry());
+        assertEquals(US_COUNTRY_CODE, mLocaleTracker.getLastKnownCountryIso());
+        verifyCountryCodeNotified(new String[]{COUNTRY_CODE_UNAVAILABLE, US_COUNTRY_CODE,
+                COUNTRY_CODE_UNAVAILABLE});
     }
 
     @Test
@@ -203,15 +200,14 @@
     public void testToggleAirplaneModeOff() throws Exception {
         sendServiceState(ServiceState.STATE_POWER_OFF);
         mLocaleTracker.updateOperatorNumeric("");
-        waitForHandlerAction(mLocaleTracker, 100);
+        processAllMessages();
         assertEquals(COUNTRY_CODE_UNAVAILABLE, mLocaleTracker.getCurrentCountry());
         verifyCountryCodeNotified(new String[]{COUNTRY_CODE_UNAVAILABLE});
         assertFalse(mLocaleTracker.isTracking());
 
         sendServiceState(ServiceState.STATE_OUT_OF_SERVICE);
-        waitForHandlerAction(mLocaleTracker, 100);
+        processAllMessages();
         assertTrue(mLocaleTracker.isTracking());
-        waitForHandlerAction(mLocaleTracker, 100);
         assertEquals(US_COUNTRY_CODE, mLocaleTracker.getCurrentCountry());
     }
 
@@ -220,7 +216,7 @@
     public void testToggleAirplaneModeOosPlmn() throws Exception {
         sendServiceState(ServiceState.STATE_POWER_OFF);
         mLocaleTracker.updateOperatorNumeric("");
-        waitForHandlerAction(mLocaleTracker, 100);
+        processAllMessages();
         assertEquals(COUNTRY_CODE_UNAVAILABLE, mLocaleTracker.getCurrentCountry());
         verifyCountryCodeNotified(new String[]{COUNTRY_CODE_UNAVAILABLE});
         assertFalse(mLocaleTracker.isTracking());
@@ -233,9 +229,8 @@
             return null; }).when(mPhone).requestCellInfoUpdate(any(), any());
 
         sendServiceState(ServiceState.STATE_OUT_OF_SERVICE);
-        waitForHandlerAction(mLocaleTracker, 100);
+        processAllMessages();
         assertTrue(mLocaleTracker.isTracking());
-        waitForHandlerAction(mLocaleTracker, 100);
         assertEquals(COUNTRY_CODE_UNAVAILABLE, mLocaleTracker.getCurrentCountry());
 
         mLocaleTracker.updateOperatorNumeric(US_MCC + FAKE_MNC);
@@ -243,12 +238,12 @@
         verifyCountryCodeNotified(new String[]{COUNTRY_CODE_UNAVAILABLE, US_COUNTRY_CODE});
 
         mLocaleTracker.updateOperatorNumeric("");
-        waitForHandlerAction(mLocaleTracker, 100);
+        processAllMessages();
         assertEquals(US_COUNTRY_CODE, mLocaleTracker.getCurrentCountry());
         verifyCountryCodeNotified(new String[]{COUNTRY_CODE_UNAVAILABLE, US_COUNTRY_CODE});
 
         mLocaleTracker.updateOperatorNumeric(LIECHTENSTEIN_MCC + FAKE_MNC);
-        waitForHandlerAction(mLocaleTracker, 100);
+        processAllMessages();
         assertEquals(LIECHTENSTEIN_COUNTRY_CODE, mLocaleTracker.getCurrentCountry());
         verifyCountryCodeNotified(new String[]{
                 COUNTRY_CODE_UNAVAILABLE, US_COUNTRY_CODE, LIECHTENSTEIN_COUNTRY_CODE});
@@ -259,7 +254,7 @@
     public void testToggleAirplaneModeNoCellInfo() throws Exception {
         sendServiceState(ServiceState.STATE_POWER_OFF);
         mLocaleTracker.updateOperatorNumeric("");
-        waitForHandlerAction(mLocaleTracker, 100);
+        processAllMessages();
         assertEquals(COUNTRY_CODE_UNAVAILABLE, mLocaleTracker.getCurrentCountry());
         verifyCountryCodeNotified(new String[]{COUNTRY_CODE_UNAVAILABLE});
         assertFalse(mLocaleTracker.isTracking());
@@ -272,9 +267,8 @@
             return null; }).when(mPhone).requestCellInfoUpdate(any(), any());
 
         sendServiceState(ServiceState.STATE_OUT_OF_SERVICE);
-        waitForHandlerAction(mLocaleTracker, 100);
+        processAllMessages();
         assertTrue(mLocaleTracker.isTracking());
-        waitForHandlerAction(mLocaleTracker, 100);
         assertEquals(COUNTRY_CODE_UNAVAILABLE, mLocaleTracker.getCurrentCountry());
     }
 
@@ -297,4 +291,21 @@
             assertEquals(600000, LocaleTracker.getCellInfoDelayTime(i));
         }
     }
+
+    @Test
+    @SmallTest
+    public void updateOperatorNumeric_NoSim_shouldHandleNetworkCountryCodeUnavailable()
+            throws Exception {
+        mLocaleTracker.updateOperatorNumeric("");
+        sendOperatorLost();
+        verify(mNitzStateMachine, times(1)).handleCountryUnavailable();
+    }
+
+    @Test
+    @SmallTest
+    public void updateOperatorNumeric_TestNetwork_shouldHandleNetworkCountryCodeSet()
+            throws Exception {
+        mLocaleTracker.updateOperatorNumeric(TEST_CELL_MCC + FAKE_MNC);
+        verify(mNitzStateMachine, times(1)).handleCountryDetected("");
+    }
 }
diff --git a/tests/telephonytests/src/com/android/internal/telephony/MccTableTest.java b/tests/telephonytests/src/com/android/internal/telephony/MccTableTest.java
index ca398bc..fcffa96 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/MccTableTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/MccTableTest.java
@@ -16,94 +16,105 @@
 
 package com.android.internal.telephony;
 
-import android.test.AndroidTestCase;
+import static org.junit.Assert.assertEquals;
+
+import android.content.Context;
 import android.test.suitebuilder.annotation.SmallTest;
 
-import org.junit.Ignore;
+import androidx.test.InstrumentationRegistry;
+
+import com.android.internal.telephony.MccTable.MccMnc;
+import com.android.internal.telephony.util.LocaleUtils;
+
+import org.junit.Test;
 
 import java.util.Locale;
 
-// TODO try using InstrumentationRegistry.getContext() instead of the default
-// AndroidTestCase context
-public class MccTableTest extends AndroidTestCase {
-    private final static String LOG_TAG = "GSM";
+public class MccTableTest {
 
     @SmallTest
-    @Ignore
-    public void testTimeZone() throws Exception {
-        assertEquals("Europe/Paris", MccTable.defaultTimeZoneForMcc(208));
-        assertEquals("Europe/Vienna", MccTable.defaultTimeZoneForMcc(232));
-        assertEquals("Africa/Johannesburg", MccTable.defaultTimeZoneForMcc(655));
-        assertEquals("Asia/Tokyo", MccTable.defaultTimeZoneForMcc(440));
-        assertEquals("Asia/Tokyo", MccTable.defaultTimeZoneForMcc(441));
-        assertEquals("Asia/Singapore", MccTable.defaultTimeZoneForMcc(525));
-        assertEquals("Europe/Stockholm", MccTable.defaultTimeZoneForMcc(240));
+    @Test
+    public void testCountryCodeForMcc() throws Exception {
+        checkMccLookupWithNoMnc("lu", 270);
+        checkMccLookupWithNoMnc("gr", 202);
+        checkMccLookupWithNoMnc("fk", 750);
+        checkMccLookupWithNoMnc("mg", 646);
+        checkMccLookupWithNoMnc("us", 314);
+        checkMccLookupWithNoMnc("", 300);  // mcc not defined, hence default
+        checkMccLookupWithNoMnc("", 0);    // mcc not defined, hence default
+        checkMccLookupWithNoMnc("", 2000); // mcc not defined, hence default
+    }
 
-        /* A test for the special handling for MCC 505. http://b/33228250. */
-        assertEquals("Australia/Sydney", MccTable.defaultTimeZoneForMcc(505));
-        assertEquals(null, MccTable.defaultTimeZoneForMcc(0));    // mcc not defined, hence default
-        assertEquals(null, MccTable.defaultTimeZoneForMcc(2000)); // mcc not defined, hence default
+    private void checkMccLookupWithNoMnc(String expectedCountryIsoCode, int mcc) {
+        assertEquals(expectedCountryIsoCode, MccTable.countryCodeForMcc(mcc));
+        assertEquals(expectedCountryIsoCode, MccTable.countryCodeForMcc(mcc));
+        assertEquals(expectedCountryIsoCode, MccTable.countryCodeForMcc("" + mcc));
+        assertEquals(expectedCountryIsoCode,
+                MccTable.geoCountryCodeForMccMnc(new MccMnc("" + mcc, "999")));
     }
 
     @SmallTest
-    @Ignore
-    public void testCountryCode() throws Exception {
-        assertEquals("lu", MccTable.countryCodeForMcc(270));
-        assertEquals("gr", MccTable.countryCodeForMcc(202));
-        assertEquals("fk", MccTable.countryCodeForMcc(750));
-        assertEquals("mg", MccTable.countryCodeForMcc(646));
-        assertEquals("us", MccTable.countryCodeForMcc(314));
-        assertEquals("", MccTable.countryCodeForMcc(300));  // mcc not defined, hence default
-        assertEquals("", MccTable.countryCodeForMcc(0));    // mcc not defined, hence default
-        assertEquals("", MccTable.countryCodeForMcc(2000)); // mcc not defined, hence default
+    @Test
+    public void testGeoCountryCodeForMccMnc() throws Exception {
+        // This test is possibly fragile as this data is configurable.
+        assertEquals("gu", MccTable.geoCountryCodeForMccMnc(new MccMnc("310", "370")));
     }
 
     @SmallTest
-    @Ignore
+    @Test
     public void testLang() throws Exception {
-        assertEquals("en", MccTable.defaultLanguageForMcc(311));
-        assertEquals("de", MccTable.defaultLanguageForMcc(232));
-        assertEquals("cs", MccTable.defaultLanguageForMcc(230));
-        assertEquals("nl", MccTable.defaultLanguageForMcc(204));
-        assertEquals("is", MccTable.defaultLanguageForMcc(274));
-        assertEquals(null, MccTable.defaultLanguageForMcc(0));    // mcc not defined, hence default
-        assertEquals(null, MccTable.defaultLanguageForMcc(2000)); // mcc not defined, hence default
+        assertEquals("en", LocaleUtils.defaultLanguageForMcc(311));
+        assertEquals("de", LocaleUtils.defaultLanguageForMcc(232));
+        assertEquals("cs", LocaleUtils.defaultLanguageForMcc(230));
+        assertEquals("nl", LocaleUtils.defaultLanguageForMcc(204));
+        assertEquals("is", LocaleUtils.defaultLanguageForMcc(274));
+        // mcc not defined, hence default
+        assertEquals(null, LocaleUtils.defaultLanguageForMcc(0));
+        // mcc not defined, hence default
+        assertEquals(null, LocaleUtils.defaultLanguageForMcc(2000));
     }
 
     @SmallTest
-    @Ignore
+    @Test
     public void testLang_India() throws Exception {
-        assertEquals("en", MccTable.defaultLanguageForMcc(404));
-        assertEquals("en", MccTable.defaultLanguageForMcc(405));
-        assertEquals("en", MccTable.defaultLanguageForMcc(406));
+        assertEquals("en", LocaleUtils.defaultLanguageForMcc(404));
+        assertEquals("en", LocaleUtils.defaultLanguageForMcc(405));
+        assertEquals("en", LocaleUtils.defaultLanguageForMcc(406));
     }
 
     @SmallTest
-    @Ignore
+    @Test
     public void testLocale() throws Exception {
         assertEquals(Locale.forLanguageTag("en-CA"),
-                MccTable.getLocaleFromMcc(getContext(), 302, null));
+                LocaleUtils.getLocaleFromMcc(getContext(), 302, null));
         assertEquals(Locale.forLanguageTag("en-GB"),
-                MccTable.getLocaleFromMcc(getContext(), 234, null));
+                LocaleUtils.getLocaleFromMcc(getContext(), 234, null));
         assertEquals(Locale.forLanguageTag("en-US"),
-                MccTable.getLocaleFromMcc(getContext(), 0, "en"));
+                LocaleUtils.getLocaleFromMcc(getContext(), 0, "en"));
         assertEquals(Locale.forLanguageTag("zh-HK"),
-                MccTable.getLocaleFromMcc(getContext(), 454, null));
+                LocaleUtils.getLocaleFromMcc(getContext(), 454, null));
         assertEquals(Locale.forLanguageTag("en-HK"),
-                MccTable.getLocaleFromMcc(getContext(), 454, "en"));
+                LocaleUtils.getLocaleFromMcc(getContext(), 454, "en"));
         assertEquals(Locale.forLanguageTag("zh-TW"),
-                MccTable.getLocaleFromMcc(getContext(), 466, null));
+                LocaleUtils.getLocaleFromMcc(getContext(), 466, null));
+    }
+
+    private Context getContext() {
+        return InstrumentationRegistry.getContext();
     }
 
     @SmallTest
-    @Ignore
+    @Test
     public void testSmDigits() throws Exception {
         assertEquals(3, MccTable.smallestDigitsMccForMnc(312));
         assertEquals(2, MccTable.smallestDigitsMccForMnc(430));
         assertEquals(3, MccTable.smallestDigitsMccForMnc(365));
         assertEquals(2, MccTable.smallestDigitsMccForMnc(536));
-        assertEquals(2, MccTable.smallestDigitsMccForMnc(352));  // sd not defined, hence default
-        assertEquals(2, MccTable.smallestDigitsMccForMnc(0));    // mcc not defined, hence default
-        assertEquals(2, MccTable.smallestDigitsMccForMnc(2000)); // mcc not defined, hence default
+        // sd not defined, hence default
+        assertEquals(2, MccTable.smallestDigitsMccForMnc(352));
+        // mcc not defined, hence default
+        assertEquals(2, MccTable.smallestDigitsMccForMnc(0));
+        // mcc not defined, hence default
+        assertEquals(2, MccTable.smallestDigitsMccForMnc(2000));
     }
 }
diff --git a/tests/telephonytests/src/com/android/internal/telephony/MissedIncomingCallSmsFilterTest.java b/tests/telephonytests/src/com/android/internal/telephony/MissedIncomingCallSmsFilterTest.java
new file mode 100644
index 0000000..d97f7ab
--- /dev/null
+++ b/tests/telephonytests/src/com/android/internal/telephony/MissedIncomingCallSmsFilterTest.java
@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) 2020 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;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.verify;
+
+import android.content.Context;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.PersistableBundle;
+import android.telecom.TelecomManager;
+import android.telephony.CarrierConfigManager;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import com.android.internal.telephony.uicc.IccUtils;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+
+/**
+ * Unit test for {@link MissedIncomingCallSmsFilter}
+ */
+public class MissedIncomingCallSmsFilterTest extends TelephonyTest {
+
+    private static final String FAKE_CARRIER_SMS_ORIGINATOR = "+18584121234";
+
+    private static final String FAKE_CALLER_ID = "6501234567";
+
+    private MissedIncomingCallSmsFilter mFilterUT;
+
+    private PersistableBundle mBundle;
+
+    @Before
+    public void setUp() throws Exception {
+        super.setUp(MissedIncomingCallSmsFilterTest.class.getSimpleName());
+
+        mBundle = mContextFixture.getCarrierConfigBundle();
+
+        mFilterUT = new MissedIncomingCallSmsFilter(mPhone);
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        super.tearDown();
+    }
+
+    @Test
+    @SmallTest
+    public void testMissedIncomingCallwithCallerId() {
+        mBundle.putStringArray(
+                CarrierConfigManager.KEY_MISSED_INCOMING_CALL_SMS_ORIGINATOR_STRING_ARRAY,
+                new String[]{FAKE_CARRIER_SMS_ORIGINATOR});
+        mBundle.putStringArray(
+                CarrierConfigManager.KEY_MISSED_INCOMING_CALL_SMS_PATTERN_STRING_ARRAY,
+                new String[]{"^(?<month>0[1-9]|1[012])\\/(?<day>0[1-9]|1[0-9]|2[0-9]|3[0-1]) "
+                        + "(?<hour>[0-1][0-9]|2[0-3]):(?<minute>[0-5][0-9])\\s*(?<callerId>[0-9]+)"
+                        + "\\s*$"});
+
+        String smsPduString = "07919107739667F9040B918185141232F400000210413141114A17B0D82B4603C170"
+                + "BA580DA4B0D56031D98C56B3DD1A";
+        byte[][] pdus = {IccUtils.hexStringToBytes(smsPduString)};
+        assertTrue(mFilterUT.filter(pdus, SmsConstants.FORMAT_3GPP));
+
+        TelecomManager telecomManager = (TelecomManager) mContext.getSystemService(
+                Context.TELECOM_SERVICE);
+
+        ArgumentCaptor<Bundle> captor = ArgumentCaptor.forClass(Bundle.class);
+        verify(telecomManager).addNewIncomingCall(any(), captor.capture());
+
+        Bundle bundle = captor.getValue();
+        Uri uri = bundle.getParcelable(TelecomManager.EXTRA_INCOMING_CALL_ADDRESS);
+
+        assertEquals(FAKE_CALLER_ID, uri.getSchemeSpecificPart());
+    }
+}
diff --git a/src/java/com/android/internal/telephony/test/ModelInterpreter.java b/tests/telephonytests/src/com/android/internal/telephony/ModelInterpreter.java
similarity index 98%
rename from src/java/com/android/internal/telephony/test/ModelInterpreter.java
rename to tests/telephonytests/src/com/android/internal/telephony/ModelInterpreter.java
index 7930b56..ddb99d9 100644
--- a/src/java/com/android/internal/telephony/test/ModelInterpreter.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/ModelInterpreter.java
@@ -16,9 +16,11 @@
 
 package com.android.internal.telephony.test;
 
+import android.compat.annotation.UnsupportedAppUsage;
 import android.os.HandlerThread;
 import android.os.Looper;
-import android.telephony.Rlog;
+
+import com.android.telephony.Rlog;
 
 import java.io.IOException;
 import java.io.InputStream;
@@ -121,6 +123,7 @@
 
 class InterpreterEx extends Exception
 {
+    @UnsupportedAppUsage
     public
     InterpreterEx (String result)
     {
diff --git a/tests/telephonytests/src/com/android/internal/telephony/MultiSimSettingControllerTest.java b/tests/telephonytests/src/com/android/internal/telephony/MultiSimSettingControllerTest.java
index 91ed608..af72c70 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/MultiSimSettingControllerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/MultiSimSettingControllerTest.java
@@ -30,25 +30,30 @@
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.ArgumentMatchers.nullable;
 import static org.mockito.Mockito.clearInvocations;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.verify;
 
 import android.content.Intent;
-import android.os.HandlerThread;
 import android.os.ParcelUuid;
+import android.os.PersistableBundle;
 import android.provider.Settings;
+import android.telephony.CarrierConfigManager;
 import android.telephony.SubscriptionInfo;
 import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyManager;
 import android.test.suitebuilder.annotation.SmallTest;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
 
 import com.android.internal.telephony.dataconnection.DataEnabledSettings;
 
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
+import org.junit.runner.RunWith;
 import org.mockito.ArgumentCaptor;
 import org.mockito.Mock;
 
@@ -56,6 +61,8 @@
 import java.util.List;
 import java.util.UUID;
 
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper
 public class MultiSimSettingControllerTest extends TelephonyTest {
     private static final int SINGLE_SIM = 1;
     private static final int DUAL_SIM = 2;
@@ -70,7 +77,6 @@
     private DataEnabledSettings mDataEnabledSettingsMock1;
     @Mock
     private DataEnabledSettings mDataEnabledSettingsMock2;
-    private HandlerThread mHandlerThread;
     private Phone[] mPhones;
 
     ParcelUuid mGroupUuid1 = new ParcelUuid(UUID.randomUUID());
@@ -83,19 +89,19 @@
             "T-mobile", 0, 255, "12345", 0, null, "310", "260",
             "156", false, null, null, -1, false, mGroupUuid1.toString(), false,
             TelephonyManager.UNKNOWN_CARRIER_ID, SubscriptionManager.PROFILE_CLASS_DEFAULT,
-            SubscriptionManager.SUBSCRIPTION_TYPE_LOCAL_SIM, null, null);
+            SubscriptionManager.SUBSCRIPTION_TYPE_LOCAL_SIM, null, null, true);
 
     private SubscriptionInfo mSubInfo3 = new SubscriptionInfo(3, "subInfo3 IccId", -1, "T-mobile",
             "T-mobile", 0, 255, "12345", 0, null, "310", "260",
             "156", false, null, null, -1, false, mGroupUuid1.toString(), false,
             TelephonyManager.UNKNOWN_CARRIER_ID, SubscriptionManager.PROFILE_CLASS_DEFAULT,
-            SubscriptionManager.SUBSCRIPTION_TYPE_LOCAL_SIM, null, null);
+            SubscriptionManager.SUBSCRIPTION_TYPE_LOCAL_SIM, null, null, true);
 
     private SubscriptionInfo mSubInfo4 = new SubscriptionInfo(4, "subInfo4 IccId", -1, "T-mobile",
             "T-mobile", 0, 255, "12345", 0, null, "310", "260",
             "156", false, null, null, -1, false, mGroupUuid1.toString(), false,
             TelephonyManager.UNKNOWN_CARRIER_ID, SubscriptionManager.PROFILE_CLASS_DEFAULT,
-            SubscriptionManager.SUBSCRIPTION_TYPE_LOCAL_SIM, null, null);
+            SubscriptionManager.SUBSCRIPTION_TYPE_LOCAL_SIM, null, null, true);
 
     @Before
     public void setUp() throws Exception {
@@ -105,6 +111,7 @@
         // Sub 1 is the default sub.
         // Sub 1 is in slot 0; sub 2 is in slot 1.
         doReturn(DUAL_SIM).when(mTelephonyManager).getPhoneCount();
+        doReturn(DUAL_SIM).when(mTelephonyManager).getActiveModemCount();
         doReturn(1).when(mSubControllerMock).getDefaultDataSubId();
         doReturn(1).when(mSubControllerMock).getDefaultVoiceSubId();
         doReturn(1).when(mSubControllerMock).getDefaultSmsSubId();
@@ -117,7 +124,7 @@
         doReturn(2).when(mPhoneMock2).getSubId();
         List<SubscriptionInfo> infoList = Arrays.asList(mSubInfo1, mSubInfo2);
         doReturn(infoList).when(mSubControllerMock)
-                .getActiveSubscriptionInfoList(anyString());
+                .getActiveSubscriptionInfoList(anyString(), nullable(String.class));
         doReturn(new int[]{1, 2}).when(mSubControllerMock).getActiveSubIdList(anyBoolean());
 
         mPhones = new Phone[] {mPhoneMock1, mPhoneMock2};
@@ -135,31 +142,15 @@
 
         replaceInstance(PhoneFactory.class, "sPhones", null, mPhones);
         replaceInstance(SubscriptionController.class, "sInstance", null, mSubControllerMock);
-        mHandlerThread = new HandlerThread("MultiSimSettingControllerTestThread") {
-            @Override
-            public void onLooperPrepared() {
-                mMultiSimSettingControllerUT = new MultiSimSettingController(
-                        mContext, mSubControllerMock);
-            }
-        };
-
-        mHandlerThread.start();
-        waitABit();
+        mMultiSimSettingControllerUT = new MultiSimSettingController(mContext, mSubControllerMock);
+        processAllMessages();
     }
 
     @After
     public void tearDown() throws Exception {
-        mHandlerThread.quit();
         super.tearDown();
     }
 
-    private void waitABit() {
-        try {
-            Thread.sleep(250);
-        } catch (Exception e) {
-        }
-    }
-
     @Test
     @SmallTest
     public void testTestSubInfoChangeBeforeAllSubReady() throws Exception {
@@ -175,19 +166,20 @@
         doReturn(SubscriptionManager.INVALID_PHONE_INDEX).when(mSubControllerMock).getPhoneId(2);
         doReturn(SubscriptionManager.INVALID_SUBSCRIPTION_ID).when(mPhoneMock2).getSubId();
         List<SubscriptionInfo> infoList = Arrays.asList(mSubInfo1);
-        doReturn(infoList).when(mSubControllerMock).getActiveSubscriptionInfoList(anyString());
+        doReturn(infoList).when(mSubControllerMock).getActiveSubscriptionInfoList(anyString(),
+                nullable(String.class));
         doReturn(new int[]{1}).when(mSubControllerMock).getActiveSubIdList(anyBoolean());
 
         // Mark subscription ready as false. The below sub info change should be ignored.
         mMultiSimSettingControllerUT.notifySubscriptionInfoChanged();
-        waitABit();
+        processAllMessages();
         verify(mSubControllerMock, never()).setDefaultDataSubId(anyInt());
         verify(mSubControllerMock, never()).setDefaultVoiceSubId(anyInt());
         verify(mSubControllerMock, never()).setDefaultSmsSubId(anyInt());
 
         mMultiSimSettingControllerUT.notifyAllSubscriptionLoaded();
         mMultiSimSettingControllerUT.notifyCarrierConfigChanged(0, 1);
-        waitABit();
+        processAllMessages();
 
         // Sub 1 should be default sub silently.
         verify(mSubControllerMock).setDefaultDataSubId(1);
@@ -212,12 +204,13 @@
         doReturn(SubscriptionManager.INVALID_PHONE_INDEX).when(mSubControllerMock).getPhoneId(2);
         doReturn(SubscriptionManager.INVALID_SUBSCRIPTION_ID).when(mPhoneMock2).getSubId();
         List<SubscriptionInfo> infoList = Arrays.asList(mSubInfo1);
-        doReturn(infoList).when(mSubControllerMock).getActiveSubscriptionInfoList(anyString());
+        doReturn(infoList).when(mSubControllerMock).getActiveSubscriptionInfoList(anyString(),
+                nullable(String.class));
         doReturn(new int[]{1}).when(mSubControllerMock).getActiveSubIdList(anyBoolean());
 
         mMultiSimSettingControllerUT.notifyAllSubscriptionLoaded();
         mMultiSimSettingControllerUT.notifyCarrierConfigChanged(0, 1);
-        waitABit();
+        processAllMessages();
 
         // Sub 1 should be default sub silently.
         // Sub 1 switches to sub 2 in the same slot.
@@ -227,12 +220,13 @@
         doReturn(SubscriptionManager.INVALID_PHONE_INDEX).when(mSubControllerMock).getPhoneId(1);
         doReturn(2).when(mPhoneMock1).getSubId();
         infoList = Arrays.asList(mSubInfo2);
-        doReturn(infoList).when(mSubControllerMock).getActiveSubscriptionInfoList(anyString());
+        doReturn(infoList).when(mSubControllerMock).getActiveSubscriptionInfoList(anyString(),
+                nullable(String.class));
         doReturn(new int[]{2}).when(mSubControllerMock).getActiveSubIdList(anyBoolean());
 
         mMultiSimSettingControllerUT.notifySubscriptionInfoChanged();
         mMultiSimSettingControllerUT.notifyCarrierConfigChanged(0, 2);
-        waitABit();
+        processAllMessages();
 
         // Sub 1 should be default sub silently.
         verify(mSubControllerMock).setDefaultDataSubId(2);
@@ -250,12 +244,13 @@
         doReturn(SubscriptionManager.INVALID_PHONE_INDEX).when(mSubControllerMock).getPhoneId(2);
         doReturn(SubscriptionManager.INVALID_SUBSCRIPTION_ID).when(mPhoneMock2).getSubId();
         List<SubscriptionInfo> infoList = Arrays.asList(mSubInfo1);
-        doReturn(infoList).when(mSubControllerMock).getActiveSubscriptionInfoList(anyString());
+        doReturn(infoList).when(mSubControllerMock).getActiveSubscriptionInfoList(anyString(),
+                nullable(String.class));
         doReturn(new int[]{1}).when(mSubControllerMock).getActiveSubIdList(anyBoolean());
 
         mMultiSimSettingControllerUT.notifyAllSubscriptionLoaded();
         mMultiSimSettingControllerUT.notifyCarrierConfigChanged(0, 1);
-        waitABit();
+        processAllMessages();
 
         // Sub 1 should be default sub silently.
         verify(mSubControllerMock).setDefaultDataSubId(1);
@@ -270,12 +265,13 @@
         doReturn(1).when(mSubControllerMock).getPhoneId(2);
         doReturn(2).when(mPhoneMock2).getSubId();
         infoList = Arrays.asList(mSubInfo1, mSubInfo2);
-        doReturn(infoList).when(mSubControllerMock).getActiveSubscriptionInfoList(anyString());
+        doReturn(infoList).when(mSubControllerMock).getActiveSubscriptionInfoList(anyString(),
+                nullable(String.class));
         doReturn(new int[]{1, 2}).when(mSubControllerMock).getActiveSubIdList(anyBoolean());
 
         mMultiSimSettingControllerUT.notifySubscriptionInfoChanged();
         mMultiSimSettingControllerUT.notifyCarrierConfigChanged(1, 2);
-        waitABit();
+        processAllMessages();
 
         // Intent should be broadcast to ask default data selection.
         Intent intent = captureBroadcastIntent();
@@ -292,12 +288,13 @@
         doReturn(1).when(mSubControllerMock).getPhoneId(3);
         doReturn(3).when(mPhoneMock2).getSubId();
         infoList = Arrays.asList(mSubInfo1, mSubInfo3);
-        doReturn(infoList).when(mSubControllerMock).getActiveSubscriptionInfoList(anyString());
+        doReturn(infoList).when(mSubControllerMock).getActiveSubscriptionInfoList(anyString(),
+                nullable(String.class));
         doReturn(new int[]{1, 3}).when(mSubControllerMock).getActiveSubIdList(anyBoolean());
 
         mMultiSimSettingControllerUT.notifySubscriptionInfoChanged();
         mMultiSimSettingControllerUT.notifyCarrierConfigChanged(1, 3);
-        waitABit();
+        processAllMessages();
 
         // Intent should be broadcast to ask default data selection.
         intent = captureBroadcastIntent();
@@ -315,18 +312,18 @@
         mMultiSimSettingControllerUT.notifyAllSubscriptionLoaded();
         mMultiSimSettingControllerUT.notifyCarrierConfigChanged(0, 1);
         mMultiSimSettingControllerUT.notifyCarrierConfigChanged(1, 2);
-        waitABit();
+        processAllMessages();
         verify(mDataEnabledSettingsMock2).setUserDataEnabled(false);
 
         // Enable on non-default sub should trigger setDefaultDataSubId.
         mMultiSimSettingControllerUT.notifyUserDataEnabled(2, true);
-        waitABit();
+        processAllMessages();
         verify(mSubControllerMock).setDefaultDataSubId(2);
 
         // Changing default data to sub 2 should trigger disabling data on sub 1.
         doReturn(2).when(mSubControllerMock).getDefaultDataSubId();
         mMultiSimSettingControllerUT.notifyDefaultDataSubChanged();
-        waitABit();
+        processAllMessages();
         verify(mDataEnabledSettingsMock1).setUserDataEnabled(false);
 
         doReturn(1).when(mSubControllerMock).getDefaultDataSubId();
@@ -337,12 +334,13 @@
         clearInvocations(mSubControllerMock);
         doReturn(false).when(mSubControllerMock).isActiveSubId(1);
         List<SubscriptionInfo> infoList = Arrays.asList(mSubInfo2);
-        doReturn(infoList).when(mSubControllerMock).getActiveSubscriptionInfoList(anyString());
+        doReturn(infoList).when(mSubControllerMock).getActiveSubscriptionInfoList(anyString(),
+                nullable(String.class));
         doReturn(new int[]{2}).when(mSubControllerMock).getActiveSubIdList(anyBoolean());
         mMultiSimSettingControllerUT.notifySubscriptionInfoChanged();
         mMultiSimSettingControllerUT.notifyCarrierConfigChanged(
                 1, SubscriptionManager.INVALID_SUBSCRIPTION_ID);
-        waitABit();
+        processAllMessages();
         verify(mSubControllerMock).setDefaultDataSubId(SubscriptionManager.INVALID_SUBSCRIPTION_ID);
         verify(mSubControllerMock).setDefaultSmsSubId(SubscriptionManager.INVALID_SUBSCRIPTION_ID);
         verify(mSubControllerMock, never()).setDefaultVoiceSubId(anyInt());
@@ -366,16 +364,16 @@
         mMultiSimSettingControllerUT.notifyAllSubscriptionLoaded();
         mMultiSimSettingControllerUT.notifyCarrierConfigChanged(0, 1);
         mMultiSimSettingControllerUT.notifyCarrierConfigChanged(1, 2);
-        waitABit();
+        processAllMessages();
 
         // Create subscription grouping.
         doReturn(mGroupUuid1).when(mSubControllerMock).getGroupUuid(2);
         doReturn(mGroupUuid1).when(mSubControllerMock).getGroupUuid(3);
         doReturn(mGroupUuid1).when(mSubControllerMock).getGroupUuid(4);
         doReturn(Arrays.asList(mSubInfo2, mSubInfo3, mSubInfo4)).when(mSubControllerMock)
-                .getSubscriptionsInGroup(any(), anyString());
+                .getSubscriptionsInGroup(any(), anyString(), nullable(String.class));
         mMultiSimSettingControllerUT.notifySubscriptionGroupChanged(mGroupUuid1);
-        waitABit();
+        processAllMessages();
         // This should result in setting sync.
         assertTrue(GlobalSettingsHelper.getBoolean(
                 mContext, Settings.Global.MOBILE_DATA, 3, false));
@@ -392,10 +390,10 @@
         // Making sub 1 default data sub should result in disabling data on sub 2, 3, 4.
         doReturn(1).when(mSubControllerMock).getDefaultDataSubId();
         mMultiSimSettingControllerUT.notifyDefaultDataSubChanged();
-        waitABit();
+        processAllMessages();
         verify(mDataEnabledSettingsMock2).setUserDataEnabled(false);
         mMultiSimSettingControllerUT.notifyUserDataEnabled(2, false);
-        waitABit();
+        processAllMessages();
         assertFalse(GlobalSettingsHelper.getBoolean(
                 mContext, Settings.Global.MOBILE_DATA, 3, true));
         assertFalse(GlobalSettingsHelper.getBoolean(
@@ -410,12 +408,13 @@
         doReturn(2).when(mSubControllerMock).getDefaultSmsSubId();
         doReturn(1).when(mSubControllerMock).getDefaultVoiceSubId();
         List<SubscriptionInfo> infoList = Arrays.asList(mSubInfo1, mSubInfo3);
-        doReturn(infoList).when(mSubControllerMock).getActiveSubscriptionInfoList(anyString());
+        doReturn(infoList).when(mSubControllerMock).getActiveSubscriptionInfoList(anyString(),
+                nullable(String.class));
         doReturn(new int[]{1, 3}).when(mSubControllerMock).getActiveSubIdList(anyBoolean());
 
         mMultiSimSettingControllerUT.notifySubscriptionInfoChanged();
         mMultiSimSettingControllerUT.notifyCarrierConfigChanged(1, 3);
-        waitABit();
+        processAllMessages();
 
         verify(mSubControllerMock).setDefaultDataSubId(3);
         verify(mSubControllerMock).setDefaultSmsSubId(3);
@@ -438,7 +437,7 @@
         mMultiSimSettingControllerUT.notifyAllSubscriptionLoaded();
         mMultiSimSettingControllerUT.notifyCarrierConfigChanged(0, 1);
         mMultiSimSettingControllerUT.notifyCarrierConfigChanged(1, 2);
-        waitABit();
+        processAllMessages();
         verify(mSubControllerMock).setDefaultDataSubId(2);
         verify(mDataEnabledSettingsMock1, never()).setUserDataEnabled(anyBoolean());
         // No user selection needed, no intent should be sent.
@@ -451,11 +450,11 @@
         // Toggle data on sub 1 or sub 2. Nothing should happen as they are independent.
         mMultiSimSettingControllerUT.notifyUserDataEnabled(1, false);
         mMultiSimSettingControllerUT.notifyUserDataEnabled(1, true);
-        waitABit();
+        processAllMessages();
         verify(mSubControllerMock, never()).setDefaultDataSubId(anyInt());
         mMultiSimSettingControllerUT.notifyUserDataEnabled(2, false);
         mMultiSimSettingControllerUT.notifyUserDataEnabled(2, true);
-        waitABit();
+        processAllMessages();
         verify(mSubControllerMock, never()).setDefaultDataSubId(anyInt());
         verify(mDataEnabledSettingsMock1, never()).setUserDataEnabled(anyBoolean());
         verify(mDataEnabledSettingsMock2, never()).setUserDataEnabled(anyBoolean());
@@ -477,7 +476,7 @@
         mMultiSimSettingControllerUT.notifyAllSubscriptionLoaded();
         mMultiSimSettingControllerUT.notifyCarrierConfigChanged(0, 1);
         mMultiSimSettingControllerUT.notifyCarrierConfigChanged(1, 2);
-        waitABit();
+        processAllMessages();
         verify(mSubControllerMock).setDefaultDataSubId(2);
         // No user selection needed, no intent should be sent.
         verify(mContext, never()).sendBroadcast(any());
@@ -487,9 +486,9 @@
         GlobalSettingsHelper.setBoolean(mContext, Settings.Global.MOBILE_DATA, 2, false);
         // Group sub 1 with sub 2.
         doReturn(Arrays.asList(mSubInfo1, mSubInfo2)).when(mSubControllerMock)
-                .getSubscriptionsInGroup(any(), anyString());
+                .getSubscriptionsInGroup(any(), anyString(), nullable(String.class));
         mMultiSimSettingControllerUT.notifySubscriptionGroupChanged(mGroupUuid1);
-        waitABit();
+        processAllMessages();
         // This should result in setting sync.
         verify(mDataEnabledSettingsMock1).setUserDataEnabled(false, false);
         assertFalse(GlobalSettingsHelper.getBoolean(
@@ -499,7 +498,7 @@
         doReturn(false).when(mPhoneMock2).isUserDataEnabled();
         // Turning data on on sub 2. Sub 1 should also be turned on.
         mMultiSimSettingControllerUT.notifyUserDataEnabled(2, true);
-        waitABit();
+        processAllMessages();
         verify(mDataEnabledSettingsMock1).setUserDataEnabled(true, false);
         // No user selection needed, no intent should be sent.
         verify(mContext, never()).sendBroadcast(any());
@@ -522,14 +521,14 @@
         mMultiSimSettingControllerUT.notifyAllSubscriptionLoaded();
         mMultiSimSettingControllerUT.notifyCarrierConfigChanged(0, 1);
         mMultiSimSettingControllerUT.notifyCarrierConfigChanged(1, 2);
-        waitABit();
+        processAllMessages();
 
         // Create subscription grouping.
         replaceInstance(SubscriptionInfo.class, "mGroupUUID", mSubInfo1, mGroupUuid1);
         doReturn(Arrays.asList(mSubInfo1, mSubInfo2)).when(mSubControllerMock)
-                .getSubscriptionsInGroup(any(), anyString());
+                .getSubscriptionsInGroup(any(), anyString(), nullable(String.class));
         mMultiSimSettingControllerUT.notifySubscriptionGroupChanged(mGroupUuid1);
-        waitABit();
+        processAllMessages();
         // This should result in setting sync.
         verify(mDataEnabledSettingsMock2).setUserDataEnabled(true, false);
         assertFalse(GlobalSettingsHelper.getBoolean(
@@ -539,7 +538,7 @@
         // Turning off user data on sub 1.
         doReturn(false).when(mPhoneMock1).isUserDataEnabled();
         mMultiSimSettingControllerUT.notifyUserDataEnabled(1, false);
-        waitABit();
+        processAllMessages();
         verify(mDataEnabledSettingsMock2).setUserDataEnabled(false, false);
     }
 
@@ -551,13 +550,13 @@
         // Sub 2 should have mobile data off, but it shouldn't happen until carrier configs are
         // loaded on both subscriptions.
         mMultiSimSettingControllerUT.notifyAllSubscriptionLoaded();
-        waitABit();
+        processAllMessages();
         verify(mDataEnabledSettingsMock2, never()).setUserDataEnabled(false);
         mMultiSimSettingControllerUT.notifyCarrierConfigChanged(0, 1);
-        waitABit();
+        processAllMessages();
         verify(mDataEnabledSettingsMock2, never()).setUserDataEnabled(false);
         mMultiSimSettingControllerUT.notifyCarrierConfigChanged(1, 2);
-        waitABit();
+        processAllMessages();
         verify(mDataEnabledSettingsMock2).setUserDataEnabled(false);
 
         // Switch from sub 2 to sub 3 in phone[1].
@@ -568,20 +567,90 @@
         doReturn(1).when(mSubControllerMock).getPhoneId(3);
         doReturn(3).when(mPhoneMock2).getSubId();
         List<SubscriptionInfo> infoList = Arrays.asList(mSubInfo1, mSubInfo3);
-        doReturn(infoList).when(mSubControllerMock).getActiveSubscriptionInfoList(anyString());
+        doReturn(infoList).when(mSubControllerMock).getActiveSubscriptionInfoList(anyString(),
+                nullable(String.class));
         doReturn(new int[]{1, 3}).when(mSubControllerMock).getActiveSubIdList(anyBoolean());
 
         // Nothing should happen until carrier config change is notified on sub 3.
         mMultiSimSettingControllerUT.notifySubscriptionInfoChanged();
-        waitABit();
+        processAllMessages();
         verify(mContext, never()).sendBroadcast(any());
 
         mMultiSimSettingControllerUT.notifyCarrierConfigChanged(1, 3);
-        waitABit();
+        processAllMessages();
         // Intent should be broadcast to ask default data selection.
         Intent intent = captureBroadcastIntent();
         assertEquals(ACTION_PRIMARY_SUBSCRIPTION_LIST_CHANGED, intent.getAction());
         assertEquals(EXTRA_DEFAULT_SUBSCRIPTION_SELECT_TYPE_DATA,
                 intent.getIntExtra(EXTRA_DEFAULT_SUBSCRIPTION_SELECT_TYPE, -1));
     }
+
+    @Test
+    @SmallTest
+    // b/146446143
+    public void testGroupChangeOnInactiveSub_shouldNotMarkAsDefaultDataSub() throws Exception {
+        // Make sub1 and sub3 as active sub.
+        doReturn(false).when(mSubControllerMock).isActiveSubId(2);
+        doReturn(true).when(mSubControllerMock).isActiveSubId(3);
+        doReturn(SubscriptionManager.INVALID_PHONE_INDEX).when(mSubControllerMock).getPhoneId(2);
+        doReturn(1).when(mSubControllerMock).getPhoneId(3);
+        doReturn(3).when(mPhoneMock2).getSubId();
+        List<SubscriptionInfo> infoList = Arrays.asList(mSubInfo1, mSubInfo3);
+        doReturn(infoList).when(mSubControllerMock).getActiveSubscriptionInfoList(anyString(),
+                nullable(String.class));
+        doReturn(new int[]{1, 3}).when(mSubControllerMock).getActiveSubIdList(anyBoolean());
+        doReturn(Arrays.asList(mSubInfo2, mSubInfo3, mSubInfo4)).when(mSubControllerMock)
+                .getSubscriptionsInGroup(any(), anyString(), nullable(String.class));
+
+        // Sub 3 and sub 2's mobile data are enabled, and sub 3 is the default data sub.
+        doReturn(3).when(mSubControllerMock).getDefaultDataSubId();
+        GlobalSettingsHelper.setBoolean(mContext, Settings.Global.MOBILE_DATA, 1, false);
+        GlobalSettingsHelper.setBoolean(mContext, Settings.Global.MOBILE_DATA, 2, true);
+        GlobalSettingsHelper.setBoolean(mContext, Settings.Global.MOBILE_DATA, 3, true);
+        doReturn(false).when(mPhoneMock1).isUserDataEnabled();
+        doReturn(true).when(mPhoneMock2).isUserDataEnabled();
+        // Sub 2 should have mobile data off, but it shouldn't happen until carrier configs are
+        // loaded on both subscriptions.
+        mMultiSimSettingControllerUT.notifyAllSubscriptionLoaded();
+        mMultiSimSettingControllerUT.notifyCarrierConfigChanged(0, 1);
+        mMultiSimSettingControllerUT.notifyCarrierConfigChanged(1, 3);
+        processAllMessages();
+
+        // Mark sub3 as oppt and notify grouping
+        doReturn(true).when(mSubControllerMock).isOpportunistic(3);
+        mMultiSimSettingControllerUT.notifySubscriptionGroupChanged(mGroupUuid1);
+        processAllMessages();
+        // Shouldn't mark sub 2 as default data, as sub 2 is in active.
+        verify(mSubControllerMock, never()).setDefaultDataSubId(2);
+    }
+
+    @Test
+    @SmallTest
+    // b/146446143
+    public void testCarrierConfigChangeWithInvalidSubId_shouldAlwaysTryToGetSubId()
+            throws Exception {
+        doReturn(true).when(mPhoneMock1).isUserDataEnabled();
+        doReturn(true).when(mPhoneMock2).isUserDataEnabled();
+        mMultiSimSettingControllerUT.notifyAllSubscriptionLoaded();
+        processAllMessages();
+        mMultiSimSettingControllerUT.notifyCarrierConfigChanged(0, 1);
+        // Notify carrier config change on phone1 without specifying subId.
+        mMultiSimSettingControllerUT.notifyCarrierConfigChanged(1,
+                SubscriptionManager.INVALID_SUBSCRIPTION_ID);
+        processAllMessages();
+        // Nothing should happen as carrier config is not ready for sub 2.
+        verify(mDataEnabledSettingsMock2, never()).setUserDataEnabled(false);
+
+        // Still notify carrier config without specifying subId2, but this time subController
+        // and CarrierConfigManager have subId 2 active and ready.
+        doReturn(new int[] {2}).when(mSubControllerMock).getSubId(1);
+        CarrierConfigManager cm = (CarrierConfigManager) mContext.getSystemService(
+                mContext.CARRIER_CONFIG_SERVICE);
+        doReturn(new PersistableBundle()).when(cm).getConfigForSubId(2);
+        mMultiSimSettingControllerUT.notifyCarrierConfigChanged(1,
+                SubscriptionManager.INVALID_SUBSCRIPTION_ID);
+        processAllMessages();
+        // This time user data should be disabled on phone1.
+        verify(mDataEnabledSettingsMock2).setUserDataEnabled(false);
+    }
 }
diff --git a/tests/telephonytests/src/com/android/internal/telephony/NetworkRegistrationInfoTest.java b/tests/telephonytests/src/com/android/internal/telephony/NetworkRegistrationInfoTest.java
index 96e37c5..e2c32a4 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/NetworkRegistrationInfoTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/NetworkRegistrationInfoTest.java
@@ -43,6 +43,7 @@
                 .setAccessNetworkTechnology(TelephonyManager.NETWORK_TYPE_LTE)
                 .setAvailableServices(Arrays.asList(NetworkRegistrationInfo.SERVICE_TYPE_DATA))
                 .setCellIdentity(new CellIdentityLte())
+                .setRegisteredPlmn("12345")
                 .build();
 
         Parcel p = Parcel.obtain();
@@ -52,4 +53,19 @@
         NetworkRegistrationInfo newNrs = NetworkRegistrationInfo.CREATOR.createFromParcel(p);
         assertEquals(nri, newNrs);
     }
+
+
+    @Test
+    @SmallTest
+    public void testDefaultValues() {
+        NetworkRegistrationInfo nri = new NetworkRegistrationInfo.Builder().build();
+        assertEquals("", nri.getRegisteredPlmn());
+    }
+
+    @Test
+    @SmallTest
+    public void testBuilder() {
+        assertEquals("12345", new NetworkRegistrationInfo.Builder()
+                .setRegisteredPlmn("12345").build().getRegisteredPlmn());
+    }
 }
diff --git a/tests/telephonytests/src/com/android/internal/telephony/NetworkScanResultTest.java b/tests/telephonytests/src/com/android/internal/telephony/NetworkScanResultTest.java
index 1b58f36..f473133 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/NetworkScanResultTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/NetworkScanResultTest.java
@@ -32,6 +32,7 @@
 import org.junit.Test;
 
 import java.util.ArrayList;
+import java.util.Collections;
 
 /** Unit tests for {@link NetworkScanResult}. */
 public class NetworkScanResultTest {
@@ -41,7 +42,8 @@
     public void testParcel() {
         ArrayList<CellInfo> infos = new ArrayList<CellInfo>();
 
-        CellIdentityGsm cig = new CellIdentityGsm(1, 2, 40, 5, "001", "01", "test", "tst");
+        CellIdentityGsm cig = new CellIdentityGsm(1, 2, 40, 5, "001", "01", "test", "tst",
+                Collections.emptyList());
         CellSignalStrengthGsm cssg = new CellSignalStrengthGsm(5, 6, 7);
         CellInfoGsm gsm = new CellInfoGsm();
         gsm.setRegistered(true);
@@ -51,7 +53,8 @@
         infos.add(gsm);
 
         CellIdentityLte cil = new CellIdentityLte(
-                10, 5, 200, 2000, 10000, "001", "01", "test", "tst");
+                10, 5, 200, 2000, new int[] {1, 2}, 10000, "001", "01", "test", "tst",
+                Collections.emptyList(), null);
         CellSignalStrengthLte cssl = new CellSignalStrengthLte(15, 16, 17, 18, 19, 20);
         CellInfoLte lte = new CellInfoLte();
         lte.setRegistered(false);
diff --git a/tests/telephonytests/src/com/android/internal/telephony/NetworkTypeControllerTest.java b/tests/telephonytests/src/com/android/internal/telephony/NetworkTypeControllerTest.java
new file mode 100644
index 0000000..eafc486
--- /dev/null
+++ b/tests/telephonytests/src/com/android/internal/telephony/NetworkTypeControllerTest.java
@@ -0,0 +1,650 @@
+/*
+ * Copyright (C) 2020 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;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.Mockito.doReturn;
+
+import android.content.Intent;
+import android.os.AsyncResult;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.PersistableBundle;
+import android.telephony.CarrierConfigManager;
+import android.telephony.NetworkRegistrationInfo;
+import android.telephony.ServiceState;
+import android.telephony.TelephonyDisplayInfo;
+import android.telephony.TelephonyManager;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+
+import com.android.internal.telephony.dataconnection.DcController;
+import com.android.internal.util.IState;
+import com.android.internal.util.StateMachine;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.lang.reflect.Method;
+
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper
+public class NetworkTypeControllerTest extends TelephonyTest {
+    // private constants copied over from NetworkTypeController
+    private static final int EVENT_DATA_RAT_CHANGED = 2;
+    private static final int EVENT_NR_STATE_CHANGED = 3;
+    private static final int EVENT_NR_FREQUENCY_CHANGED = 4;
+    private static final int EVENT_PHYSICAL_LINK_STATE_CHANGED = 5;
+    private static final int EVENT_PHYSICAL_CHANNEL_CONFIG_NOTIF_CHANGED = 6;
+    private static final int EVENT_CARRIER_CONFIG_CHANGED = 7;
+    private static final int EVENT_PRIMARY_TIMER_EXPIRED = 8;
+    private static final int EVENT_SECONDARY_TIMER_EXPIRED = 9;
+    private static final int EVENT_RADIO_OFF_OR_UNAVAILABLE = 10;
+    private static final int EVENT_PREFERRED_NETWORK_MODE_CHANGED = 11;
+    private static final int EVENT_INITIALIZE = 12;
+
+    private NetworkTypeController mNetworkTypeController;
+    private PersistableBundle mBundle;
+
+    private IState getCurrentState() throws Exception {
+        Method method = StateMachine.class.getDeclaredMethod("getCurrentState");
+        method.setAccessible(true);
+        return (IState) method.invoke(mNetworkTypeController);
+    }
+
+    private void updateOverrideNetworkType() throws Exception {
+        Method method = NetworkTypeController.class.getDeclaredMethod("updateOverrideNetworkType");
+        method.setAccessible(true);
+        method.invoke(mNetworkTypeController);
+    }
+
+    private void broadcastCarrierConfigs() {
+        Intent intent = new Intent(CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED);
+        intent.putExtra(CarrierConfigManager.EXTRA_SUBSCRIPTION_INDEX, mPhone.getSubId());
+        intent.putExtra(CarrierConfigManager.EXTRA_SLOT_INDEX, mPhone.getPhoneId());
+        mContext.sendBroadcast(intent);
+        processAllMessages();
+    }
+
+    @Before
+    public void setUp() throws Exception {
+        super.setUp(getClass().getSimpleName());
+        mBundle = mContextFixture.getCarrierConfigBundle();
+        mBundle.putString(CarrierConfigManager.KEY_5G_ICON_CONFIGURATION_STRING,
+                "connected_mmwave:5G_Plus,connected:5G,not_restricted_rrc_idle:5G,"
+                        + "not_restricted_rrc_con:5G");
+        broadcastCarrierConfigs();
+
+        replaceInstance(Handler.class, "mLooper", mDisplayInfoController, Looper.myLooper());
+        doReturn(TelephonyManager.NETWORK_MODE_NR_LTE_CDMA_EVDO_GSM_WCDMA).when(mPhone)
+                .getCachedPreferredNetworkType();
+        mNetworkTypeController = new NetworkTypeController(mPhone, mDisplayInfoController);
+        processAllMessages();
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        mNetworkTypeController.getHandler().removeCallbacksAndMessages(null);
+        mNetworkTypeController = null;
+        super.tearDown();
+    }
+
+    @Test
+    public void testUpdateOverrideNetworkTypeNrNsa() throws Exception {
+        doReturn(TelephonyManager.NETWORK_TYPE_LTE).when(mServiceState).getDataNetworkType();
+
+        // not NR
+        doReturn(NetworkRegistrationInfo.NR_STATE_NONE).when(mServiceState).getNrState();
+        updateOverrideNetworkType();
+        assertEquals(TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NONE,
+                mNetworkTypeController.getOverrideNetworkType());
+
+        // NR NSA, restricted
+        doReturn(NetworkRegistrationInfo.NR_STATE_RESTRICTED).when(mServiceState).getNrState();
+        updateOverrideNetworkType();
+        assertEquals(TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NONE,
+                mNetworkTypeController.getOverrideNetworkType());
+
+        // NR NSA, not restricted
+        doReturn(NetworkRegistrationInfo.NR_STATE_NOT_RESTRICTED).when(mServiceState).getNrState();
+        updateOverrideNetworkType();
+        assertEquals(TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_NSA,
+                mNetworkTypeController.getOverrideNetworkType());
+
+        // NR NSA, sub 6 frequency
+        doReturn(NetworkRegistrationInfo.NR_STATE_CONNECTED).when(mServiceState).getNrState();
+        doReturn(ServiceState.FREQUENCY_RANGE_LOW).when(mServiceState).getNrFrequencyRange();
+        updateOverrideNetworkType();
+        assertEquals(TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_NSA,
+                mNetworkTypeController.getOverrideNetworkType());
+
+        // NR NSA, millimeter wave frequency
+        doReturn(ServiceState.FREQUENCY_RANGE_MMWAVE).when(mServiceState).getNrFrequencyRange();
+        updateOverrideNetworkType();
+        assertEquals(TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_NSA_MMWAVE,
+                mNetworkTypeController.getOverrideNetworkType());
+    }
+
+    @Test
+    public void testUpdateOverrideNetworkTypeLte() throws Exception {
+        doReturn(TelephonyManager.NETWORK_TYPE_LTE).when(mServiceState).getDataNetworkType();
+
+        // normal LTE
+        updateOverrideNetworkType();
+        assertEquals(TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NONE,
+                mNetworkTypeController.getOverrideNetworkType());
+
+        // LTE CA
+        doReturn(true).when(mServiceState).isUsingCarrierAggregation();
+        updateOverrideNetworkType();
+        assertEquals(TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_LTE_CA,
+                mNetworkTypeController.getOverrideNetworkType());
+
+        // LTE ADVANCED PRO
+        doReturn("test_patternShowAdvanced").when(mServiceState).getOperatorAlphaLongRaw();
+        mBundle.putString(CarrierConfigManager.KEY_SHOW_CARRIER_DATA_ICON_PATTERN_STRING,
+                ".*_patternShowAdvanced");
+        broadcastCarrierConfigs();
+        updateOverrideNetworkType();
+        assertEquals(TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_LTE_ADVANCED_PRO,
+                mNetworkTypeController.getOverrideNetworkType());
+    }
+
+    @Test
+    public void testTransitionToCurrentStateLegacy() throws Exception {
+        assertEquals("DefaultState", getCurrentState().getName());
+        doReturn(TelephonyManager.NETWORK_TYPE_HSPAP).when(mServiceState).getDataNetworkType();
+
+        mNetworkTypeController.sendMessage(NetworkTypeController.EVENT_UPDATE);
+        processAllMessages();
+        assertEquals("legacy", getCurrentState().getName());
+    }
+
+    @Test
+    public void testTransitionToCurrentStateRestricted() throws Exception {
+        assertEquals("DefaultState", getCurrentState().getName());
+        doReturn(TelephonyManager.NETWORK_TYPE_LTE).when(mServiceState).getDataNetworkType();
+        doReturn(NetworkRegistrationInfo.NR_STATE_RESTRICTED).when(mServiceState).getNrState();
+
+        mNetworkTypeController.sendMessage(NetworkTypeController.EVENT_UPDATE);
+        processAllMessages();
+        assertEquals("restricted", getCurrentState().getName());
+    }
+
+    @Test
+    public void testTransitionToCurrentStateIdle() throws Exception {
+        assertEquals("DefaultState", getCurrentState().getName());
+        doReturn(TelephonyManager.NETWORK_TYPE_LTE).when(mServiceState).getDataNetworkType();
+        doReturn(NetworkRegistrationInfo.NR_STATE_NOT_RESTRICTED).when(mServiceState).getNrState();
+        mNetworkTypeController.sendMessage(EVENT_PHYSICAL_LINK_STATE_CHANGED,
+                new AsyncResult(null, DcController.PHYSICAL_LINK_NOT_ACTIVE, null));
+        mNetworkTypeController.sendMessage(NetworkTypeController.EVENT_UPDATE);
+        processAllMessages();
+        assertEquals("not_restricted_rrc_idle", getCurrentState().getName());
+    }
+
+    @Test
+    public void testTransitionToCurrentStateLteConnected() throws Exception {
+        assertEquals("DefaultState", getCurrentState().getName());
+        doReturn(TelephonyManager.NETWORK_TYPE_LTE).when(mServiceState).getDataNetworkType();
+        doReturn(NetworkRegistrationInfo.NR_STATE_NOT_RESTRICTED).when(mServiceState).getNrState();
+        mNetworkTypeController.sendMessage(EVENT_PHYSICAL_LINK_STATE_CHANGED,
+                new AsyncResult(null, DcController.PHYSICAL_LINK_ACTIVE, null));
+        mNetworkTypeController.sendMessage(NetworkTypeController.EVENT_UPDATE);
+        processAllMessages();
+        assertEquals("not_restricted_rrc_con", getCurrentState().getName());
+    }
+
+    @Test
+    public void testTransitionToCurrentStateNrConnected() throws Exception {
+        assertEquals("DefaultState", getCurrentState().getName());
+        doReturn(TelephonyManager.NETWORK_TYPE_LTE).when(mServiceState).getDataNetworkType();
+        doReturn(NetworkRegistrationInfo.NR_STATE_CONNECTED).when(mServiceState).getNrState();
+
+        mNetworkTypeController.sendMessage(NetworkTypeController.EVENT_UPDATE);
+        processAllMessages();
+        assertEquals("connected", getCurrentState().getName());
+    }
+
+    @Test
+    public void testTransitionToCurrentStateNrConnectedMmwave() throws Exception {
+        assertEquals("DefaultState", getCurrentState().getName());
+        doReturn(TelephonyManager.NETWORK_TYPE_LTE).when(mServiceState).getDataNetworkType();
+        doReturn(NetworkRegistrationInfo.NR_STATE_CONNECTED).when(mServiceState).getNrState();
+        doReturn(ServiceState.FREQUENCY_RANGE_MMWAVE).when(mServiceState).getNrFrequencyRange();
+
+        mNetworkTypeController.sendMessage(NetworkTypeController.EVENT_UPDATE);
+        processAllMessages();
+        assertEquals("connected_mmwave", getCurrentState().getName());
+    }
+
+    @Test
+    public void testEventDataRatChanged() throws Exception {
+        testTransitionToCurrentStateLegacy();
+        doReturn(TelephonyManager.NETWORK_TYPE_LTE).when(mServiceState).getDataNetworkType();
+        doReturn(NetworkRegistrationInfo.NR_STATE_CONNECTED).when(mServiceState).getNrState();
+
+        mNetworkTypeController.sendMessage(EVENT_DATA_RAT_CHANGED);
+        processAllMessages();
+        assertEquals("connected", getCurrentState().getName());
+    }
+
+    @Test
+    public void testEventNrStateChanged() throws Exception {
+        testTransitionToCurrentStateNrConnected();
+        doReturn(NetworkRegistrationInfo.NR_STATE_RESTRICTED).when(mServiceState).getNrState();
+
+        mNetworkTypeController.sendMessage(EVENT_NR_STATE_CHANGED);
+        processAllMessages();
+        assertEquals("restricted", getCurrentState().getName());
+    }
+
+    @Test
+    public void testEventNrFrequencyRangeChangedFromNrConnectedMmwaveToNrConnected()
+            throws Exception {
+        testTransitionToCurrentStateNrConnectedMmwave();
+        doReturn(ServiceState.FREQUENCY_RANGE_LOW).when(mServiceState).getNrFrequencyRange();
+
+        mNetworkTypeController.sendMessage(EVENT_NR_FREQUENCY_CHANGED);
+        processAllMessages();
+
+        assertEquals("connected", getCurrentState().getName());
+    }
+
+    @Test
+    public void testEventNrFrequencyRangeChangedFromNrConnectedToNrConnectedMmwave()
+            throws Exception {
+        testTransitionToCurrentStateNrConnected();
+        doReturn(ServiceState.FREQUENCY_RANGE_MMWAVE).when(mServiceState).getNrFrequencyRange();
+
+        mNetworkTypeController.sendMessage(EVENT_NR_FREQUENCY_CHANGED);
+        processAllMessages();
+
+        assertEquals("connected_mmwave", getCurrentState().getName());
+    }
+
+    @Test
+    public void testNrPhysicalChannelChangeFromNrConnectedMmwaveToLteConnected() throws Exception {
+        testTransitionToCurrentStateNrConnectedMmwave();
+        doReturn(TelephonyManager.NETWORK_TYPE_LTE).when(mServiceState).getDataNetworkType();
+        doReturn(NetworkRegistrationInfo.NR_STATE_NOT_RESTRICTED).when(mServiceState).getNrState();
+        mNetworkTypeController.sendMessage(EVENT_PHYSICAL_LINK_STATE_CHANGED,
+                new AsyncResult(null, DcController.PHYSICAL_LINK_ACTIVE, null));
+        mNetworkTypeController.sendMessage(EVENT_NR_FREQUENCY_CHANGED);
+        mNetworkTypeController.sendMessage(EVENT_NR_STATE_CHANGED);
+        processAllMessages();
+
+        assertEquals("not_restricted_rrc_con", getCurrentState().getName());
+    }
+
+    @Test
+    public void testEventPhysicalLinkStateChanged() throws Exception {
+        testTransitionToCurrentStateLteConnected();
+        doReturn(ServiceState.FREQUENCY_RANGE_MMWAVE).when(mServiceState).getNrFrequencyRange();
+        mNetworkTypeController.sendMessage(EVENT_PHYSICAL_LINK_STATE_CHANGED,
+                new AsyncResult(null, DcController.PHYSICAL_LINK_NOT_ACTIVE, null));
+        processAllMessages();
+        assertEquals("not_restricted_rrc_idle", getCurrentState().getName());
+    }
+
+    @Test
+    public void testEventPhysicalChannelConfigNotifChanged() throws Exception {
+        testTransitionToCurrentStateNrConnected();
+        assertEquals(TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_NSA,
+                mNetworkTypeController.getOverrideNetworkType());
+
+        mNetworkTypeController.sendMessage(EVENT_PHYSICAL_CHANNEL_CONFIG_NOTIF_CHANGED,
+                new AsyncResult(null, false, null));
+        processAllMessages();
+        assertEquals(TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NONE,
+                mNetworkTypeController.getOverrideNetworkType());
+    }
+
+    @Test
+    public void testEventRadioOffOrUnavailable() throws Exception {
+        testTransitionToCurrentStateNrConnected();
+        assertEquals(TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_NSA,
+                mNetworkTypeController.getOverrideNetworkType());
+
+        doReturn(NetworkRegistrationInfo.NR_STATE_NONE).when(mServiceState).getNrState();
+        doReturn(TelephonyManager.NETWORK_TYPE_UNKNOWN).when(mServiceState).getDataNetworkType();
+
+        mNetworkTypeController.sendMessage(EVENT_RADIO_OFF_OR_UNAVAILABLE);
+        processAllMessages();
+        assertEquals(TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NONE,
+                mNetworkTypeController.getOverrideNetworkType());
+    }
+
+    @Test
+    public void testEventPreferredNetworkModeChanged() throws Exception {
+        testTransitionToCurrentStateNrConnected();
+        assertEquals(TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_NSA,
+                mNetworkTypeController.getOverrideNetworkType());
+
+        // remove NR from preferred network types
+        doReturn(TelephonyManager.NETWORK_MODE_LTE_CDMA_EVDO_GSM_WCDMA).when(mPhone)
+                .getCachedPreferredNetworkType();
+
+        mNetworkTypeController.sendMessage(EVENT_PREFERRED_NETWORK_MODE_CHANGED);
+        processAllMessages();
+        assertEquals(TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NONE,
+                mNetworkTypeController.getOverrideNetworkType());
+    }
+
+    @Test
+    public void testPrimaryTimerExpire() throws Exception {
+        doReturn(NetworkRegistrationInfo.NR_STATE_CONNECTED).when(mServiceState).getNrState();
+        mBundle.putString(CarrierConfigManager.KEY_5G_ICON_DISPLAY_GRACE_PERIOD_STRING,
+                "connected_mmwave,any,10;connected,any,10;not_restricted_rrc_con,any,10");
+        broadcastCarrierConfigs();
+
+        assertEquals("connected", getCurrentState().getName());
+        assertEquals(TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_NSA,
+                mNetworkTypeController.getOverrideNetworkType());
+
+        // should trigger 10 second timer
+        doReturn(NetworkRegistrationInfo.NR_STATE_NONE).when(mServiceState).getNrState();
+        mNetworkTypeController.sendMessage(EVENT_NR_STATE_CHANGED);
+        processAllMessages();
+
+        assertEquals("legacy", getCurrentState().getName());
+        assertEquals(TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_NSA,
+                mNetworkTypeController.getOverrideNetworkType());
+
+        // timer expires
+        moveTimeForward(10 * 1000);
+        processAllMessages();
+
+        assertEquals("legacy", getCurrentState().getName());
+        assertEquals(TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NONE,
+                mNetworkTypeController.getOverrideNetworkType());
+    }
+
+    @Test
+    public void testPrimaryTimerReset() throws Exception {
+        doReturn(NetworkRegistrationInfo.NR_STATE_CONNECTED).when(mServiceState).getNrState();
+        mBundle.putString(CarrierConfigManager.KEY_5G_ICON_DISPLAY_GRACE_PERIOD_STRING,
+                "connected_mmwave,any,10;connected,any,10;not_restricted_rrc_con,any,10");
+        broadcastCarrierConfigs();
+
+        assertEquals("connected", getCurrentState().getName());
+        assertEquals(TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_NSA,
+                mNetworkTypeController.getOverrideNetworkType());
+
+        // trigger 10 second timer after disconnecting from NR
+        doReturn(NetworkRegistrationInfo.NR_STATE_NONE).when(mServiceState).getNrState();
+        mNetworkTypeController.sendMessage(EVENT_NR_STATE_CHANGED);
+        processAllMessages();
+
+        assertEquals("legacy", getCurrentState().getName());
+        assertEquals(TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_NSA,
+                mNetworkTypeController.getOverrideNetworkType());
+
+        // reconnect to NR in the middle of the timer
+        doReturn(NetworkRegistrationInfo.NR_STATE_CONNECTED).when(mServiceState).getNrState();
+        mNetworkTypeController.sendMessage(EVENT_NR_STATE_CHANGED);
+
+        // timer expires
+        moveTimeForward(10 * 1000);
+        processAllMessages();
+
+        // timer should not have gone off
+        assertEquals("connected", getCurrentState().getName());
+        assertEquals(TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_NSA,
+                mNetworkTypeController.getOverrideNetworkType());
+    }
+
+    @Test
+    public void testPrimaryTimerExpireMmwave() throws Exception {
+        doReturn(NetworkRegistrationInfo.NR_STATE_CONNECTED).when(mServiceState).getNrState();
+        doReturn(ServiceState.FREQUENCY_RANGE_MMWAVE).when(mServiceState).getNrFrequencyRange();
+        mBundle.putString(CarrierConfigManager.KEY_5G_ICON_DISPLAY_GRACE_PERIOD_STRING,
+                "connected_mmwave,any,10;connected,any,10;not_restricted_rrc_con,any,10");
+        broadcastCarrierConfigs();
+
+        assertEquals("connected_mmwave", getCurrentState().getName());
+        assertEquals(TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_NSA_MMWAVE,
+                mNetworkTypeController.getOverrideNetworkType());
+
+        // should trigger 10 second timer
+        doReturn(ServiceState.FREQUENCY_RANGE_LOW).when(mServiceState).getNrFrequencyRange();
+        mNetworkTypeController.sendMessage(EVENT_NR_FREQUENCY_CHANGED);
+        processAllMessages();
+
+
+        assertEquals("connected", getCurrentState().getName());
+        assertEquals(TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_NSA_MMWAVE,
+                mNetworkTypeController.getOverrideNetworkType());
+
+        // timer expires
+        moveTimeForward(10 * 1000);
+        processAllMessages();
+
+        assertEquals("connected", getCurrentState().getName());
+        assertEquals(TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_NSA,
+                mNetworkTypeController.getOverrideNetworkType());
+    }
+
+    @Test
+    public void testPrimaryTimerResetMmwave() throws Exception {
+        doReturn(NetworkRegistrationInfo.NR_STATE_CONNECTED).when(mServiceState).getNrState();
+        doReturn(ServiceState.FREQUENCY_RANGE_MMWAVE).when(mServiceState).getNrFrequencyRange();
+        mBundle.putString(CarrierConfigManager.KEY_5G_ICON_DISPLAY_GRACE_PERIOD_STRING,
+                "connected_mmwave,any,10;connected,any,10;not_restricted_rrc_con,any,10");
+        broadcastCarrierConfigs();
+
+        assertEquals("connected_mmwave", getCurrentState().getName());
+        assertEquals(TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_NSA_MMWAVE,
+                mNetworkTypeController.getOverrideNetworkType());
+
+        // trigger 10 second timer after disconnecting from NR
+        doReturn(ServiceState.FREQUENCY_RANGE_LOW).when(mServiceState).getNrFrequencyRange();
+        mNetworkTypeController.sendMessage(EVENT_NR_FREQUENCY_CHANGED);
+        processAllMessages();
+
+        assertEquals("connected", getCurrentState().getName());
+        assertEquals(TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_NSA_MMWAVE,
+                mNetworkTypeController.getOverrideNetworkType());
+
+        // reconnect to NR in the middle of the timer
+        doReturn(ServiceState.FREQUENCY_RANGE_MMWAVE).when(mServiceState).getNrFrequencyRange();
+        mNetworkTypeController.sendMessage(EVENT_NR_FREQUENCY_CHANGED);
+
+        // timer expires
+        moveTimeForward(10 * 1000);
+        processAllMessages();
+
+        // timer should not have gone off
+        assertEquals("connected_mmwave", getCurrentState().getName());
+        assertEquals(TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_NSA_MMWAVE,
+                mNetworkTypeController.getOverrideNetworkType());
+    }
+
+    @Test
+    public void testSecondaryTimerExpire() throws Exception {
+        doReturn(NetworkRegistrationInfo.NR_STATE_CONNECTED).when(mServiceState).getNrState();
+        mBundle.putString(CarrierConfigManager.KEY_5G_ICON_DISPLAY_GRACE_PERIOD_STRING,
+                "connected_mmwave,any,10;connected,any,10;not_restricted_rrc_con,any,10");
+        mBundle.putString(CarrierConfigManager.KEY_5G_ICON_DISPLAY_SECONDARY_GRACE_PERIOD_STRING,
+                "connected,any,30");
+        broadcastCarrierConfigs();
+
+        assertEquals("connected", getCurrentState().getName());
+        assertEquals(TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_NSA,
+                mNetworkTypeController.getOverrideNetworkType());
+
+        // should trigger 10 second primary timer
+        doReturn(NetworkRegistrationInfo.NR_STATE_NONE).when(mServiceState).getNrState();
+        mNetworkTypeController.sendMessage(EVENT_NR_STATE_CHANGED);
+        processAllMessages();
+
+        assertEquals("legacy", getCurrentState().getName());
+        assertEquals(TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_NSA,
+                mNetworkTypeController.getOverrideNetworkType());
+
+        // primary timer expires
+        moveTimeForward(10 * 1000);
+        processAllMessages();
+
+        // should trigger 30 second secondary timer
+        assertEquals("legacy", getCurrentState().getName());
+        assertEquals(TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_NSA,
+                mNetworkTypeController.getOverrideNetworkType());
+
+        // secondary timer expires
+        moveTimeForward(30 * 1000);
+        processAllMessages();
+
+        assertEquals("legacy", getCurrentState().getName());
+        assertEquals(TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NONE,
+                mNetworkTypeController.getOverrideNetworkType());
+    }
+
+    @Test
+    public void testSecondaryTimerReset() throws Exception {
+        doReturn(NetworkRegistrationInfo.NR_STATE_CONNECTED).when(mServiceState).getNrState();
+        mBundle.putString(CarrierConfigManager.KEY_5G_ICON_DISPLAY_GRACE_PERIOD_STRING,
+                "connected_mmwave,any,10;connected,any,10;not_restricted_rrc_con,any,10");
+        mBundle.putString(CarrierConfigManager.KEY_5G_ICON_DISPLAY_SECONDARY_GRACE_PERIOD_STRING,
+                "connected,any,30");
+        broadcastCarrierConfigs();
+
+        assertEquals("connected", getCurrentState().getName());
+        assertEquals(TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_NSA,
+                mNetworkTypeController.getOverrideNetworkType());
+
+        // should trigger 10 second primary timer
+        doReturn(NetworkRegistrationInfo.NR_STATE_NONE).when(mServiceState).getNrState();
+        mNetworkTypeController.sendMessage(EVENT_NR_STATE_CHANGED);
+        processAllMessages();
+
+        assertEquals("legacy", getCurrentState().getName());
+        assertEquals(TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_NSA,
+                mNetworkTypeController.getOverrideNetworkType());
+
+        // primary timer expires
+        moveTimeForward(10 * 1000);
+        processAllMessages();
+
+        // should trigger 30 second secondary timer
+        assertEquals("legacy", getCurrentState().getName());
+        assertEquals(TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_NSA,
+                mNetworkTypeController.getOverrideNetworkType());
+
+        // reconnect to NR in the middle of the timer
+        doReturn(NetworkRegistrationInfo.NR_STATE_CONNECTED).when(mServiceState).getNrState();
+        mNetworkTypeController.sendMessage(EVENT_NR_STATE_CHANGED);
+
+        // secondary timer expires
+        moveTimeForward(30 * 1000);
+        processAllMessages();
+
+        // timer should not have gone off
+        assertEquals("connected", getCurrentState().getName());
+        assertEquals(TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_NSA,
+                mNetworkTypeController.getOverrideNetworkType());
+    }
+
+    @Test
+    public void testSecondaryTimerExpireMmwave() throws Exception {
+        doReturn(NetworkRegistrationInfo.NR_STATE_CONNECTED).when(mServiceState).getNrState();
+        doReturn(ServiceState.FREQUENCY_RANGE_MMWAVE).when(mServiceState).getNrFrequencyRange();
+        mBundle.putString(CarrierConfigManager.KEY_5G_ICON_DISPLAY_GRACE_PERIOD_STRING,
+                "connected_mmwave,any,10;connected,any,10;not_restricted_rrc_con,any,10");
+        mBundle.putString(CarrierConfigManager.KEY_5G_ICON_DISPLAY_SECONDARY_GRACE_PERIOD_STRING,
+                "connected_mmwave,any,30");
+        broadcastCarrierConfigs();
+
+        assertEquals("connected_mmwave", getCurrentState().getName());
+        assertEquals(TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_NSA_MMWAVE,
+                mNetworkTypeController.getOverrideNetworkType());
+
+        // should trigger 10 second primary timer
+        doReturn(ServiceState.FREQUENCY_RANGE_LOW).when(mServiceState).getNrFrequencyRange();
+        mNetworkTypeController.sendMessage(EVENT_NR_FREQUENCY_CHANGED);
+        processAllMessages();
+
+        assertEquals("connected", getCurrentState().getName());
+        assertEquals(TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_NSA_MMWAVE,
+                mNetworkTypeController.getOverrideNetworkType());
+
+        // primary timer expires
+        moveTimeForward(10 * 1000);
+        processAllMessages();
+
+        // should trigger 30 second secondary timer
+        assertEquals("connected", getCurrentState().getName());
+        assertEquals(TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_NSA_MMWAVE,
+                mNetworkTypeController.getOverrideNetworkType());
+
+        // secondary timer expires
+        moveTimeForward(30 * 1000);
+        processAllMessages();
+
+        assertEquals("connected", getCurrentState().getName());
+        assertEquals(TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_NSA,
+                mNetworkTypeController.getOverrideNetworkType());
+    }
+
+    @Test
+    public void testSecondaryTimerResetMmwave() throws Exception {
+        doReturn(NetworkRegistrationInfo.NR_STATE_CONNECTED).when(mServiceState).getNrState();
+        doReturn(ServiceState.FREQUENCY_RANGE_MMWAVE).when(mServiceState).getNrFrequencyRange();
+        mBundle.putString(CarrierConfigManager.KEY_5G_ICON_DISPLAY_GRACE_PERIOD_STRING,
+                "connected_mmwave,any,10;connected,any,10;not_restricted_rrc_con,any,10");
+        mBundle.putString(CarrierConfigManager.KEY_5G_ICON_DISPLAY_SECONDARY_GRACE_PERIOD_STRING,
+                "connected_mmwave,any,30");
+        broadcastCarrierConfigs();
+
+        assertEquals("connected_mmwave", getCurrentState().getName());
+        assertEquals(TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_NSA_MMWAVE,
+                mNetworkTypeController.getOverrideNetworkType());
+
+        // should trigger 10 second primary timer
+        doReturn(ServiceState.FREQUENCY_RANGE_LOW).when(mServiceState).getNrFrequencyRange();
+        mNetworkTypeController.sendMessage(EVENT_NR_FREQUENCY_CHANGED);
+        processAllMessages();
+
+        assertEquals("connected", getCurrentState().getName());
+        assertEquals(TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_NSA_MMWAVE,
+                mNetworkTypeController.getOverrideNetworkType());
+
+        // primary timer expires
+        moveTimeForward(10 * 1000);
+        processAllMessages();
+
+        // should trigger 30 second secondary timer
+        assertEquals("connected", getCurrentState().getName());
+        assertEquals(TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_NSA_MMWAVE,
+                mNetworkTypeController.getOverrideNetworkType());
+
+        // reconnect to NR in the middle of the timer
+        doReturn(ServiceState.FREQUENCY_RANGE_MMWAVE).when(mServiceState).getNrFrequencyRange();
+        mNetworkTypeController.sendMessage(EVENT_NR_FREQUENCY_CHANGED);
+
+        // secondary timer expires
+        moveTimeForward(30 * 1000);
+        processAllMessages();
+
+        // timer should not have gone off
+        assertEquals("connected_mmwave", getCurrentState().getName());
+        assertEquals(TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_NSA_MMWAVE,
+                mNetworkTypeController.getOverrideNetworkType());
+    }
+}
diff --git a/tests/telephonytests/src/com/android/internal/telephony/NitzStateMachineImplTest.java b/tests/telephonytests/src/com/android/internal/telephony/NitzStateMachineImplTest.java
deleted file mode 100644
index 5678e3b..0000000
--- a/tests/telephonytests/src/com/android/internal/telephony/NitzStateMachineImplTest.java
+++ /dev/null
@@ -1,970 +0,0 @@
-/*
- * Copyright 2017 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;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertTrue;
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.atLeast;
-import static org.mockito.Mockito.clearInvocations;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.verifyNoMoreInteractions;
-import static org.mockito.Mockito.when;
-
-import android.icu.util.Calendar;
-import android.icu.util.GregorianCalendar;
-import android.icu.util.TimeZone;
-import android.util.TimestampedValue;
-
-import com.android.internal.telephony.TimeZoneLookupHelper.CountryResult;
-import com.android.internal.telephony.TimeZoneLookupHelper.OffsetResult;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-import org.mockito.Mock;
-
-public class NitzStateMachineImplTest extends TelephonyTest {
-
-    // A country with a single zone : the zone can be guessed from the country.
-    // The UK uses UTC for part of the year so it is not good for detecting bogus NITZ signals.
-    private static final Scenario UNITED_KINGDOM_SCENARIO = new Scenario.Builder()
-            .setInitialDeviceSystemClockUtc(1977, 1, 1, 12, 0, 0)
-            .setInitialDeviceRealtimeMillis(123456789L)
-            .setTimeZone("Europe/London")
-            .setActualTimeUtc(2018, 1, 1, 12, 0, 0)
-            .setCountryIso("gb")
-            .build();
-
-    // A country that has multiple zones, but there is only one matching time zone at the time :
-    // the zone cannot be guessed from the country alone, but can be guessed from the country +
-    // NITZ. The US never uses UTC so it can be used for testing bogus NITZ signal handling.
-    private static final Scenario UNIQUE_US_ZONE_SCENARIO = new Scenario.Builder()
-            .setInitialDeviceSystemClockUtc(1977, 1, 1, 12, 0, 0)
-            .setInitialDeviceRealtimeMillis(123456789L)
-            .setTimeZone("America/Los_Angeles")
-            .setActualTimeUtc(2018, 1, 1, 12, 0, 0)
-            .setCountryIso("us")
-            .build();
-
-    // A country with a single zone: the zone can be guessed from the country alone. CZ never uses
-    // UTC so it can be used for testing bogus NITZ signal handling.
-    private static final Scenario CZECHIA_SCENARIO = new Scenario.Builder()
-            .setInitialDeviceSystemClockUtc(1977, 1, 1, 12, 0, 0)
-            .setInitialDeviceRealtimeMillis(123456789L)
-            .setTimeZone("Europe/Prague")
-            .setActualTimeUtc(2018, 1, 1, 12, 0, 0)
-            .setCountryIso("cz")
-            .build();
-
-    @Mock
-    private NitzStateMachine.DeviceState mDeviceState;
-
-    @Mock
-    private TimeServiceHelper mTimeServiceHelper;
-
-    private TimeZoneLookupHelper mRealTimeZoneLookupHelper;
-
-    private NitzStateMachineImpl mNitzStateMachine;
-
-    @Before
-    public void setUp() throws Exception {
-        logd("NitzStateMachineTest +Setup!");
-        super.setUp("NitzStateMachineTest");
-
-        // In tests we use the real TimeZoneLookupHelper.
-        mRealTimeZoneLookupHelper = new TimeZoneLookupHelper();
-        mNitzStateMachine = new NitzStateMachineImpl(
-                mPhone, mTimeServiceHelper, mDeviceState, mRealTimeZoneLookupHelper);
-
-        logd("ServiceStateTrackerTest -Setup!");
-    }
-
-    @After
-    public void tearDown() throws Exception {
-        checkNoUnverifiedSetOperations(mTimeServiceHelper);
-
-        super.tearDown();
-    }
-
-    @Test
-    public void test_uniqueUsZone_Assumptions() {
-        // Check we'll get the expected behavior from TimeZoneLookupHelper.
-
-        // allZonesHaveSameOffset == false, so we shouldn't pick an arbitrary zone.
-        CountryResult expectedCountryLookupResult = new CountryResult(
-                "America/New_York", false /* allZonesHaveSameOffset */,
-                UNIQUE_US_ZONE_SCENARIO.getInitialSystemClockMillis());
-        CountryResult actualCountryLookupResult =
-                mRealTimeZoneLookupHelper.lookupByCountry(
-                        UNIQUE_US_ZONE_SCENARIO.getNetworkCountryIsoCode(),
-                        UNIQUE_US_ZONE_SCENARIO.getInitialSystemClockMillis());
-        assertEquals(expectedCountryLookupResult, actualCountryLookupResult);
-
-        // isOnlyMatch == true, so the combination of country + NITZ should be enough.
-        OffsetResult expectedLookupResult =
-                new OffsetResult("America/Los_Angeles", true /* isOnlyMatch */);
-        OffsetResult actualLookupResult = mRealTimeZoneLookupHelper.lookupByNitzCountry(
-                UNIQUE_US_ZONE_SCENARIO.getNitzSignal().getValue(),
-                UNIQUE_US_ZONE_SCENARIO.getNetworkCountryIsoCode());
-        assertEquals(expectedLookupResult, actualLookupResult);
-    }
-
-    @Test
-    public void test_unitedKingdom_Assumptions() {
-        // Check we'll get the expected behavior from TimeZoneLookupHelper.
-
-        // allZonesHaveSameOffset == true (not only that, there is only one zone), so we can pick
-        // the zone knowing only the country.
-        CountryResult expectedCountryLookupResult = new CountryResult(
-                "Europe/London", true /* allZonesHaveSameOffset */,
-                UNITED_KINGDOM_SCENARIO.getInitialSystemClockMillis());
-        CountryResult actualCountryLookupResult =
-                mRealTimeZoneLookupHelper.lookupByCountry(
-                        UNITED_KINGDOM_SCENARIO.getNetworkCountryIsoCode(),
-                        UNITED_KINGDOM_SCENARIO.getInitialSystemClockMillis());
-        assertEquals(expectedCountryLookupResult, actualCountryLookupResult);
-
-        OffsetResult expectedLookupResult =
-                new OffsetResult("Europe/London", true /* isOnlyMatch */);
-        OffsetResult actualLookupResult = mRealTimeZoneLookupHelper.lookupByNitzCountry(
-                UNITED_KINGDOM_SCENARIO.getNitzSignal().getValue(),
-                UNITED_KINGDOM_SCENARIO.getNetworkCountryIsoCode());
-        assertEquals(expectedLookupResult, actualLookupResult);
-    }
-
-    @Test
-    public void test_uniqueUsZone_timeZoneEnabled_countryThenNitz() throws Exception {
-        Scenario scenario = UNIQUE_US_ZONE_SCENARIO;
-        Device device = new DeviceBuilder()
-                .setClocksFromScenario(scenario)
-                .setTimeZoneDetectionEnabled(true)
-                .setTimeZoneSettingInitialized(false)
-                .initialize();
-        Script script = new Script(device);
-
-        script.countryReceived(scenario.getNetworkCountryIsoCode())
-                // Country won't be enough for time zone detection.
-                .verifyNothingWasSetAndReset()
-                .nitzReceived(scenario.getNitzSignal())
-                // Country + NITZ is enough for both time + time zone detection.
-                .verifyTimeSuggestedAndZoneSetAndReset(
-                        scenario.getNitzSignal(), scenario.getTimeZoneId());
-
-        // Check NitzStateMachine state.
-        assertTrue(mNitzStateMachine.getNitzTimeZoneDetectionSuccessful());
-        assertEquals(scenario.getNitzSignal().getValue(), mNitzStateMachine.getCachedNitzData());
-        assertEquals(scenario.getTimeZoneId(), mNitzStateMachine.getSavedTimeZoneId());
-    }
-
-    @Test
-    public void test_unitedKingdom_timeZoneEnabled_countryThenNitz() throws Exception {
-        Scenario scenario = UNITED_KINGDOM_SCENARIO;
-        Device device = new DeviceBuilder()
-                .setClocksFromScenario(scenario)
-                .setTimeZoneDetectionEnabled(true)
-                .setTimeZoneSettingInitialized(false)
-                .initialize();
-        Script script = new Script(device);
-
-        script.countryReceived(scenario.getNetworkCountryIsoCode())
-                // Country alone is enough to guess the time zone.
-                .verifyOnlyTimeZoneWasSetAndReset(scenario.getTimeZoneId())
-                .nitzReceived(scenario.getNitzSignal())
-                // Country + NITZ is enough for both time + time zone detection.
-                .verifyTimeSuggestedAndZoneSetAndReset(
-                        scenario.getNitzSignal(), scenario.getTimeZoneId());
-
-        // Check NitzStateMachine state.
-        assertTrue(mNitzStateMachine.getNitzTimeZoneDetectionSuccessful());
-        assertEquals(scenario.getNitzSignal().getValue(), mNitzStateMachine.getCachedNitzData());
-        assertEquals(scenario.getTimeZoneId(), mNitzStateMachine.getSavedTimeZoneId());
-    }
-
-    @Test
-    public void test_uniqueUsZone_timeZoneDisabled_countryThenNitz() throws Exception {
-        Scenario scenario = UNIQUE_US_ZONE_SCENARIO;
-        Device device = new DeviceBuilder()
-                .setClocksFromScenario(scenario)
-                .setTimeZoneDetectionEnabled(false)
-                .setTimeZoneSettingInitialized(false)
-                .initialize();
-        Script script = new Script(device);
-
-        script.countryReceived(scenario.getNetworkCountryIsoCode())
-                // Country is not enough to guess the time zone and time zone detection is disabled.
-                .verifyNothingWasSetAndReset()
-                .nitzReceived(scenario.getNitzSignal())
-                // Time zone detection is disabled, but time should be suggested from NITZ.
-                .verifyOnlyTimeWasSuggestedAndReset(scenario.getNitzSignal());
-
-        // Check NitzStateMachine state.
-        assertTrue(mNitzStateMachine.getNitzTimeZoneDetectionSuccessful());
-        assertEquals(scenario.getNitzSignal().getValue(), mNitzStateMachine.getCachedNitzData());
-        assertEquals(scenario.getTimeZoneId(), mNitzStateMachine.getSavedTimeZoneId());
-    }
-
-    @Test
-    public void test_unitedKingdom_timeZoneDisabled_countryThenNitz() throws Exception {
-        Scenario scenario = UNITED_KINGDOM_SCENARIO;
-        Device device = new DeviceBuilder()
-                .setClocksFromScenario(scenario)
-                .setTimeZoneDetectionEnabled(false)
-                .setTimeZoneSettingInitialized(false)
-                .initialize();
-        Script script = new Script(device);
-
-        script.countryReceived(scenario.getNetworkCountryIsoCode())
-                // Country alone would be enough for time zone detection, but it's disabled.
-                .verifyNothingWasSetAndReset()
-                .nitzReceived(scenario.getNitzSignal())
-                // Time zone detection is disabled, but time should be suggested from NITZ.
-                .verifyOnlyTimeWasSuggestedAndReset(scenario.getNitzSignal());
-
-        // Check NitzStateMachine state.
-        assertTrue(mNitzStateMachine.getNitzTimeZoneDetectionSuccessful());
-        assertEquals(scenario.getNitzSignal().getValue(), mNitzStateMachine.getCachedNitzData());
-        assertEquals(scenario.getTimeZoneId(), mNitzStateMachine.getSavedTimeZoneId());
-    }
-
-    @Test
-    public void test_uniqueUsZone_timeZoneEnabled_nitzThenCountry() throws Exception {
-        Scenario scenario = UNIQUE_US_ZONE_SCENARIO;
-        Device device = new DeviceBuilder()
-                .setClocksFromScenario(scenario)
-                .setTimeZoneDetectionEnabled(true)
-                .setTimeZoneSettingInitialized(false)
-                .initialize();
-        Script script = new Script(device);
-
-        // Simulate receiving an NITZ signal.
-        script.nitzReceived(scenario.getNitzSignal())
-                // The NITZ alone isn't enough to detect a time zone.
-                .verifyOnlyTimeWasSuggestedAndReset(scenario.getNitzSignal());
-
-        // Check NitzStateMachine state.
-        assertFalse(mNitzStateMachine.getNitzTimeZoneDetectionSuccessful());
-        assertEquals(scenario.getNitzSignal().getValue(), mNitzStateMachine.getCachedNitzData());
-        assertNull(mNitzStateMachine.getSavedTimeZoneId());
-
-        // Simulate the country code becoming known.
-        script.countryReceived(scenario.getNetworkCountryIsoCode())
-                // The NITZ + country is enough to detect the time zone.
-                .verifyOnlyTimeZoneWasSetAndReset(scenario.getTimeZoneId());
-
-        // Check NitzStateMachine state.
-        assertTrue(mNitzStateMachine.getNitzTimeZoneDetectionSuccessful());
-        assertEquals(scenario.getNitzSignal().getValue(), mNitzStateMachine.getCachedNitzData());
-        assertEquals(scenario.getTimeZoneId(), mNitzStateMachine.getSavedTimeZoneId());
-    }
-
-    @Test
-    public void test_unitedKingdom_timeZoneEnabled_nitzThenCountry() throws Exception {
-        Scenario scenario = UNITED_KINGDOM_SCENARIO;
-        Device device = new DeviceBuilder()
-                .setClocksFromScenario(scenario)
-                .setTimeZoneDetectionEnabled(true)
-                .setTimeZoneSettingInitialized(false)
-                .initialize();
-        Script script = new Script(device);
-
-        // Simulate receiving an NITZ signal.
-        script.nitzReceived(scenario.getNitzSignal())
-                // The NITZ alone isn't enough to detect a time zone.
-                .verifyOnlyTimeWasSuggestedAndReset(scenario.getNitzSignal());
-
-        // Check NitzStateMachine state.
-        assertFalse(mNitzStateMachine.getNitzTimeZoneDetectionSuccessful());
-        assertEquals(scenario.getNitzSignal().getValue(), mNitzStateMachine.getCachedNitzData());
-        assertNull(mNitzStateMachine.getSavedTimeZoneId());
-
-        // Simulate the country code becoming known.
-        script.countryReceived(scenario.getNetworkCountryIsoCode());
-
-        // The NITZ + country is enough to detect the time zone.
-        // NOTE: setting the time zone happens twice because of a quirk in NitzStateMachine: it
-        // handles the country lookup / set, then combines the country with the NITZ state and does
-        // another lookup / set. We shouldn't require it is set twice but we do for simplicity.
-        script.verifyOnlyTimeZoneWasSetAndReset(scenario.getTimeZoneId(), 2 /* times */);
-
-        // Check NitzStateMachine state.
-        assertTrue(mNitzStateMachine.getNitzTimeZoneDetectionSuccessful());
-        assertEquals(scenario.getNitzSignal().getValue(), mNitzStateMachine.getCachedNitzData());
-        assertEquals(scenario.getTimeZoneId(), mNitzStateMachine.getSavedTimeZoneId());
-    }
-
-    @Test
-    public void test_validCzNitzSignal_nitzReceivedFirst() throws Exception {
-        Scenario scenario = CZECHIA_SCENARIO;
-        Device device = new DeviceBuilder()
-                .setClocksFromScenario(scenario)
-                .setTimeZoneDetectionEnabled(true)
-                .setTimeZoneSettingInitialized(true)
-                .initialize();
-        Script script = new Script(device);
-
-        TimestampedValue<NitzData> goodNitzSignal = scenario.getNitzSignal();
-
-        // Simulate receiving an NITZ signal.
-        script.nitzReceived(goodNitzSignal)
-                // The NITZ alone isn't enough to detect a time zone.
-                .verifyOnlyTimeWasSuggestedAndReset(scenario.getNitzSignal());
-
-        // Check NitzStateMachine state.
-        assertFalse(mNitzStateMachine.getNitzTimeZoneDetectionSuccessful());
-        assertEquals(goodNitzSignal.getValue(), mNitzStateMachine.getCachedNitzData());
-        assertNull(mNitzStateMachine.getSavedTimeZoneId());
-
-        // Simulate the country code becoming known.
-        script.countryReceived(scenario.getNetworkCountryIsoCode())
-                // The NITZ country is enough to detect the time zone, but the NITZ + country is
-                // also sufficient so we expect the time zone to be set twice.
-                .verifyOnlyTimeZoneWasSetAndReset(scenario.getTimeZoneId(), 2);
-
-        // Check NitzStateMachine state.
-        assertTrue(mNitzStateMachine.getNitzTimeZoneDetectionSuccessful());
-        assertEquals(goodNitzSignal.getValue(), mNitzStateMachine.getCachedNitzData());
-        assertEquals(scenario.getTimeZoneId(), mNitzStateMachine.getSavedTimeZoneId());
-    }
-
-    @Test
-    public void test_validCzNitzSignal_countryReceivedFirst() throws Exception {
-        Scenario scenario = CZECHIA_SCENARIO;
-        Device device = new DeviceBuilder()
-                .setClocksFromScenario(scenario)
-                .setTimeZoneDetectionEnabled(true)
-                .setTimeZoneSettingInitialized(true)
-                .initialize();
-        Script script = new Script(device);
-
-        TimestampedValue<NitzData> goodNitzSignal = scenario.getNitzSignal();
-
-        // Simulate the country code becoming known.
-        script.countryReceived(scenario.getNetworkCountryIsoCode())
-                // The NITZ country is enough to detect the time zone.
-                .verifyOnlyTimeZoneWasSetAndReset(scenario.getTimeZoneId(), 1);
-
-        // Check NitzStateMachine state.
-        assertFalse(mNitzStateMachine.getNitzTimeZoneDetectionSuccessful());
-        assertNull(mNitzStateMachine.getCachedNitzData());
-        assertEquals(scenario.getTimeZoneId(), mNitzStateMachine.getSavedTimeZoneId());
-
-        // Simulate receiving an NITZ signal.
-        script.nitzReceived(goodNitzSignal)
-                // The time will be suggested from the NITZ signal.
-                // The combination of NITZ + country will cause the time zone to be set.
-                .verifyTimeSuggestedAndZoneSetAndReset(
-                        scenario.getNitzSignal(), scenario.getTimeZoneId());
-
-        // Check NitzStateMachine state.
-        assertTrue(mNitzStateMachine.getNitzTimeZoneDetectionSuccessful());
-        assertEquals(goodNitzSignal.getValue(), mNitzStateMachine.getCachedNitzData());
-        assertEquals(scenario.getTimeZoneId(), mNitzStateMachine.getSavedTimeZoneId());
-    }
-
-    @Test
-    public void test_bogusCzNitzSignal_nitzReceivedFirst() throws Exception {
-        Scenario scenario = CZECHIA_SCENARIO;
-        Device device = new DeviceBuilder()
-                .setClocksFromScenario(scenario)
-                .setTimeZoneDetectionEnabled(true)
-                .setTimeZoneSettingInitialized(true)
-                .initialize();
-        Script script = new Script(device);
-
-        TimestampedValue<NitzData> goodNitzSignal = scenario.getNitzSignal();
-
-        // Create a corrupted NITZ signal, where the offset information has been lost.
-        NitzData bogusNitzData = NitzData.createForTests(
-                0 /* UTC! */, null /* dstOffsetMillis */,
-                goodNitzSignal.getValue().getCurrentTimeInMillis(),
-                null /* emulatorHostTimeZone */);
-        TimestampedValue<NitzData> badNitzSignal = new TimestampedValue<>(
-                goodNitzSignal.getReferenceTimeMillis(), bogusNitzData);
-
-        // Simulate receiving an NITZ signal.
-        script.nitzReceived(badNitzSignal)
-                // The NITZ alone isn't enough to detect a time zone, but there isn't enough
-                // information to work out it is bogus.
-                .verifyOnlyTimeWasSuggestedAndReset(scenario.getNitzSignal());
-
-        // Check NitzStateMachine state.
-        assertFalse(mNitzStateMachine.getNitzTimeZoneDetectionSuccessful());
-        assertEquals(badNitzSignal.getValue(), mNitzStateMachine.getCachedNitzData());
-        assertNull(mNitzStateMachine.getSavedTimeZoneId());
-
-        // Simulate the country code becoming known.
-        script.countryReceived(scenario.getNetworkCountryIsoCode())
-                // The country is enough to detect the time zone for CZ. If the NITZ signal
-                // wasn't obviously bogus we'd try to set it twice.
-                .verifyOnlyTimeZoneWasSetAndReset(scenario.getTimeZoneId(), 1);
-
-        // Check NitzStateMachine state.
-        assertFalse(mNitzStateMachine.getNitzTimeZoneDetectionSuccessful());
-        assertEquals(badNitzSignal.getValue(), mNitzStateMachine.getCachedNitzData());
-        assertEquals(scenario.getTimeZoneId(), mNitzStateMachine.getSavedTimeZoneId());
-    }
-
-    @Test
-    public void test_bogusCzNitzSignal_countryReceivedFirst() throws Exception {
-        Scenario scenario = CZECHIA_SCENARIO;
-        Device device = new DeviceBuilder()
-                .setClocksFromScenario(scenario)
-                .setTimeZoneDetectionEnabled(true)
-                .setTimeZoneSettingInitialized(true)
-                .initialize();
-        Script script = new Script(device);
-
-        TimestampedValue<NitzData> goodNitzSignal = scenario.getNitzSignal();
-
-        // Create a corrupted NITZ signal, where the offset information has been lost.
-        NitzData bogusNitzData = NitzData.createForTests(
-                0 /* UTC! */, null /* dstOffsetMillis */,
-                goodNitzSignal.getValue().getCurrentTimeInMillis(),
-                null /* emulatorHostTimeZone */);
-        TimestampedValue<NitzData> badNitzSignal = new TimestampedValue<>(
-                goodNitzSignal.getReferenceTimeMillis(), bogusNitzData);
-
-        // Simulate the country code becoming known.
-        script.countryReceived(scenario.getNetworkCountryIsoCode())
-                // The country is enough to detect the time zone for CZ.
-                .verifyOnlyTimeZoneWasSetAndReset(scenario.getTimeZoneId());
-
-        // Check NitzStateMachine state.
-        assertFalse(mNitzStateMachine.getNitzTimeZoneDetectionSuccessful());
-        assertNull(mNitzStateMachine.getCachedNitzData());
-        assertEquals(scenario.getTimeZoneId(), mNitzStateMachine.getSavedTimeZoneId());
-
-        // Simulate receiving an NITZ signal.
-        script.nitzReceived(badNitzSignal)
-                // The NITZ should be detected as bogus so only the time will be suggested.
-                .verifyOnlyTimeWasSuggestedAndReset(scenario.getNitzSignal());
-
-        // Check NitzStateMachine state.
-        assertFalse(mNitzStateMachine.getNitzTimeZoneDetectionSuccessful());
-        assertEquals(badNitzSignal.getValue(), mNitzStateMachine.getCachedNitzData());
-        assertEquals(scenario.getTimeZoneId(), mNitzStateMachine.getSavedTimeZoneId());
-    }
-
-    @Test
-    public void test_bogusUniqueUsNitzSignal_nitzReceivedFirst() throws Exception {
-        Scenario scenario = UNIQUE_US_ZONE_SCENARIO;
-        Device device = new DeviceBuilder()
-                .setClocksFromScenario(scenario)
-                .setTimeZoneDetectionEnabled(true)
-                .setTimeZoneSettingInitialized(true)
-                .initialize();
-        Script script = new Script(device);
-
-        TimestampedValue<NitzData> goodNitzSignal = scenario.getNitzSignal();
-
-        // Create a corrupted NITZ signal, where the offset information has been lost.
-        NitzData bogusNitzData = NitzData.createForTests(
-                0 /* UTC! */, null /* dstOffsetMillis */,
-                goodNitzSignal.getValue().getCurrentTimeInMillis(),
-                null /* emulatorHostTimeZone */);
-        TimestampedValue<NitzData> badNitzSignal = new TimestampedValue<>(
-                goodNitzSignal.getReferenceTimeMillis(), bogusNitzData);
-
-        // Simulate receiving an NITZ signal.
-        script.nitzReceived(badNitzSignal)
-                // The NITZ alone isn't enough to detect a time zone, but there isn't enough
-                // information to work out its bogus.
-                .verifyOnlyTimeWasSuggestedAndReset(scenario.getNitzSignal());
-
-        // Check NitzStateMachine state.
-        assertFalse(mNitzStateMachine.getNitzTimeZoneDetectionSuccessful());
-        assertEquals(badNitzSignal.getValue(), mNitzStateMachine.getCachedNitzData());
-        assertNull(mNitzStateMachine.getSavedTimeZoneId());
-
-        // Simulate the country code becoming known.
-        script.countryReceived(scenario.getNetworkCountryIsoCode())
-                // The country isn't enough to detect the time zone for US so we will leave the time
-                // zone unset.
-                .verifyNothingWasSetAndReset();
-
-        // Check NitzStateMachine state.
-        assertFalse(mNitzStateMachine.getNitzTimeZoneDetectionSuccessful());
-        assertEquals(badNitzSignal.getValue(), mNitzStateMachine.getCachedNitzData());
-        assertNull(mNitzStateMachine.getSavedTimeZoneId());
-    }
-
-    @Test
-    public void test_bogusUsUniqueNitzSignal_countryReceivedFirst() throws Exception {
-        Scenario scenario = UNIQUE_US_ZONE_SCENARIO;
-        Device device = new DeviceBuilder()
-                .setClocksFromScenario(scenario)
-                .setTimeZoneDetectionEnabled(true)
-                .setTimeZoneSettingInitialized(true)
-                .initialize();
-        Script script = new Script(device);
-
-        TimestampedValue<NitzData> goodNitzSignal = scenario.getNitzSignal();
-
-        // Create a corrupted NITZ signal, where the offset information has been lost.
-        NitzData bogusNitzData = NitzData.createForTests(
-                0 /* UTC! */, null /* dstOffsetMillis */,
-                goodNitzSignal.getValue().getCurrentTimeInMillis(),
-                null /* emulatorHostTimeZone */);
-        TimestampedValue<NitzData> badNitzSignal = new TimestampedValue<>(
-                goodNitzSignal.getReferenceTimeMillis(), bogusNitzData);
-
-        // Simulate the country code becoming known.
-        script.countryReceived(scenario.getNetworkCountryIsoCode())
-                // The country isn't enough to detect the time zone for US so we will leave the time
-                // zone unset.
-                .verifyNothingWasSetAndReset();
-
-        // Check NitzStateMachine state.
-        assertFalse(mNitzStateMachine.getNitzTimeZoneDetectionSuccessful());
-        assertNull(mNitzStateMachine.getCachedNitzData());
-        assertNull(mNitzStateMachine.getSavedTimeZoneId());
-
-        // Simulate receiving an NITZ signal.
-        script.nitzReceived(badNitzSignal)
-                // The NITZ should be detected as bogus so only the time will be suggested.
-                .verifyOnlyTimeWasSuggestedAndReset(scenario.getNitzSignal());
-
-        // Check NitzStateMachine state.
-        assertFalse(mNitzStateMachine.getNitzTimeZoneDetectionSuccessful());
-        assertEquals(badNitzSignal.getValue(), mNitzStateMachine.getCachedNitzData());
-        assertNull(mNitzStateMachine.getSavedTimeZoneId());
-    }
-
-    @Test
-    public void test_emulatorNitzExtensionUsedForTimeZone() throws Exception {
-        Scenario scenario = UNIQUE_US_ZONE_SCENARIO;
-        Device device = new DeviceBuilder()
-                .setClocksFromScenario(scenario)
-                .setTimeZoneDetectionEnabled(true)
-                .setTimeZoneSettingInitialized(true)
-                .initialize();
-        Script script = new Script(device);
-
-        TimestampedValue<NitzData> originalNitzSignal = scenario.getNitzSignal();
-
-        // Create an NITZ signal with an explicit time zone (as can happen on emulators)
-        NitzData originalNitzData = originalNitzSignal.getValue();
-        // A time zone that is obviously not in the US, but it should not be questioned.
-        String emulatorTimeZoneId = "Europe/London";
-        NitzData emulatorNitzData = NitzData.createForTests(
-                originalNitzData.getLocalOffsetMillis(),
-                originalNitzData.getDstAdjustmentMillis(),
-                originalNitzData.getCurrentTimeInMillis(),
-                java.util.TimeZone.getTimeZone(emulatorTimeZoneId) /* emulatorHostTimeZone */);
-        TimestampedValue<NitzData> emulatorNitzSignal = new TimestampedValue<>(
-                originalNitzSignal.getReferenceTimeMillis(), emulatorNitzData);
-
-        // Simulate receiving the emulator NITZ signal.
-        script.nitzReceived(emulatorNitzSignal)
-                .verifyTimeSuggestedAndZoneSetAndReset(
-                        scenario.getNitzSignal(), emulatorTimeZoneId);
-
-        // Check NitzStateMachine state.
-        assertTrue(mNitzStateMachine.getNitzTimeZoneDetectionSuccessful());
-        assertEquals(emulatorNitzSignal.getValue(), mNitzStateMachine.getCachedNitzData());
-        assertEquals(emulatorTimeZoneId, mNitzStateMachine.getSavedTimeZoneId());
-    }
-
-    @Test
-    public void test_emptyCountryStringUsTime_countryReceivedFirst() throws Exception {
-        Scenario scenario = UNIQUE_US_ZONE_SCENARIO;
-        Device device = new DeviceBuilder()
-                .setClocksFromScenario(scenario)
-                .setTimeZoneDetectionEnabled(true)
-                .setTimeZoneSettingInitialized(true)
-                .initialize();
-        Script script = new Script(device);
-
-        String expectedZoneId = checkNitzOnlyLookupIsAmbiguousAndReturnZoneId(scenario);
-
-        // Nothing should be set. The country is not valid.
-        script.countryReceived("").verifyNothingWasSetAndReset();
-
-        // Check NitzStateMachine state.
-        assertFalse(mNitzStateMachine.getNitzTimeZoneDetectionSuccessful());
-        assertNull(mNitzStateMachine.getCachedNitzData());
-        assertNull(mNitzStateMachine.getSavedTimeZoneId());
-
-        // Simulate receiving the NITZ signal.
-        script.nitzReceived(scenario.getNitzSignal())
-                .verifyTimeSuggestedAndZoneSetAndReset(scenario.getNitzSignal(), expectedZoneId);
-
-        // Check NitzStateMachine state.
-        assertTrue(mNitzStateMachine.getNitzTimeZoneDetectionSuccessful());
-        assertEquals(scenario.getNitzSignal().getValue(), mNitzStateMachine.getCachedNitzData());
-        assertEquals(expectedZoneId, mNitzStateMachine.getSavedTimeZoneId());
-    }
-
-    @Test
-    public void test_emptyCountryStringUsTime_nitzReceivedFirst() throws Exception {
-        Scenario scenario = UNIQUE_US_ZONE_SCENARIO;
-        Device device = new DeviceBuilder()
-                .setClocksFromScenario(scenario)
-                .setTimeZoneDetectionEnabled(true)
-                .setTimeZoneSettingInitialized(true)
-                .initialize();
-        Script script = new Script(device);
-
-        String expectedZoneId = checkNitzOnlyLookupIsAmbiguousAndReturnZoneId(scenario);
-
-        // Simulate receiving the NITZ signal.
-        script.nitzReceived(scenario.getNitzSignal())
-                .verifyOnlyTimeWasSuggestedAndReset(scenario.getNitzSignal());
-
-        // Check NitzStateMachine state.
-        assertFalse(mNitzStateMachine.getNitzTimeZoneDetectionSuccessful());
-        assertEquals(scenario.getNitzSignal().getValue(), mNitzStateMachine.getCachedNitzData());
-        assertNull(mNitzStateMachine.getSavedTimeZoneId());
-
-        // The time zone should be set (but the country is not valid so it's unlikely to be
-        // correct).
-        script.countryReceived("").verifyOnlyTimeZoneWasSetAndReset(expectedZoneId);
-
-        // Check NitzStateMachine state.
-        assertTrue(mNitzStateMachine.getNitzTimeZoneDetectionSuccessful());
-        assertEquals(scenario.getNitzSignal().getValue(), mNitzStateMachine.getCachedNitzData());
-        assertEquals(expectedZoneId, mNitzStateMachine.getSavedTimeZoneId());
-    }
-
-    /**
-     * Asserts a test scenario has the properties we expect for NITZ-only lookup. There are
-     * usually multiple zones that will share the same UTC offset so we get a low quality / low
-     * confidence answer, but the zone we find should at least have the correct offset.
-     */
-    private String checkNitzOnlyLookupIsAmbiguousAndReturnZoneId(Scenario scenario) {
-        OffsetResult result =
-                mRealTimeZoneLookupHelper.lookupByNitz(scenario.getNitzSignal().getValue());
-        String expectedZoneId = result.zoneId;
-        // All our scenarios should return multiple matches. The only cases where this wouldn't be
-        // true are places that use offsets like XX:15, XX:30 and XX:45.
-        assertFalse(result.isOnlyMatch);
-        assertSameOffset(scenario.getActualTimeMillis(), expectedZoneId, scenario.getTimeZoneId());
-        return expectedZoneId;
-    }
-
-    private static void assertSameOffset(long timeMillis, String zoneId1, String zoneId2) {
-        assertEquals(TimeZone.getTimeZone(zoneId1).getOffset(timeMillis),
-                TimeZone.getTimeZone(zoneId2).getOffset(timeMillis));
-    }
-
-    private static long createUtcTime(int year, int monthInYear, int day, int hourOfDay, int minute,
-            int second) {
-        Calendar cal = new GregorianCalendar(TimeZone.getTimeZone("Etc/UTC"));
-        cal.clear();
-        cal.set(year, monthInYear - 1, day, hourOfDay, minute, second);
-        return cal.getTimeInMillis();
-    }
-
-    /**
-     * A helper class for common test operations involving a device.
-     */
-    class Script {
-        private final Device mDevice;
-
-        Script(Device device) {
-            this.mDevice = device;
-        }
-
-        Script countryReceived(String countryIsoCode) {
-            mDevice.networkCountryKnown(countryIsoCode);
-            return this;
-        }
-
-        Script nitzReceived(TimestampedValue<NitzData> nitzSignal) {
-            mDevice.nitzSignalReceived(nitzSignal);
-            return this;
-        }
-
-        Script verifyNothingWasSetAndReset() {
-            mDevice.verifyTimeZoneWasNotSet();
-            mDevice.verifyTimeWasNotSuggested();
-            mDevice.checkNoUnverifiedSetOperations();
-            mDevice.resetInvocations();
-            return this;
-        }
-
-        Script verifyOnlyTimeZoneWasSetAndReset(String timeZoneId, int times) {
-            mDevice.verifyTimeZoneWasSet(timeZoneId, times);
-            mDevice.verifyTimeWasNotSuggested();
-            mDevice.checkNoUnverifiedSetOperations();
-            mDevice.resetInvocations();
-            return this;
-        }
-
-        Script verifyOnlyTimeZoneWasSetAndReset(String timeZoneId) {
-            return verifyOnlyTimeZoneWasSetAndReset(timeZoneId, 1);
-        }
-
-        Script verifyOnlyTimeWasSuggestedAndReset(TimestampedValue<NitzData> nitzSignal) {
-            mDevice.verifyTimeZoneWasNotSet();
-
-            TimestampedValue<Long> time = new TimestampedValue<>(
-                    nitzSignal.getReferenceTimeMillis(),
-                    nitzSignal.getValue().getCurrentTimeInMillis());
-            mDevice.verifyTimeWasSuggested(time);
-            mDevice.checkNoUnverifiedSetOperations();
-            mDevice.resetInvocations();
-            return this;
-        }
-
-        Script verifyTimeSuggestedAndZoneSetAndReset(
-                TimestampedValue<NitzData> nitzSignal, String timeZoneId) {
-            mDevice.verifyTimeZoneWasSet(timeZoneId);
-
-            TimestampedValue<Long> time = new TimestampedValue<>(
-                    nitzSignal.getReferenceTimeMillis(),
-                    nitzSignal.getValue().getCurrentTimeInMillis());
-            mDevice.verifyTimeWasSuggested(time);
-            mDevice.checkNoUnverifiedSetOperations();
-            mDevice.resetInvocations();
-            return this;
-        }
-
-        Script reset() {
-            mDevice.checkNoUnverifiedSetOperations();
-            mDevice.resetInvocations();
-            return this;
-        }
-    }
-
-    /**
-     * An abstraction of a device for use in telephony time zone detection tests. It can be used to
-     * retrieve device state, modify device state and verify changes.
-     */
-    class Device {
-
-        private final long mInitialSystemClockMillis;
-        private final long mInitialRealtimeMillis;
-        private final boolean mTimeZoneDetectionEnabled;
-        private final boolean mTimeZoneSettingInitialized;
-
-        Device(long initialSystemClockMillis, long initialRealtimeMillis,
-                boolean timeZoneDetectionEnabled, boolean timeZoneSettingInitialized) {
-            mInitialSystemClockMillis = initialSystemClockMillis;
-            mInitialRealtimeMillis = initialRealtimeMillis;
-            mTimeZoneDetectionEnabled = timeZoneDetectionEnabled;
-            mTimeZoneSettingInitialized = timeZoneSettingInitialized;
-        }
-
-        void initialize() {
-            // Set initial configuration.
-            when(mDeviceState.getIgnoreNitz()).thenReturn(false);
-            when(mDeviceState.getNitzUpdateDiffMillis()).thenReturn(2000);
-            when(mDeviceState.getNitzUpdateSpacingMillis()).thenReturn(1000 * 60 * 10);
-
-            // Simulate the country not being known.
-            when(mDeviceState.getNetworkCountryIsoForPhone()).thenReturn("");
-
-            when(mTimeServiceHelper.elapsedRealtime()).thenReturn(mInitialRealtimeMillis);
-            when(mTimeServiceHelper.currentTimeMillis()).thenReturn(mInitialSystemClockMillis);
-            when(mTimeServiceHelper.isTimeZoneDetectionEnabled())
-                    .thenReturn(mTimeZoneDetectionEnabled);
-            when(mTimeServiceHelper.isTimeZoneSettingInitialized())
-                    .thenReturn(mTimeZoneSettingInitialized);
-        }
-
-        void networkCountryKnown(String countryIsoCode) {
-            when(mDeviceState.getNetworkCountryIsoForPhone()).thenReturn(countryIsoCode);
-            mNitzStateMachine.handleNetworkCountryCodeSet(true);
-        }
-
-        void nitzSignalReceived(TimestampedValue<NitzData> nitzSignal) {
-            mNitzStateMachine.handleNitzReceived(nitzSignal);
-        }
-
-        void verifyTimeZoneWasNotSet() {
-            verify(mTimeServiceHelper, times(0)).setDeviceTimeZone(any(String.class));
-        }
-
-        void verifyTimeZoneWasSet(String timeZoneId) {
-            verifyTimeZoneWasSet(timeZoneId, 1 /* times */);
-        }
-
-        void verifyTimeZoneWasSet(String timeZoneId, int times) {
-            verify(mTimeServiceHelper, times(times)).setDeviceTimeZone(timeZoneId);
-        }
-
-        void verifyTimeWasNotSuggested() {
-            verify(mTimeServiceHelper, times(0)).suggestDeviceTime(any());
-        }
-
-        void verifyTimeWasSuggested(TimestampedValue<Long> expectedTime) {
-            verify(mTimeServiceHelper, times(1)).suggestDeviceTime(eq(expectedTime));
-        }
-
-        /**
-         * Used after calling verify... methods to reset expectations.
-         */
-        void resetInvocations() {
-            clearInvocations(mTimeServiceHelper);
-        }
-
-        void checkNoUnverifiedSetOperations() {
-            NitzStateMachineImplTest.checkNoUnverifiedSetOperations(mTimeServiceHelper);
-        }
-    }
-
-    /** A class used to construct a Device. */
-    class DeviceBuilder {
-
-        private long mInitialSystemClock;
-        private long mInitialRealtimeMillis;
-        private boolean mTimeZoneDetectionEnabled;
-        private boolean mTimeZoneSettingInitialized;
-
-        Device initialize() {
-            Device device = new Device(mInitialSystemClock, mInitialRealtimeMillis,
-                    mTimeZoneDetectionEnabled, mTimeZoneSettingInitialized);
-            device.initialize();
-            return device;
-        }
-
-        DeviceBuilder setTimeZoneDetectionEnabled(boolean enabled) {
-            mTimeZoneDetectionEnabled = enabled;
-            return this;
-        }
-
-        DeviceBuilder setTimeZoneSettingInitialized(boolean initialized) {
-            mTimeZoneSettingInitialized = initialized;
-            return this;
-        }
-
-        DeviceBuilder setClocksFromScenario(Scenario scenario) {
-            mInitialRealtimeMillis = scenario.getInitialRealTimeMillis();
-            mInitialSystemClock = scenario.getInitialSystemClockMillis();
-            return this;
-        }
-    }
-
-    /**
-     * A scenario used during tests. Describes a fictional reality.
-     */
-    static class Scenario {
-
-        private final long mInitialDeviceSystemClockMillis;
-        private final long mInitialDeviceRealtimeMillis;
-        private final long mActualTimeMillis;
-        private final TimeZone mZone;
-        private final String mNetworkCountryIsoCode;
-
-        private TimestampedValue<NitzData> mNitzSignal;
-
-        Scenario(long initialDeviceSystemClock, long elapsedRealtime, long timeMillis,
-                String zoneId, String countryIsoCode) {
-            mInitialDeviceSystemClockMillis = initialDeviceSystemClock;
-            mActualTimeMillis = timeMillis;
-            mInitialDeviceRealtimeMillis = elapsedRealtime;
-            mZone = TimeZone.getTimeZone(zoneId);
-            mNetworkCountryIsoCode = countryIsoCode;
-        }
-
-        TimestampedValue<NitzData> getNitzSignal() {
-            if (mNitzSignal == null) {
-                int[] offsets = new int[2];
-                mZone.getOffset(mActualTimeMillis, false /* local */, offsets);
-                int zoneOffsetMillis = offsets[0] + offsets[1];
-                NitzData nitzData = NitzData.createForTests(
-                        zoneOffsetMillis, offsets[1], mActualTimeMillis,
-                        null /* emulatorHostTimeZone */);
-                mNitzSignal = new TimestampedValue<>(mInitialDeviceRealtimeMillis, nitzData);
-            }
-            return mNitzSignal;
-        }
-
-        long getInitialRealTimeMillis() {
-            return mInitialDeviceRealtimeMillis;
-        }
-
-        long getInitialSystemClockMillis() {
-            return mInitialDeviceSystemClockMillis;
-        }
-
-        String getNetworkCountryIsoCode() {
-            return mNetworkCountryIsoCode;
-        }
-
-        String getTimeZoneId() {
-            return mZone.getID();
-        }
-
-        long getActualTimeMillis() {
-            return mActualTimeMillis;
-        }
-
-        static class Builder {
-
-            private long mInitialDeviceSystemClockMillis;
-            private long mInitialDeviceRealtimeMillis;
-            private long mActualTimeMillis;
-            private String mZoneId;
-            private String mCountryIsoCode;
-
-            Builder setInitialDeviceSystemClockUtc(int year, int monthInYear, int day,
-                    int hourOfDay, int minute, int second) {
-                mInitialDeviceSystemClockMillis = createUtcTime(year, monthInYear, day, hourOfDay,
-                        minute, second);
-                return this;
-            }
-
-            Builder setInitialDeviceRealtimeMillis(long realtimeMillis) {
-                mInitialDeviceRealtimeMillis = realtimeMillis;
-                return this;
-            }
-
-            Builder setActualTimeUtc(int year, int monthInYear, int day, int hourOfDay,
-                    int minute, int second) {
-                mActualTimeMillis = createUtcTime(year, monthInYear, day, hourOfDay, minute,
-                        second);
-                return this;
-            }
-
-            Builder setTimeZone(String zoneId) {
-                mZoneId = zoneId;
-                return this;
-            }
-
-            Builder setCountryIso(String isoCode) {
-                mCountryIsoCode = isoCode;
-                return this;
-            }
-
-            Scenario build() {
-                return new Scenario(mInitialDeviceSystemClockMillis, mInitialDeviceRealtimeMillis,
-                        mActualTimeMillis, mZoneId, mCountryIsoCode);
-            }
-        }
-    }
-
-    /**
-     * Confirms all mTimeServiceHelper side effects were verified.
-     */
-    private static void checkNoUnverifiedSetOperations(TimeServiceHelper mTimeServiceHelper) {
-        // We don't care about current auto time / time zone state retrievals / listening so we can
-        // use "at least 0" times to indicate they don't matter.
-        verify(mTimeServiceHelper, atLeast(0)).setListener(any());
-        verify(mTimeServiceHelper, atLeast(0)).isTimeZoneDetectionEnabled();
-        verify(mTimeServiceHelper, atLeast(0)).isTimeZoneSettingInitialized();
-        verify(mTimeServiceHelper, atLeast(0)).elapsedRealtime();
-        verify(mTimeServiceHelper, atLeast(0)).currentTimeMillis();
-        verifyNoMoreInteractions(mTimeServiceHelper);
-    }
-}
diff --git a/tests/telephonytests/src/com/android/internal/telephony/PhoneConfigurationManagerTest.java b/tests/telephonytests/src/com/android/internal/telephony/PhoneConfigurationManagerTest.java
new file mode 100644
index 0000000..07edd34
--- /dev/null
+++ b/tests/telephonytests/src/com/android/internal/telephony/PhoneConfigurationManagerTest.java
@@ -0,0 +1,188 @@
+/*
+ * Copyright (C) 2019 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;
+
+import static android.telephony.TelephonyManager.ACTION_MULTI_SIM_CONFIG_CHANGED;
+import static android.telephony.TelephonyManager.EXTRA_ACTIVE_SIM_SUPPORTED_COUNT;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.clearInvocations;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+
+import android.content.Intent;
+import android.os.AsyncResult;
+import android.os.Handler;
+import android.os.Message;
+import android.telephony.PhoneCapability;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper
+public class PhoneConfigurationManagerTest extends TelephonyTest {
+    @Mock
+    Handler mHandler;
+    @Mock
+    CommandsInterface mMockCi;
+    @Mock
+    PhoneConfigurationManager.MockableInterface mMi;
+
+    private static final int EVENT_MULTI_SIM_CONFIG_CHANGED = 1;
+    PhoneConfigurationManager mPcm;
+
+    @Before
+    public void setUp() throws Exception {
+        super.setUp(getClass().getSimpleName());
+        mPhone.mCi = mMockCi;
+        mCT.mCi = mMockCi;
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        // Restore system properties.
+        super.tearDown();
+    }
+
+    private void setRebootRequiredForConfigSwitch(boolean rebootRequired) {
+        doReturn(rebootRequired).when(mMi).isRebootRequiredForModemConfigChange();
+    }
+
+    private void init(int numOfSim) throws Exception {
+        doReturn(numOfSim).when(mTelephonyManager).getActiveModemCount();
+        replaceInstance(PhoneConfigurationManager.class, "sInstance", null, null);
+        mPcm = PhoneConfigurationManager.init(mContext);
+        replaceInstance(PhoneConfigurationManager.class, "mMi", mPcm, mMi);
+        processAllMessages();
+    }
+
+    /**
+     * Test that a single phone case results in our phone being active and the RIL called
+     */
+    @Test
+    @SmallTest
+    public void testGetPhoneCount() throws Exception {
+        init(1);
+        doReturn(1).when(mTelephonyManager).getActiveModemCount();
+        assertEquals(1, mPcm.getPhoneCount());
+        doReturn(2).when(mTelephonyManager).getActiveModemCount();
+        assertEquals(2, mPcm.getPhoneCount());
+    }
+
+    @Test
+    @SmallTest
+    public void testEnablePhone() throws Exception {
+        init(1);
+        // Phone is null. No crash.
+        mPcm.enablePhone(null, true, null);
+
+        Message message = new Message();
+        mPcm.enablePhone(mPhone, false, message);
+        verify(mMockCi).enableModem(eq(false), eq(message));
+    }
+
+    @Test
+    @SmallTest
+    public void testGetDsdsCapability() throws Exception {
+        init(1);
+        assertEquals(PhoneCapability.DEFAULT_SSSS_CAPABILITY, mPcm.getStaticPhoneCapability());
+
+        ArgumentCaptor<Message> captor = ArgumentCaptor.forClass(Message.class);
+        verify(mMockRadioConfig).getPhoneCapability(captor.capture());
+        Message msg = captor.getValue();
+        AsyncResult.forMessage(msg, PhoneCapability.DEFAULT_DSDS_CAPABILITY, null);
+        msg.sendToTarget();
+        processAllMessages();
+
+        // Not static capability should indicate DSDS capable.
+        assertEquals(PhoneCapability.DEFAULT_DSDS_CAPABILITY, mPcm.getStaticPhoneCapability());
+    }
+
+    @Test
+    @SmallTest
+    public void testSwitchMultiSimConfig_notDsdsCapable_shouldFail() throws Exception {
+        init(1);
+        assertEquals(PhoneCapability.DEFAULT_SSSS_CAPABILITY, mPcm.getStaticPhoneCapability());
+
+        // Try switching to dual SIM. Shouldn't work as we haven't indicated DSDS is supported.
+        mPcm.switchMultiSimConfig(2);
+        verify(mMockRadioConfig, never()).setModemsConfig(anyInt(), any());
+    }
+
+    @Test
+    @SmallTest
+    public void testSwitchMultiSimConfig_dsdsCapable_noRebootRequired() throws Exception {
+        init(1);
+        // Register for multi SIM config change.
+        mPcm.registerForMultiSimConfigChange(mHandler, EVENT_MULTI_SIM_CONFIG_CHANGED, null);
+        verify(mHandler, never()).sendMessageAtTime(any(), anyLong());
+
+        // Try switching to dual SIM. Shouldn't work as we haven't indicated DSDS is supported.
+        mPcm.switchMultiSimConfig(2);
+        verify(mMockRadioConfig, never()).setModemsConfig(anyInt(), any());
+
+        // Send static capability back to indicate DSDS is supported.
+        clearInvocations(mMockRadioConfig);
+        testGetDsdsCapability();
+
+        // Try to switch to DSDS.
+        setRebootRequiredForConfigSwitch(false);
+        mPcm.switchMultiSimConfig(2);
+        ArgumentCaptor<Message> captor = ArgumentCaptor.forClass(Message.class);
+        verify(mMockRadioConfig).setModemsConfig(eq(2), captor.capture());
+
+        // Send message back to indicate switch success.
+        Message message = captor.getValue();
+        AsyncResult.forMessage(message, null, null);
+        message.sendToTarget();
+        processAllMessages();
+
+        // Verify set system property being called.
+        verify(mMi).setMultiSimProperties(2);
+        verify(mMi).notifyPhoneFactoryOnMultiSimConfigChanged(any(), eq(2));
+
+        // Capture and verify registration notification.
+        verify(mHandler).sendMessageAtTime(captor.capture(), anyLong());
+        message = captor.getValue();
+        assertEquals(EVENT_MULTI_SIM_CONFIG_CHANGED, message.what);
+        assertEquals(2, ((AsyncResult) message.obj).result);
+
+        // Capture and verify broadcast.
+        ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class);
+        verify(mContext).sendBroadcast(intentCaptor.capture());
+        Intent intent = intentCaptor.getValue();
+        assertEquals(ACTION_MULTI_SIM_CONFIG_CHANGED, intent.getAction());
+        assertEquals(2, intent.getIntExtra(
+                EXTRA_ACTIVE_SIM_SUPPORTED_COUNT, 0));
+
+        // Verify RIL notification.
+        verify(mMockCi).onSlotActiveStatusChange(true);
+    }
+}
diff --git a/tests/telephonytests/src/com/android/internal/telephony/PhoneFactoryTest.java b/tests/telephonytests/src/com/android/internal/telephony/PhoneFactoryTest.java
index 2f03537..d1aea03 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/PhoneFactoryTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/PhoneFactoryTest.java
@@ -18,7 +18,6 @@
 
 import static org.junit.Assert.fail;
 
-import android.net.NetworkFactory;
 import android.test.suitebuilder.annotation.SmallTest;
 
 import org.junit.Test;
@@ -28,25 +27,25 @@
     @SmallTest
     public void testBeforeMakePhone() {
         try {
-            Phone phone = PhoneFactory.getDefaultPhone();
+            PhoneFactory.getDefaultPhone();
             fail("Expecting IllegalStateException");
         } catch (IllegalStateException e) {
         }
 
         try {
-            Phone phone = PhoneFactory.getPhone(0);
+            PhoneFactory.getPhone(0);
             fail("Expecting IllegalStateException");
         } catch (IllegalStateException e) {
         }
 
         try {
-            Phone[] phone = PhoneFactory.getPhones();
+            PhoneFactory.getPhones();
             fail("Expecting IllegalStateException");
         } catch (IllegalStateException e) {
         }
 
         try {
-            NetworkFactory factory = PhoneFactory.getNetworkFactory(0);
+            PhoneFactory.getNetworkFactory(0);
             fail("Expecting IllegalStateException");
         } catch (IllegalStateException e) {
         }
diff --git a/tests/telephonytests/src/com/android/internal/telephony/PhoneNumberUtilsTest.java b/tests/telephonytests/src/com/android/internal/telephony/PhoneNumberUtilsTest.java
index c3adcc6..b7d2913 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/PhoneNumberUtilsTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/PhoneNumberUtilsTest.java
@@ -29,11 +29,28 @@
 
 import androidx.test.filters.FlakyTest;
 
+import org.junit.After;
+import org.junit.Before;
 import org.junit.Ignore;
 import org.junit.Test;
 
 public class PhoneNumberUtilsTest {
 
+    private static final int MIN_MATCH = 7;
+
+    private int mOldMinMatch;
+
+    @Before
+    public void setUp() throws Exception {
+        mOldMinMatch = PhoneNumberUtils.getMinMatchForTest();
+        PhoneNumberUtils.setMinMatchForTest(MIN_MATCH);
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        PhoneNumberUtils.setMinMatchForTest(mOldMinMatch);
+    }
+
     @SmallTest
     @Test
     public void testExtractNetworkPortion() throws Exception {
diff --git a/tests/telephonytests/src/com/android/internal/telephony/PhoneStateListenerExecutorTest.java b/tests/telephonytests/src/com/android/internal/telephony/PhoneStateListenerExecutorTest.java
index 40940bd..73126f7 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/PhoneStateListenerExecutorTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/PhoneStateListenerExecutorTest.java
@@ -16,12 +16,10 @@
 package com.android.internal.telephony;
 
 import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.Mockito.verify;
 
 import android.telephony.PhoneStateListener;
-import android.telephony.PhysicalChannelConfig;
 import android.telephony.ServiceState;
 import android.test.suitebuilder.annotation.SmallTest;
 
@@ -30,8 +28,6 @@
 import org.junit.Test;
 
 import java.lang.reflect.Field;
-import java.util.Collections;
-import java.util.List;
 import java.util.concurrent.Executor;
 
 public class PhoneStateListenerExecutorTest extends TelephonyTest {
@@ -46,7 +42,6 @@
     private PhoneStateListener mPhoneStateListenerUT;
 
     private boolean mUserMobileDataState = false;
-    private List<PhysicalChannelConfig> mPhysicalChannelConfigs;
 
     @Before
     public void setUp() throws Exception {
@@ -56,8 +51,8 @@
             @Override
             public void onServiceStateChanged(ServiceState serviceState) {
                 logd("Service State Changed");
-                mServiceState.setVoiceRegState(serviceState.getVoiceRegState());
-                mServiceState.setDataRegState(serviceState.getDataRegState());
+                mServiceState.setVoiceRegState(serviceState.getState());
+                mServiceState.setDataRegState(serviceState.getDataRegistrationState());
             }
 
             @Override
@@ -65,13 +60,6 @@
                 logd("User Mobile Data State Changed");
                 mUserMobileDataState = true;
             }
-
-            @Override
-            public void onPhysicalChannelConfigurationChanged(
-                    List<PhysicalChannelConfig> configs) {
-                logd("PhysicalChannelConfig Changed");
-                mPhysicalChannelConfigs = configs;
-            }
         };
     }
 
@@ -106,24 +94,4 @@
 
         assertTrue(mUserMobileDataState);
     }
-
-    @Test @SmallTest
-    public void testTriggerPhysicalChannelConfigurationChanged() throws Exception {
-        Field field = PhoneStateListener.class.getDeclaredField("callback");
-        field.setAccessible(true);
-
-        assertNull(mPhysicalChannelConfigs);
-
-        PhysicalChannelConfig config = new PhysicalChannelConfig.Builder()
-                .setCellConnectionStatus(PhysicalChannelConfig.CONNECTION_PRIMARY_SERVING)
-                .setCellBandwidthDownlinkKhz(2000 /* bandwidth */)
-                .build();
-
-        List<PhysicalChannelConfig> configs = Collections.singletonList(config);
-
-        ((IPhoneStateListener) field.get(mPhoneStateListenerUT))
-            .onPhysicalChannelConfigurationChanged(configs);
-
-        assertTrue(mPhysicalChannelConfigs.equals(configs));
-    }
 }
diff --git a/tests/telephonytests/src/com/android/internal/telephony/PhoneStateListenerTest.java b/tests/telephonytests/src/com/android/internal/telephony/PhoneStateListenerTest.java
index 729260e..8c55442 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/PhoneStateListenerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/PhoneStateListenerTest.java
@@ -20,73 +20,62 @@
 import static org.junit.Assert.assertTrue;
 import static org.mockito.Mockito.verify;
 
-import android.os.HandlerThread;
 import android.telephony.PhoneStateListener;
-import android.telephony.PhysicalChannelConfig;
 import android.telephony.ServiceState;
+import android.telephony.emergency.EmergencyNumber;
 import android.test.suitebuilder.annotation.SmallTest;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
 
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
+import org.junit.runner.RunWith;
 
 import java.lang.reflect.Field;
-import java.util.Collections;
-import java.util.List;
+import java.util.ArrayList;
 
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper
 public class PhoneStateListenerTest extends TelephonyTest {
 
     private PhoneStateListener mPhoneStateListenerUT;
-    private PhoneStateListenerHandler mPhoneStateListenerHandler;
     private boolean mUserMobileDataState = false;
-    private List<PhysicalChannelConfig> mPhysicalChannelConfigs;
-
-    private class PhoneStateListenerHandler extends HandlerThread {
-        private PhoneStateListenerHandler(String name) {
-            super(name);
-        }
-        @Override
-        public void onLooperPrepared() {
-
-            mPhoneStateListenerUT = new PhoneStateListener() {
-                @Override
-                public void onServiceStateChanged(ServiceState serviceState) {
-                    logd("Service State Changed");
-                    mServiceState.setVoiceRegState(serviceState.getVoiceRegState());
-                    mServiceState.setDataRegState(serviceState.getDataRegState());
-                    setReady(true);
-                }
-
-                @Override
-                public void onUserMobileDataStateChanged(boolean state) {
-                    logd("User Mobile Data State Changed");
-                    mUserMobileDataState = true;
-                    setReady(true);
-                }
-
-                @Override
-                public void onPhysicalChannelConfigurationChanged(
-                        List<PhysicalChannelConfig> configs) {
-                    logd("PhysicalChannelConfig Changed");
-                    mPhysicalChannelConfigs = configs;
-                    setReady(true);
-                }
-            };
-            setReady(true);
-        }
-    }
+    private EmergencyNumber mCalledEmergencyNumber;
+    private EmergencyNumber mTextedEmergencyNumber;
 
     @Before
     public void setUp() throws Exception {
-        this.setUp(this.getClass().getSimpleName());
-        mPhoneStateListenerHandler = new PhoneStateListenerHandler(TAG);
-        mPhoneStateListenerHandler.start();
-        waitUntilReady();
+        super.setUp(getClass().getSimpleName());
+        mPhoneStateListenerUT = new PhoneStateListener() {
+            @Override
+            public void onServiceStateChanged(ServiceState serviceState) {
+                logd("Service State Changed");
+                mServiceState.setVoiceRegState(serviceState.getState());
+                mServiceState.setDataRegState(serviceState.getDataRegistrationState());
+            }
+
+            @Override
+            public void onUserMobileDataStateChanged(boolean state) {
+                logd("User Mobile Data State Changed");
+                mUserMobileDataState = true;
+            }
+
+            public void onOutgoingEmergencyCall(EmergencyNumber emergencyNumber) {
+                logd("OutgoingCallEmergencyNumber Changed");
+                mCalledEmergencyNumber = emergencyNumber;
+            }
+
+            public void onOutgoingEmergencySms(EmergencyNumber emergencyNumber) {
+                logd("OutgoingSmsEmergencyNumber Changed");
+                mTextedEmergencyNumber = emergencyNumber;
+            }
+        };
+        processAllMessages();
     }
 
     @After
     public void tearDown() throws Exception {
-        mPhoneStateListenerHandler.quit();
         super.tearDown();
     }
 
@@ -99,9 +88,8 @@
         ss.setDataRegState(ServiceState.STATE_IN_SERVICE);
         ss.setVoiceRegState(ServiceState.STATE_EMERGENCY_ONLY);
 
-        setReady(false);
         ((IPhoneStateListener) field.get(mPhoneStateListenerUT)).onServiceStateChanged(ss);
-        waitUntilReady();
+        processAllMessages();
 
         verify(mServiceState).setDataRegState(ServiceState.STATE_IN_SERVICE);
         verify(mServiceState).setVoiceRegState(ServiceState.STATE_EMERGENCY_ONLY);
@@ -114,32 +102,55 @@
 
         assertFalse(mUserMobileDataState);
 
-        setReady(false);
         ((IPhoneStateListener) field.get(mPhoneStateListenerUT)).onUserMobileDataStateChanged(true);
-        waitUntilReady();
+        processAllMessages();
 
         assertTrue(mUserMobileDataState);
     }
 
     @Test @SmallTest
-    public void testTriggerPhysicalChannelConfigurationChanged() throws Exception {
+    public void testTriggerOutgoingCallEmergencyNumberChanged() throws Exception {
         Field field = PhoneStateListener.class.getDeclaredField("callback");
         field.setAccessible(true);
 
-        assertNull(mPhysicalChannelConfigs);
+        assertNull(mCalledEmergencyNumber);
 
-        PhysicalChannelConfig config = new PhysicalChannelConfig.Builder()
-                .setCellConnectionStatus(PhysicalChannelConfig.CONNECTION_PRIMARY_SERVING)
-                .setCellBandwidthDownlinkKhz(20000 /* bandwidth */)
-                .build();
+        EmergencyNumber emergencyNumber = new EmergencyNumber(
+                "911",
+                "us",
+                "30",
+                EmergencyNumber.EMERGENCY_SERVICE_CATEGORY_UNSPECIFIED,
+                new ArrayList<String>(),
+                EmergencyNumber.EMERGENCY_NUMBER_SOURCE_NETWORK_SIGNALING,
+                EmergencyNumber.EMERGENCY_CALL_ROUTING_NORMAL);
 
-        List<PhysicalChannelConfig> configs = Collections.singletonList(config);
+        ((IPhoneStateListener) field.get(mPhoneStateListenerUT)).onOutgoingEmergencyCall(
+                emergencyNumber);
+        processAllMessages();
 
-        setReady(false);
-        ((IPhoneStateListener) field.get(mPhoneStateListenerUT))
-            .onPhysicalChannelConfigurationChanged(configs);
-        waitUntilReady();
+        assertTrue(mCalledEmergencyNumber.equals(emergencyNumber));
+    }
 
-        assertTrue(mPhysicalChannelConfigs.equals(configs));
+    @Test @SmallTest
+    public void testTriggerOutgoingSmsEmergencyNumberChanged() throws Exception {
+        Field field = PhoneStateListener.class.getDeclaredField("callback");
+        field.setAccessible(true);
+
+        assertNull(mTextedEmergencyNumber);
+
+        EmergencyNumber emergencyNumber = new EmergencyNumber(
+                "911",
+                "us",
+                "30",
+                EmergencyNumber.EMERGENCY_SERVICE_CATEGORY_UNSPECIFIED,
+                new ArrayList<String>(),
+                EmergencyNumber.EMERGENCY_NUMBER_SOURCE_NETWORK_SIGNALING,
+                EmergencyNumber.EMERGENCY_CALL_ROUTING_NORMAL);
+
+        ((IPhoneStateListener) field.get(mPhoneStateListenerUT)).onOutgoingEmergencySms(
+                emergencyNumber);
+        processAllMessages();
+
+        assertTrue(mTextedEmergencyNumber.equals(emergencyNumber));
     }
 }
diff --git a/tests/telephonytests/src/com/android/internal/telephony/PhoneSubInfoControllerTest.java b/tests/telephonytests/src/com/android/internal/telephony/PhoneSubInfoControllerTest.java
index 9117c69..6480f65 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/PhoneSubInfoControllerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/PhoneSubInfoControllerTest.java
@@ -22,14 +22,16 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
-import static org.mockito.Matchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.nullable;
 import static org.mockito.Matchers.anyInt;
 import static org.mockito.Mockito.anyString;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.eq;
 
 import android.app.AppOpsManager;
+import android.app.PropertyInvalidatedCache;
 import android.content.Context;
+import android.os.Build;
 import android.test.suitebuilder.annotation.SmallTest;
 
 import org.junit.After;
@@ -39,6 +41,8 @@
 import org.mockito.Mock;
 
 public class PhoneSubInfoControllerTest extends TelephonyTest {
+    private static final String FEATURE_ID = "myfeatureId";
+
     private PhoneSubInfoController mPhoneSubInfoControllerUT;
     private AppOpsManager mAppOsMgr;
 
@@ -48,15 +52,17 @@
     @Before
     public void setUp() throws Exception {
         super.setUp(getClass().getSimpleName());
+        PropertyInvalidatedCache.disableForTestMode();
         /* mPhone -> PhoneId: 0 -> SubId:0
            mSecondPhone -> PhoneId:1 -> SubId: 1*/
         doReturn(0).when(mSubscriptionController).getPhoneId(eq(0));
         doReturn(1).when(mSubscriptionController).getPhoneId(eq(1));
         doReturn(2).when(mTelephonyManager).getPhoneCount();
-        doReturn(true).when(mSubscriptionController).isActiveSubId(0, TAG);
-        doReturn(true).when(mSubscriptionController).isActiveSubId(1, TAG);
+        doReturn(2).when(mTelephonyManager).getActiveModemCount();
+        doReturn(true).when(mSubscriptionController).isActiveSubId(0, TAG, FEATURE_ID);
+        doReturn(true).when(mSubscriptionController).isActiveSubId(1, TAG, FEATURE_ID);
         doReturn(new int[]{0, 1}).when(mSubscriptionManager)
-                .getActiveSubscriptionIdList(anyBoolean());
+                .getCompleteActiveSubscriptionIdList();
 
         mServiceManagerMockedServices.put("isub", mSubscriptionController);
         doReturn(mSubscriptionController).when(mSubscriptionController)
@@ -65,8 +71,8 @@
 
         mAppOsMgr = (AppOpsManager) mContext.getSystemService(Context.APP_OPS_SERVICE);
 
-        mPhoneSubInfoControllerUT = new PhoneSubInfoController(mContext,
-                new Phone[]{mPhone, mSecondPhone});
+        replaceInstance(PhoneFactory.class, "sPhones", null, new Phone[]{mPhone, mSecondPhone});
+        mPhoneSubInfoControllerUT = new PhoneSubInfoController(mContext);
 
         setupMocksForTelephonyPermissions();
         // TelephonyPermissions will query the READ_DEVICE_IDENTIFIERS op from AppOpManager to
@@ -74,7 +80,8 @@
         // ensure this appop does not interfere with any of the tests always return its default
         // value.
         doReturn(AppOpsManager.MODE_ERRORED).when(mAppOsMgr).noteOpNoThrow(
-                eq(AppOpsManager.OPSTR_READ_DEVICE_IDENTIFIERS), anyInt(), eq(TAG));
+                eq(AppOpsManager.OPSTR_READ_DEVICE_IDENTIFIERS), anyInt(), eq(TAG), eq(FEATURE_ID),
+                nullable(String.class));
     }
 
     @After
@@ -88,8 +95,10 @@
         doReturn("353626073736741").when(mPhone).getDeviceId();
         doReturn("353626073736742").when(mSecondPhone).getDeviceId();
 
-        assertEquals("353626073736741", mPhoneSubInfoControllerUT.getDeviceIdForPhone(0, TAG));
-        assertEquals("353626073736742", mPhoneSubInfoControllerUT.getDeviceIdForPhone(1, TAG));
+        assertEquals("353626073736741",
+                mPhoneSubInfoControllerUT.getDeviceIdForPhone(0, TAG, FEATURE_ID));
+        assertEquals("353626073736742",
+                mPhoneSubInfoControllerUT.getDeviceIdForPhone(1, TAG, FEATURE_ID));
     }
 
     @Test
@@ -98,13 +107,14 @@
         // The READ_PRIVILEGED_PHONE_STATE permission or passing a device / profile owner access
         // check is required to access device identifiers. Since neither of those are true for this
         // test each case will result in a SecurityException being thrown.
+        setIdentifierAccess(false);
         doReturn("353626073736741").when(mPhone).getDeviceId();
         doReturn("353626073736742").when(mSecondPhone).getDeviceId();
 
         //case 1: no READ_PRIVILEGED_PHONE_STATE, READ_PHONE_STATE & appOsMgr READ_PHONE_PERMISSION
         mContextFixture.removeCallingOrSelfPermission(ContextFixture.PERMISSION_ENABLE_ALL);
         try {
-            mPhoneSubInfoControllerUT.getDeviceIdForPhone(0, TAG);
+            mPhoneSubInfoControllerUT.getDeviceIdForPhone(0, TAG, FEATURE_ID);
             Assert.fail("expected Security Exception Thrown");
         } catch (Exception ex) {
             assertTrue(ex instanceof SecurityException);
@@ -112,7 +122,7 @@
         }
 
         try {
-            mPhoneSubInfoControllerUT.getDeviceIdForPhone(1, TAG);
+            mPhoneSubInfoControllerUT.getDeviceIdForPhone(1, TAG, FEATURE_ID);
             Assert.fail("expected Security Exception Thrown");
         } catch (Exception ex) {
             assertTrue(ex instanceof SecurityException);
@@ -122,9 +132,10 @@
         //case 2: no READ_PRIVILEGED_PHONE_STATE & appOsMgr READ_PHONE_PERMISSION
         mContextFixture.addCallingOrSelfPermission(READ_PHONE_STATE);
         doReturn(AppOpsManager.MODE_ERRORED).when(mAppOsMgr).noteOp(
-                eq(AppOpsManager.OP_READ_PHONE_STATE), anyInt(), eq(TAG));
+                eq(AppOpsManager.OPSTR_READ_PHONE_STATE), anyInt(), eq(TAG), eq(FEATURE_ID),
+                nullable(String.class));
         try {
-            mPhoneSubInfoControllerUT.getDeviceIdForPhone(0, TAG);
+            mPhoneSubInfoControllerUT.getDeviceIdForPhone(0, TAG, FEATURE_ID);
             Assert.fail("expected Security Exception Thrown");
         } catch (Exception ex) {
             assertTrue(ex instanceof SecurityException);
@@ -132,7 +143,7 @@
         }
 
         try {
-            mPhoneSubInfoControllerUT.getDeviceIdForPhone(1, TAG);
+            mPhoneSubInfoControllerUT.getDeviceIdForPhone(1, TAG, FEATURE_ID);
             Assert.fail("expected Security Exception Thrown");
         } catch (Exception ex) {
             assertTrue(ex instanceof SecurityException);
@@ -143,9 +154,10 @@
         // The READ_PRIVILEGED_PHONE_STATE permission is now required to get device identifiers.
         mContextFixture.addCallingOrSelfPermission(READ_PHONE_STATE);
         doReturn(AppOpsManager.MODE_ALLOWED).when(mAppOsMgr).noteOp(
-                eq(AppOpsManager.OP_READ_PHONE_STATE), anyInt(), eq(TAG));
+                eq(AppOpsManager.OPSTR_READ_PHONE_STATE), anyInt(), eq(TAG), eq(FEATURE_ID),
+                nullable(String.class));
         try {
-            mPhoneSubInfoControllerUT.getDeviceIdForPhone(0, TAG);
+            mPhoneSubInfoControllerUT.getDeviceIdForPhone(0, TAG, FEATURE_ID);
             Assert.fail("expected Security Exception Thrown");
         } catch (Exception ex) {
             assertTrue(ex instanceof SecurityException);
@@ -153,7 +165,7 @@
         }
 
         try {
-            mPhoneSubInfoControllerUT.getDeviceIdForPhone(1, TAG);
+            mPhoneSubInfoControllerUT.getDeviceIdForPhone(1, TAG, FEATURE_ID);
             Assert.fail("expected Security Exception Thrown");
         } catch (Exception ex) {
             assertTrue(ex instanceof SecurityException);
@@ -165,22 +177,28 @@
     @SmallTest
     public void testGetNai() {
         doReturn("aaa@example.com").when(mPhone).getNai();
-        assertEquals("aaa@example.com", mPhoneSubInfoControllerUT.getNaiForSubscriber(0, TAG));
+        assertEquals("aaa@example.com",
+                mPhoneSubInfoControllerUT.getNaiForSubscriber(0, TAG, FEATURE_ID));
 
         doReturn("bbb@example.com").when(mSecondPhone).getNai();
-        assertEquals("bbb@example.com", mPhoneSubInfoControllerUT.getNaiForSubscriber(1, TAG));
+        assertEquals("bbb@example.com",
+                mPhoneSubInfoControllerUT.getNaiForSubscriber(1, TAG, FEATURE_ID));
     }
 
     @Test
     @SmallTest
     public void testGetNaiWithOutPermission() {
+        // The READ_PRIVILEGED_PHONE_STATE permission, carrier privileges, or passing a device /
+        // profile owner access check is required to access subscriber identifiers. Since none of
+        // those are true for this test each case will result in a SecurityException being thrown.
+        setIdentifierAccess(false);
         doReturn("aaa@example.com").when(mPhone).getNai();
         doReturn("bbb@example.com").when(mSecondPhone).getNai();
 
         //case 1: no READ_PRIVILEGED_PHONE_STATE, READ_PHONE_STATE & appOsMgr READ_PHONE_PERMISSION
         mContextFixture.removeCallingOrSelfPermission(ContextFixture.PERMISSION_ENABLE_ALL);
         try {
-            mPhoneSubInfoControllerUT.getNaiForSubscriber(0, TAG);
+            mPhoneSubInfoControllerUT.getNaiForSubscriber(0, TAG, FEATURE_ID);
             Assert.fail("expected Security Exception Thrown");
         } catch (Exception ex) {
             assertTrue(ex instanceof SecurityException);
@@ -188,7 +206,7 @@
         }
 
         try {
-            mPhoneSubInfoControllerUT.getNaiForSubscriber(1, TAG);
+            mPhoneSubInfoControllerUT.getNaiForSubscriber(1, TAG, FEATURE_ID);
             Assert.fail("expected Security Exception Thrown");
         } catch (Exception ex) {
             assertTrue(ex instanceof SecurityException);
@@ -198,26 +216,56 @@
         //case 2: no READ_PRIVILEGED_PHONE_STATE & appOsMgr READ_PHONE_PERMISSION
         mContextFixture.addCallingOrSelfPermission(READ_PHONE_STATE);
         doReturn(AppOpsManager.MODE_ERRORED).when(mAppOsMgr).noteOp(
-                eq(AppOpsManager.OP_READ_PHONE_STATE), anyInt(), eq(TAG));
-        assertNull(mPhoneSubInfoControllerUT.getNaiForSubscriber(0, TAG));
-        assertNull(mPhoneSubInfoControllerUT.getNaiForSubscriber(1, TAG));
+                eq(AppOpsManager.OPSTR_READ_PHONE_STATE), anyInt(), eq(TAG), eq(FEATURE_ID),
+                nullable(String.class));
+        try {
+            mPhoneSubInfoControllerUT.getNaiForSubscriber(0, TAG, FEATURE_ID);
+            Assert.fail("expected Security Exception Thrown");
+        } catch (Exception ex) {
+            assertTrue(ex instanceof SecurityException);
+            assertTrue(ex.getMessage().contains("getNai"));
+        }
+
+        try {
+            mPhoneSubInfoControllerUT.getNaiForSubscriber(1, TAG, FEATURE_ID);
+            Assert.fail("expected Security Exception Thrown");
+        } catch (Exception ex) {
+            assertTrue(ex instanceof SecurityException);
+            assertTrue(ex.getMessage().contains("getNai"));
+        }
 
         //case 3: no READ_PRIVILEGED_PHONE_STATE
         mContextFixture.addCallingOrSelfPermission(READ_PHONE_STATE);
         doReturn(AppOpsManager.MODE_ALLOWED).when(mAppOsMgr).noteOp(
-                eq(AppOpsManager.OP_READ_PHONE_STATE), anyInt(), eq(TAG));
-        assertEquals("aaa@example.com", mPhoneSubInfoControllerUT.getNaiForSubscriber(0, TAG));
-        assertEquals("bbb@example.com", mPhoneSubInfoControllerUT.getNaiForSubscriber(1, TAG));
+                eq(AppOpsManager.OPSTR_READ_PHONE_STATE), anyInt(), eq(TAG), eq(FEATURE_ID),
+                nullable(String.class));
+        try {
+            mPhoneSubInfoControllerUT.getNaiForSubscriber(0, TAG, FEATURE_ID);
+            Assert.fail("expected Security Exception Thrown");
+        } catch (Exception ex) {
+            assertTrue(ex instanceof SecurityException);
+            assertTrue(ex.getMessage().contains("getNai"));
+        }
+
+        try {
+            mPhoneSubInfoControllerUT.getNaiForSubscriber(1, TAG, FEATURE_ID);
+            Assert.fail("expected Security Exception Thrown");
+        } catch (Exception ex) {
+            assertTrue(ex instanceof SecurityException);
+            assertTrue(ex.getMessage().contains("getNai"));
+        }
     }
 
     @Test
     @SmallTest
     public void testGetImei() {
         doReturn("990000862471854").when(mPhone).getImei();
-        assertEquals("990000862471854", mPhoneSubInfoControllerUT.getImeiForSubscriber(0, TAG));
+        assertEquals("990000862471854",
+                mPhoneSubInfoControllerUT.getImeiForSubscriber(0, TAG, FEATURE_ID));
 
         doReturn("990000862471855").when(mSecondPhone).getImei();
-        assertEquals("990000862471855", mPhoneSubInfoControllerUT.getImeiForSubscriber(1, TAG));
+        assertEquals("990000862471855",
+                mPhoneSubInfoControllerUT.getImeiForSubscriber(1, TAG, FEATURE_ID));
     }
 
     @Test
@@ -226,13 +274,14 @@
         // The READ_PRIVILEGED_PHONE_STATE permission, carrier privileges, or passing a device /
         // profile owner access check is required to access device identifiers. Since none of
         // those are true for this test each case will result in a SecurityException being thrown.
+        setIdentifierAccess(false);
         doReturn("990000862471854").when(mPhone).getImei();
         doReturn("990000862471855").when(mSecondPhone).getImei();
 
         //case 1: no READ_PRIVILEGED_PHONE_STATE, READ_PHONE_STATE & appOsMgr READ_PHONE_PERMISSION
         mContextFixture.removeCallingOrSelfPermission(ContextFixture.PERMISSION_ENABLE_ALL);
         try {
-            mPhoneSubInfoControllerUT.getImeiForSubscriber(0, TAG);
+            mPhoneSubInfoControllerUT.getImeiForSubscriber(0, TAG, FEATURE_ID);
             Assert.fail("expected Security Exception Thrown");
         } catch (Exception ex) {
             assertTrue(ex instanceof SecurityException);
@@ -240,7 +289,7 @@
         }
 
         try {
-            mPhoneSubInfoControllerUT.getImeiForSubscriber(1, TAG);
+            mPhoneSubInfoControllerUT.getImeiForSubscriber(1, TAG, FEATURE_ID);
             Assert.fail("expected Security Exception Thrown");
         } catch (Exception ex) {
             assertTrue(ex instanceof SecurityException);
@@ -250,9 +299,10 @@
         //case 2: no READ_PRIVILEGED_PHONE_STATE & appOsMgr READ_PHONE_PERMISSION
         mContextFixture.addCallingOrSelfPermission(READ_PHONE_STATE);
         doReturn(AppOpsManager.MODE_ERRORED).when(mAppOsMgr).noteOp(
-                eq(AppOpsManager.OP_READ_PHONE_STATE), anyInt(), eq(TAG));
+                eq(AppOpsManager.OPSTR_READ_PHONE_STATE), anyInt(), eq(TAG), eq(FEATURE_ID),
+                nullable(String.class));
         try {
-            mPhoneSubInfoControllerUT.getImeiForSubscriber(0, TAG);
+            mPhoneSubInfoControllerUT.getImeiForSubscriber(0, TAG, FEATURE_ID);
             Assert.fail("expected Security Exception Thrown");
         } catch (Exception ex) {
             assertTrue(ex instanceof SecurityException);
@@ -260,7 +310,7 @@
         }
 
         try {
-            mPhoneSubInfoControllerUT.getImeiForSubscriber(1, TAG);
+            mPhoneSubInfoControllerUT.getImeiForSubscriber(1, TAG, FEATURE_ID);
             Assert.fail("expected Security Exception Thrown");
         } catch (Exception ex) {
             assertTrue(ex instanceof SecurityException);
@@ -270,9 +320,10 @@
         //case 3: no READ_PRIVILEGED_PHONE_STATE
         mContextFixture.addCallingOrSelfPermission(READ_PHONE_STATE);
         doReturn(AppOpsManager.MODE_ALLOWED).when(mAppOsMgr).noteOp(
-                eq(AppOpsManager.OP_READ_PHONE_STATE), anyInt(), eq(TAG));
+                eq(AppOpsManager.OPSTR_READ_PHONE_STATE), anyInt(), eq(TAG), eq(FEATURE_ID),
+                nullable(String.class));
         try {
-            mPhoneSubInfoControllerUT.getImeiForSubscriber(0, TAG);
+            mPhoneSubInfoControllerUT.getImeiForSubscriber(0, TAG, FEATURE_ID);
             Assert.fail("expected Security Exception Thrown");
         } catch (Exception ex) {
             assertTrue(ex instanceof SecurityException);
@@ -280,7 +331,7 @@
         }
 
         try {
-            mPhoneSubInfoControllerUT.getImeiForSubscriber(1, TAG);
+            mPhoneSubInfoControllerUT.getImeiForSubscriber(1, TAG, FEATURE_ID);
             Assert.fail("expected Security Exception Thrown");
         } catch (Exception ex) {
             assertTrue(ex instanceof SecurityException);
@@ -292,10 +343,10 @@
     @SmallTest
     public void testGetDeviceSvn() {
         doReturn("00").when(mPhone).getDeviceSvn();
-        assertEquals("00", mPhoneSubInfoControllerUT.getDeviceSvnUsingSubId(0, TAG));
+        assertEquals("00", mPhoneSubInfoControllerUT.getDeviceSvnUsingSubId(0, TAG, FEATURE_ID));
 
         doReturn("01").when(mSecondPhone).getDeviceSvn();
-        assertEquals("01", mPhoneSubInfoControllerUT.getDeviceSvnUsingSubId(1, TAG));
+        assertEquals("01", mPhoneSubInfoControllerUT.getDeviceSvnUsingSubId(1, TAG, FEATURE_ID));
     }
 
     @Test
@@ -307,7 +358,7 @@
         //case 1: no READ_PRIVILEGED_PHONE_STATE, READ_PHONE_STATE & appOsMgr READ_PHONE_PERMISSION
         mContextFixture.removeCallingOrSelfPermission(ContextFixture.PERMISSION_ENABLE_ALL);
         try {
-            mPhoneSubInfoControllerUT.getDeviceSvnUsingSubId(0, TAG);
+            mPhoneSubInfoControllerUT.getDeviceSvnUsingSubId(0, TAG, FEATURE_ID);
             Assert.fail("expected Security Exception Thrown");
         } catch (Exception ex) {
             assertTrue(ex instanceof SecurityException);
@@ -315,7 +366,7 @@
         }
 
         try {
-            mPhoneSubInfoControllerUT.getDeviceSvnUsingSubId(1, TAG);
+            mPhoneSubInfoControllerUT.getDeviceSvnUsingSubId(1, TAG, FEATURE_ID);
             Assert.fail("expected Security Exception Thrown");
         } catch (Exception ex) {
             assertTrue(ex instanceof SecurityException);
@@ -325,17 +376,19 @@
         //case 2: no READ_PRIVILEGED_PHONE_STATE & appOsMgr READ_PHONE_PERMISSION
         mContextFixture.addCallingOrSelfPermission(READ_PHONE_STATE);
         doReturn(AppOpsManager.MODE_ERRORED).when(mAppOsMgr).noteOp(
-                eq(AppOpsManager.OP_READ_PHONE_STATE), anyInt(), eq(TAG));
+                eq(AppOpsManager.OPSTR_READ_PHONE_STATE), anyInt(), eq(TAG), eq(FEATURE_ID),
+                nullable(String.class));
 
-        assertNull(mPhoneSubInfoControllerUT.getDeviceSvnUsingSubId(0, TAG));
-        assertNull(mPhoneSubInfoControllerUT.getDeviceSvnUsingSubId(1, TAG));
+        assertNull(mPhoneSubInfoControllerUT.getDeviceSvnUsingSubId(0, TAG, FEATURE_ID));
+        assertNull(mPhoneSubInfoControllerUT.getDeviceSvnUsingSubId(1, TAG, FEATURE_ID));
 
         //case 3: no READ_PRIVILEGED_PHONE_STATE
         mContextFixture.addCallingOrSelfPermission(READ_PHONE_STATE);
         doReturn(AppOpsManager.MODE_ALLOWED).when(mAppOsMgr).noteOp(
-                eq(AppOpsManager.OP_READ_PHONE_STATE), anyInt(), eq(TAG));
-        assertEquals("00", mPhoneSubInfoControllerUT.getDeviceSvnUsingSubId(0, TAG));
-        assertEquals("01", mPhoneSubInfoControllerUT.getDeviceSvnUsingSubId(1, TAG));
+                eq(AppOpsManager.OPSTR_READ_PHONE_STATE), anyInt(), eq(TAG), eq(FEATURE_ID),
+                nullable(String.class));
+        assertEquals("00", mPhoneSubInfoControllerUT.getDeviceSvnUsingSubId(0, TAG, FEATURE_ID));
+        assertEquals("01", mPhoneSubInfoControllerUT.getDeviceSvnUsingSubId(1, TAG, FEATURE_ID));
     }
 
     @Test
@@ -344,18 +397,18 @@
         //IMSI
         doReturn("310260426283121").when(mPhone).getSubscriberId();
         assertEquals("310260426283121", mPhoneSubInfoControllerUT
-                .getSubscriberIdForSubscriber(0, TAG));
+                .getSubscriberIdForSubscriber(0, TAG, FEATURE_ID));
 
         doReturn("310260426283122").when(mSecondPhone).getSubscriberId();
         assertEquals("310260426283122", mPhoneSubInfoControllerUT
-                .getSubscriberIdForSubscriber(1, TAG));
+                .getSubscriberIdForSubscriber(1, TAG, FEATURE_ID));
     }
 
     @Test
     @SmallTest
     public void testGetSubscriberIdWithInactiveSubId() {
         //IMSI
-        assertNull(mPhoneSubInfoControllerUT.getSubscriberIdForSubscriber(2, TAG));
+        assertNull(mPhoneSubInfoControllerUT.getSubscriberIdForSubscriber(2, TAG, FEATURE_ID));
     }
 
     @Test
@@ -364,13 +417,14 @@
         // The READ_PRIVILEGED_PHONE_STATE permission, carrier privileges, or passing a device /
         // profile owner access check is required to access subscriber identifiers. Since none of
         // those are true for this test each case will result in a SecurityException being thrown.
+        setIdentifierAccess(false);
         doReturn("310260426283121").when(mPhone).getSubscriberId();
         doReturn("310260426283122").when(mSecondPhone).getSubscriberId();
 
         //case 1: no READ_PRIVILEGED_PHONE_STATE, READ_PHONE_STATE & appOsMgr READ_PHONE_PERMISSION
         mContextFixture.removeCallingOrSelfPermission(ContextFixture.PERMISSION_ENABLE_ALL);
         try {
-            mPhoneSubInfoControllerUT.getSubscriberIdForSubscriber(0, TAG);
+            mPhoneSubInfoControllerUT.getSubscriberIdForSubscriber(0, TAG, FEATURE_ID);
             Assert.fail("expected Security Exception Thrown");
         } catch (Exception ex) {
             assertTrue(ex instanceof SecurityException);
@@ -378,7 +432,7 @@
         }
 
         try {
-            mPhoneSubInfoControllerUT.getSubscriberIdForSubscriber(1, TAG);
+            mPhoneSubInfoControllerUT.getSubscriberIdForSubscriber(1, TAG, FEATURE_ID);
             Assert.fail("expected Security Exception Thrown");
         } catch (Exception ex) {
             assertTrue(ex instanceof SecurityException);
@@ -388,9 +442,10 @@
         //case 2: no READ_PRIVILEGED_PHONE_STATE & appOsMgr READ_PHONE_PERMISSION
         mContextFixture.addCallingOrSelfPermission(READ_PHONE_STATE);
         doReturn(AppOpsManager.MODE_ERRORED).when(mAppOsMgr).noteOp(
-                eq(AppOpsManager.OP_READ_PHONE_STATE), anyInt(), eq(TAG));
+                eq(AppOpsManager.OPSTR_READ_PHONE_STATE), anyInt(), eq(TAG), eq(FEATURE_ID),
+                nullable(String.class));
         try {
-            mPhoneSubInfoControllerUT.getSubscriberIdForSubscriber(0, TAG);
+            mPhoneSubInfoControllerUT.getSubscriberIdForSubscriber(0, TAG, FEATURE_ID);
             Assert.fail("expected Security Exception Thrown");
         } catch (Exception ex) {
             assertTrue(ex instanceof SecurityException);
@@ -398,7 +453,7 @@
         }
 
         try {
-            mPhoneSubInfoControllerUT.getSubscriberIdForSubscriber(1, TAG);
+            mPhoneSubInfoControllerUT.getSubscriberIdForSubscriber(1, TAG, FEATURE_ID);
             Assert.fail("expected Security Exception Thrown");
         } catch (Exception ex) {
             assertTrue(ex instanceof SecurityException);
@@ -409,9 +464,10 @@
         // The READ_PRIVILEGED_PHONE_STATE permission is now required to get device identifiers.
         mContextFixture.addCallingOrSelfPermission(READ_PHONE_STATE);
         doReturn(AppOpsManager.MODE_ALLOWED).when(mAppOsMgr).noteOp(
-                eq(AppOpsManager.OP_READ_PHONE_STATE), anyInt(), eq(TAG));
+                eq(AppOpsManager.OPSTR_READ_PHONE_STATE), anyInt(), eq(TAG), eq(FEATURE_ID),
+                nullable(String.class));
         try {
-            mPhoneSubInfoControllerUT.getSubscriberIdForSubscriber(0, TAG);
+            mPhoneSubInfoControllerUT.getSubscriberIdForSubscriber(0, TAG, FEATURE_ID);
             Assert.fail("expected Security Exception Thrown");
         } catch (Exception ex) {
             assertTrue(ex instanceof SecurityException);
@@ -419,7 +475,7 @@
         }
 
         try {
-            mPhoneSubInfoControllerUT.getSubscriberIdForSubscriber(1, TAG);
+            mPhoneSubInfoControllerUT.getSubscriberIdForSubscriber(1, TAG, FEATURE_ID);
             Assert.fail("expected Security Exception Thrown");
         } catch (Exception ex) {
             assertTrue(ex instanceof SecurityException);
@@ -433,11 +489,11 @@
         //IccId
         doReturn("8991101200003204510").when(mPhone).getIccSerialNumber();
         assertEquals("8991101200003204510", mPhoneSubInfoControllerUT
-                .getIccSerialNumberForSubscriber(0, TAG));
+                .getIccSerialNumberForSubscriber(0, TAG, FEATURE_ID));
 
         doReturn("8991101200003204511").when(mSecondPhone).getIccSerialNumber();
         assertEquals("8991101200003204511", mPhoneSubInfoControllerUT
-                .getIccSerialNumberForSubscriber(1, TAG));
+                .getIccSerialNumberForSubscriber(1, TAG, FEATURE_ID));
     }
 
     @Test
@@ -446,13 +502,14 @@
         // The READ_PRIVILEGED_PHONE_STATE permission, carrier privileges, or passing a device /
         // profile owner access check is required to access subscriber identifiers. Since none of
         // those are true for this test each case will result in a SecurityException being thrown.
+        setIdentifierAccess(false);
         doReturn("8991101200003204510").when(mPhone).getIccSerialNumber();
         doReturn("8991101200003204511").when(mSecondPhone).getIccSerialNumber();
 
         //case 1: no READ_PRIVILEGED_PHONE_STATE, READ_PHONE_STATE & appOsMgr READ_PHONE_PERMISSION
         mContextFixture.removeCallingOrSelfPermission(ContextFixture.PERMISSION_ENABLE_ALL);
         try {
-            mPhoneSubInfoControllerUT.getIccSerialNumberForSubscriber(0, TAG);
+            mPhoneSubInfoControllerUT.getIccSerialNumberForSubscriber(0, TAG, FEATURE_ID);
             Assert.fail("expected Security Exception Thrown");
         } catch (Exception ex) {
             assertTrue(ex instanceof SecurityException);
@@ -460,7 +517,7 @@
         }
 
         try {
-            mPhoneSubInfoControllerUT.getIccSerialNumberForSubscriber(1, TAG);
+            mPhoneSubInfoControllerUT.getIccSerialNumberForSubscriber(1, TAG, FEATURE_ID);
             Assert.fail("expected Security Exception Thrown");
         } catch (Exception ex) {
             assertTrue(ex instanceof SecurityException);
@@ -470,9 +527,10 @@
         //case 2: no READ_PRIVILEGED_PHONE_STATE & appOsMgr READ_PHONE_PERMISSION
         mContextFixture.addCallingOrSelfPermission(READ_PHONE_STATE);
         doReturn(AppOpsManager.MODE_ERRORED).when(mAppOsMgr).noteOp(
-                eq(AppOpsManager.OP_READ_PHONE_STATE), anyInt(), eq(TAG));
+                eq(AppOpsManager.OPSTR_READ_PHONE_STATE), anyInt(), eq(TAG), eq(FEATURE_ID),
+                nullable(String.class));
         try {
-            mPhoneSubInfoControllerUT.getIccSerialNumberForSubscriber(0, TAG);
+            mPhoneSubInfoControllerUT.getIccSerialNumberForSubscriber(0, TAG, FEATURE_ID);
             Assert.fail("expected Security Exception Thrown");
         } catch (Exception ex) {
             assertTrue(ex instanceof SecurityException);
@@ -480,7 +538,7 @@
         }
 
         try {
-            mPhoneSubInfoControllerUT.getIccSerialNumberForSubscriber(1, TAG);
+            mPhoneSubInfoControllerUT.getIccSerialNumberForSubscriber(1, TAG, FEATURE_ID);
             Assert.fail("expected Security Exception Thrown");
         } catch (Exception ex) {
             assertTrue(ex instanceof SecurityException);
@@ -490,9 +548,10 @@
         //case 3: no READ_PRIVILEGED_PHONE_STATE
         mContextFixture.addCallingOrSelfPermission(READ_PHONE_STATE);
         doReturn(AppOpsManager.MODE_ALLOWED).when(mAppOsMgr).noteOp(
-                eq(AppOpsManager.OP_READ_PHONE_STATE), anyInt(), eq(TAG));
+                eq(AppOpsManager.OPSTR_READ_PHONE_STATE), anyInt(), eq(TAG), eq(FEATURE_ID),
+                nullable(String.class));
         try {
-            mPhoneSubInfoControllerUT.getIccSerialNumberForSubscriber(0, TAG);
+            mPhoneSubInfoControllerUT.getIccSerialNumberForSubscriber(0, TAG, FEATURE_ID);
             Assert.fail("expected Security Exception Thrown");
         } catch (Exception ex) {
             assertTrue(ex instanceof SecurityException);
@@ -500,7 +559,7 @@
         }
 
         try {
-            mPhoneSubInfoControllerUT.getIccSerialNumberForSubscriber(1, TAG);
+            mPhoneSubInfoControllerUT.getIccSerialNumberForSubscriber(1, TAG, FEATURE_ID);
             Assert.fail("expected Security Exception Thrown");
         } catch (Exception ex) {
             assertTrue(ex instanceof SecurityException);
@@ -510,17 +569,21 @@
 
     @Test
     @SmallTest
-    public void testLine1Number() {
+    public void testGetLine1Number() {
+        mApplicationInfo.targetSdkVersion = Build.VERSION_CODES.R;
         doReturn("+18051234567").when(mPhone).getLine1Number();
-        assertEquals("+18051234567", mPhoneSubInfoControllerUT.getLine1NumberForSubscriber(0, TAG));
+        assertEquals("+18051234567",
+                mPhoneSubInfoControllerUT.getLine1NumberForSubscriber(0, TAG, FEATURE_ID));
 
         doReturn("+18052345678").when(mSecondPhone).getLine1Number();
-        assertEquals("+18052345678", mPhoneSubInfoControllerUT.getLine1NumberForSubscriber(1, TAG));
+        assertEquals("+18052345678",
+                mPhoneSubInfoControllerUT.getLine1NumberForSubscriber(1, TAG, FEATURE_ID));
     }
 
     @Test
     @SmallTest
-    public void testLine1NumberWithOutPermission() {
+    public void testGetLine1NumberWithOutPermissionTargetPreR() {
+        mApplicationInfo.targetSdkVersion = Build.VERSION_CODES.Q;
         doReturn("+18051234567").when(mPhone).getLine1Number();
         doReturn("+18052345678").when(mSecondPhone).getLine1Number();
 
@@ -528,20 +591,23 @@
         READ_SMS and no OP_WRITE_SMS & OP_READ_SMS from appOsMgr */
         mContextFixture.removeCallingOrSelfPermission(ContextFixture.PERMISSION_ENABLE_ALL);
         doReturn(AppOpsManager.MODE_ERRORED).when(mAppOsMgr).noteOp(
-                eq(AppOpsManager.OP_READ_SMS), anyInt(), eq(TAG));
+                eq(AppOpsManager.OPSTR_READ_SMS), anyInt(), eq(TAG), eq(FEATURE_ID),
+                nullable(String.class));
         doReturn(AppOpsManager.MODE_ERRORED).when(mAppOsMgr).noteOp(
-                eq(AppOpsManager.OP_WRITE_SMS), anyInt(), eq(TAG));
+                eq(AppOpsManager.OPSTR_WRITE_SMS), anyInt(), eq(TAG), eq(FEATURE_ID),
+                nullable(String.class));
         doReturn(AppOpsManager.MODE_ERRORED).when(mAppOsMgr).noteOp(
-                eq(AppOpsManager.OP_READ_PHONE_STATE), anyInt(), eq(TAG));
+                eq(AppOpsManager.OPSTR_READ_PHONE_STATE), anyInt(), eq(TAG), eq(FEATURE_ID),
+                nullable(String.class));
         try {
-            mPhoneSubInfoControllerUT.getLine1NumberForSubscriber(0, TAG);
+            mPhoneSubInfoControllerUT.getLine1NumberForSubscriber(0, TAG, FEATURE_ID);
             Assert.fail("expected Security Exception Thrown");
         } catch (Exception ex) {
             assertTrue(ex instanceof SecurityException);
         }
 
         try {
-            mPhoneSubInfoControllerUT.getLine1NumberForSubscriber(1, TAG);
+            mPhoneSubInfoControllerUT.getLine1NumberForSubscriber(1, TAG, FEATURE_ID);
             Assert.fail("expected Security Exception Thrown");
         } catch (Exception ex) {
             assertTrue(ex instanceof SecurityException);
@@ -549,66 +615,153 @@
 
         /* case 2: only enable WRITE_SMS permission */
         doReturn(AppOpsManager.MODE_ALLOWED).when(mAppOsMgr).noteOp(
-                eq(AppOpsManager.OP_WRITE_SMS), anyInt(), eq(TAG));
-        assertEquals("+18051234567", mPhoneSubInfoControllerUT.getLine1NumberForSubscriber(0, TAG));
-        assertEquals("+18052345678", mPhoneSubInfoControllerUT.getLine1NumberForSubscriber(1, TAG));
+                eq(AppOpsManager.OPSTR_WRITE_SMS), anyInt(), eq(TAG), eq(FEATURE_ID),
+                nullable(String.class));
+        assertEquals("+18051234567",
+                mPhoneSubInfoControllerUT.getLine1NumberForSubscriber(0, TAG, FEATURE_ID));
+        assertEquals("+18052345678",
+                mPhoneSubInfoControllerUT.getLine1NumberForSubscriber(1, TAG, FEATURE_ID));
 
         /* case 3: only enable READ_PRIVILEGED_PHONE_STATE */
         doReturn(AppOpsManager.MODE_ERRORED).when(mAppOsMgr).noteOp(
-                eq(AppOpsManager.OP_WRITE_SMS), anyInt(), eq(TAG));
+                eq(AppOpsManager.OPSTR_WRITE_SMS), anyInt(), eq(TAG), eq(FEATURE_ID),
+                nullable(String.class));
         mContextFixture.addCallingOrSelfPermission(READ_PRIVILEGED_PHONE_STATE);
-        assertEquals("+18051234567", mPhoneSubInfoControllerUT.getLine1NumberForSubscriber(0, TAG));
-        assertEquals("+18052345678", mPhoneSubInfoControllerUT.getLine1NumberForSubscriber(1, TAG));
+        assertEquals("+18051234567",
+                mPhoneSubInfoControllerUT.getLine1NumberForSubscriber(0, TAG, FEATURE_ID));
+        assertEquals("+18052345678",
+                mPhoneSubInfoControllerUT.getLine1NumberForSubscriber(1, TAG, FEATURE_ID));
 
         /* case 4: only enable READ_PHONE_STATE permission */
         mContextFixture.removeCallingOrSelfPermission(READ_PRIVILEGED_PHONE_STATE);
         mContextFixture.addCallingOrSelfPermission(READ_PHONE_STATE);
-        assertNull(mPhoneSubInfoControllerUT.getLine1NumberForSubscriber(0, TAG));
-        assertNull(mPhoneSubInfoControllerUT.getLine1NumberForSubscriber(1, TAG));
+        assertNull(mPhoneSubInfoControllerUT.getLine1NumberForSubscriber(0, TAG, FEATURE_ID));
+        assertNull(mPhoneSubInfoControllerUT.getLine1NumberForSubscriber(1, TAG, FEATURE_ID));
 
         /* case 5: enable appOsMgr READ_PHONE_PERMISSION & READ_PHONE_STATE */
         doReturn(AppOpsManager.MODE_ALLOWED).when(mAppOsMgr).noteOp(
-                eq(AppOpsManager.OP_READ_PHONE_STATE), anyInt(), eq(TAG));
-        assertEquals("+18051234567", mPhoneSubInfoControllerUT.getLine1NumberForSubscriber(0, TAG));
-        assertEquals("+18052345678", mPhoneSubInfoControllerUT.getLine1NumberForSubscriber(1, TAG));
+                eq(AppOpsManager.OPSTR_READ_PHONE_STATE), anyInt(), eq(TAG), eq(FEATURE_ID),
+                nullable(String.class));
+        assertEquals("+18051234567",
+                mPhoneSubInfoControllerUT.getLine1NumberForSubscriber(0, TAG, FEATURE_ID));
+        assertEquals("+18052345678",
+                mPhoneSubInfoControllerUT.getLine1NumberForSubscriber(1, TAG, FEATURE_ID));
 
-        /* case 6: only enable READ_SMS */
+        /* case 6: only enable READ_SMS; without the appop should throw SecurityException */
         doReturn(AppOpsManager.MODE_ERRORED).when(mAppOsMgr).noteOp(
-                eq(AppOpsManager.OP_READ_PHONE_STATE), anyInt(), eq(TAG));
+                eq(AppOpsManager.OPSTR_READ_PHONE_STATE), anyInt(), eq(TAG), eq(FEATURE_ID),
+                nullable(String.class));
         mContextFixture.removeCallingOrSelfPermission(READ_PHONE_STATE);
         mContextFixture.addCallingOrSelfPermission(READ_SMS);
-        assertNull(mPhoneSubInfoControllerUT.getLine1NumberForSubscriber(0, TAG));
-        assertNull(mPhoneSubInfoControllerUT.getLine1NumberForSubscriber(1, TAG));
+        try {
+            mPhoneSubInfoControllerUT.getLine1NumberForSubscriber(0, TAG, FEATURE_ID);
+            Assert.fail("expected SecurityException thrown");
+        } catch (SecurityException expected) {
+        }
+        try {
+            mPhoneSubInfoControllerUT.getLine1NumberForSubscriber(1, TAG, FEATURE_ID);
+            Assert.fail("expected SecurityException thrown");
+        } catch (SecurityException expected) {
+        }
 
         /* case 7: enable READ_SMS and OP_READ_SMS */
         doReturn(AppOpsManager.MODE_ALLOWED).when(mAppOsMgr).noteOp(
-                eq(AppOpsManager.OP_READ_SMS), anyInt(), eq(TAG));
-        assertEquals("+18051234567", mPhoneSubInfoControllerUT.getLine1NumberForSubscriber(0, TAG));
-        assertEquals("+18052345678", mPhoneSubInfoControllerUT.getLine1NumberForSubscriber(1, TAG));
+                eq(AppOpsManager.OPSTR_READ_SMS), anyInt(), eq(TAG), eq(FEATURE_ID),
+                nullable(String.class));
+        assertEquals("+18051234567",
+                mPhoneSubInfoControllerUT.getLine1NumberForSubscriber(0, TAG, FEATURE_ID));
+        assertEquals("+18052345678",
+                mPhoneSubInfoControllerUT.getLine1NumberForSubscriber(1, TAG, FEATURE_ID));
     }
 
     @Test
     @SmallTest
-    public void testLine1AlphaTag() {
+    public void testGetLine1NumberWithOutPermissionTargetR() {
+        mApplicationInfo.targetSdkVersion = Build.VERSION_CODES.R;
+        doReturn("+18051234567").when(mPhone).getLine1Number();
+        doReturn("+18052345678").when(mSecondPhone).getLine1Number();
+
+        /* case 1: no READ_PRIVILEGED_PHONE_STATE & READ_PHONE_STATE &
+        READ_SMS and no OP_WRITE_SMS & OP_READ_SMS from appOsMgr */
+        mContextFixture.removeCallingOrSelfPermission(ContextFixture.PERMISSION_ENABLE_ALL);
+        doReturn(AppOpsManager.MODE_ERRORED).when(mAppOsMgr).noteOp(
+                eq(AppOpsManager.OPSTR_READ_SMS), anyInt(), eq(TAG), eq(FEATURE_ID),
+                nullable(String.class));
+        doReturn(AppOpsManager.MODE_ERRORED).when(mAppOsMgr).noteOp(
+                eq(AppOpsManager.OPSTR_WRITE_SMS), anyInt(), eq(TAG), eq(FEATURE_ID),
+                nullable(String.class));
+        doReturn(AppOpsManager.MODE_ERRORED).when(mAppOsMgr).noteOp(
+                eq(AppOpsManager.OPSTR_READ_PHONE_STATE), anyInt(), eq(TAG), eq(FEATURE_ID),
+                nullable(String.class));
+        try {
+            mPhoneSubInfoControllerUT.getLine1NumberForSubscriber(0, TAG, FEATURE_ID);
+            Assert.fail("expected Security Exception Thrown");
+        } catch (Exception ex) {
+            assertTrue(ex instanceof SecurityException);
+        }
+
+        try {
+            mPhoneSubInfoControllerUT.getLine1NumberForSubscriber(1, TAG, FEATURE_ID);
+            Assert.fail("expected Security Exception Thrown");
+        } catch (Exception ex) {
+            assertTrue(ex instanceof SecurityException);
+        }
+
+        /* case 2: enable READ_PHONE_STATE permission */
+        doReturn(AppOpsManager.MODE_ALLOWED).when(mAppOsMgr).noteOp(
+                eq(AppOpsManager.OPSTR_READ_PHONE_STATE), anyInt(), eq(TAG), eq(FEATURE_ID),
+                nullable(String.class));
+        try {
+            mPhoneSubInfoControllerUT.getLine1NumberForSubscriber(0, TAG, FEATURE_ID);
+            Assert.fail("expected Security Exception Thrown");
+        } catch (Exception ex) {
+            assertTrue(ex instanceof SecurityException);
+        }
+
+        try {
+            mPhoneSubInfoControllerUT.getLine1NumberForSubscriber(1, TAG, FEATURE_ID);
+            Assert.fail("expected Security Exception Thrown");
+        } catch (Exception ex) {
+            assertTrue(ex instanceof SecurityException);
+        }
+
+        /* case 3: enable READ_SMS and OP_READ_SMS */
+        doReturn(AppOpsManager.MODE_ERRORED).when(mAppOsMgr).noteOp(
+                eq(AppOpsManager.OPSTR_READ_PHONE_STATE), anyInt(), eq(TAG), eq(FEATURE_ID),
+                nullable(String.class));
+        mContextFixture.addCallingOrSelfPermission(READ_SMS);
+        doReturn(AppOpsManager.MODE_ALLOWED).when(mAppOsMgr).noteOp(
+                eq(AppOpsManager.OPSTR_READ_SMS), anyInt(), eq(TAG), eq(FEATURE_ID),
+                nullable(String.class));
+        assertEquals("+18051234567",
+                mPhoneSubInfoControllerUT.getLine1NumberForSubscriber(0, TAG, FEATURE_ID));
+        assertEquals("+18052345678",
+                mPhoneSubInfoControllerUT.getLine1NumberForSubscriber(1, TAG, FEATURE_ID));
+    }
+
+
+    @Test
+    @SmallTest
+    public void testGetLine1AlphaTag() {
         doReturn("LINE1_SIM_0").when(mPhone).getLine1AlphaTag();
         assertEquals("LINE1_SIM_0", mPhoneSubInfoControllerUT
-                .getLine1AlphaTagForSubscriber(0, TAG));
+                .getLine1AlphaTagForSubscriber(0, TAG, FEATURE_ID));
 
         doReturn("LINE1_SIM_1").when(mSecondPhone).getLine1AlphaTag();
         assertEquals("LINE1_SIM_1", mPhoneSubInfoControllerUT
-                .getLine1AlphaTagForSubscriber(1, TAG));
+                .getLine1AlphaTagForSubscriber(1, TAG, FEATURE_ID));
     }
 
     @Test
     @SmallTest
-    public void testLine1AlphaTagWithOutPermission() {
+    public void testGetLine1AlphaTagWithOutPermission() {
         doReturn("LINE1_SIM_0").when(mPhone).getLine1AlphaTag();
         doReturn("LINE1_SIM_1").when(mSecondPhone).getLine1AlphaTag();
 
         //case 1: no READ_PRIVILEGED_PHONE_STATE, READ_PHONE_STATE & appOsMgr READ_PHONE_PERMISSION
         mContextFixture.removeCallingOrSelfPermission(ContextFixture.PERMISSION_ENABLE_ALL);
         try {
-            mPhoneSubInfoControllerUT.getLine1AlphaTagForSubscriber(0, TAG);
+            mPhoneSubInfoControllerUT.getLine1AlphaTagForSubscriber(0, TAG, FEATURE_ID);
             Assert.fail("expected Security Exception Thrown");
         } catch (Exception ex) {
             assertTrue(ex instanceof SecurityException);
@@ -616,7 +769,7 @@
         }
 
         try {
-            mPhoneSubInfoControllerUT.getLine1AlphaTagForSubscriber(1, TAG);
+            mPhoneSubInfoControllerUT.getLine1AlphaTagForSubscriber(1, TAG, FEATURE_ID);
             Assert.fail("expected Security Exception Thrown");
         } catch (Exception ex) {
             assertTrue(ex instanceof SecurityException);
@@ -626,69 +779,146 @@
         //case 2: no READ_PRIVILEGED_PHONE_STATE & appOsMgr READ_PHONE_PERMISSION
         mContextFixture.addCallingOrSelfPermission(READ_PHONE_STATE);
         doReturn(AppOpsManager.MODE_ERRORED).when(mAppOsMgr).noteOp(
-                eq(AppOpsManager.OP_READ_PHONE_STATE), anyInt(), eq(TAG));
+                eq(AppOpsManager.OPSTR_READ_PHONE_STATE), anyInt(), eq(TAG), eq(FEATURE_ID),
+                nullable(String.class));
 
-        assertNull(mPhoneSubInfoControllerUT.getLine1AlphaTagForSubscriber(0, TAG));
-        assertNull(mPhoneSubInfoControllerUT.getLine1AlphaTagForSubscriber(1, TAG));
+        assertNull(mPhoneSubInfoControllerUT.getLine1AlphaTagForSubscriber(0, TAG, FEATURE_ID));
+        assertNull(mPhoneSubInfoControllerUT.getLine1AlphaTagForSubscriber(1, TAG, FEATURE_ID));
 
         //case 3: no READ_PRIVILEGED_PHONE_STATE
         mContextFixture.addCallingOrSelfPermission(READ_PHONE_STATE);
         doReturn(AppOpsManager.MODE_ALLOWED).when(mAppOsMgr).noteOp(
-                eq(AppOpsManager.OP_READ_PHONE_STATE), anyInt(), eq(TAG));
+                eq(AppOpsManager.OPSTR_READ_PHONE_STATE), anyInt(), eq(TAG), eq(FEATURE_ID),
+                nullable(String.class));
         assertEquals("LINE1_SIM_0", mPhoneSubInfoControllerUT
-                .getLine1AlphaTagForSubscriber(0, TAG));
+                .getLine1AlphaTagForSubscriber(0, TAG, FEATURE_ID));
         assertEquals("LINE1_SIM_1", mPhoneSubInfoControllerUT
-                .getLine1AlphaTagForSubscriber(1, TAG));
+                .getLine1AlphaTagForSubscriber(1, TAG, FEATURE_ID));
     }
 
     @Test
     @SmallTest
-    public void testMsisdn() {
+    public void testGetMsisdn() {
+        mApplicationInfo.targetSdkVersion = Build.VERSION_CODES.R;
         doReturn("+18051234567").when(mPhone).getMsisdn();
-        assertEquals("+18051234567", mPhoneSubInfoControllerUT.getMsisdnForSubscriber(0, TAG));
+        assertEquals("+18051234567",
+                mPhoneSubInfoControllerUT.getMsisdnForSubscriber(0, TAG, FEATURE_ID));
 
         doReturn("+18052345678").when(mSecondPhone).getMsisdn();
-        assertEquals("+18052345678", mPhoneSubInfoControllerUT.getMsisdnForSubscriber(1, TAG));
+        assertEquals("+18052345678",
+                mPhoneSubInfoControllerUT.getMsisdnForSubscriber(1, TAG, FEATURE_ID));
     }
 
     @Test
     @SmallTest
-    public void testMsisdnWithOutPermission() {
+    public void testGetMsisdnWithOutPermissionTargetPreR() {
+        mApplicationInfo.targetSdkVersion = Build.VERSION_CODES.Q;
         doReturn("+18051234567").when(mPhone).getMsisdn();
         doReturn("+18052345678").when(mSecondPhone).getMsisdn();
 
-        //case 1: no READ_PRIVILEGED_PHONE_STATE, READ_PHONE_STATE & appOsMgr READ_PHONE_PERMISSION
+        /* case 1: no READ_PRIVILEGED_PHONE_STATE & READ_PHONE_STATE from appOsMgr */
         mContextFixture.removeCallingOrSelfPermission(ContextFixture.PERMISSION_ENABLE_ALL);
-        try {
-            mPhoneSubInfoControllerUT.getMsisdnForSubscriber(0, TAG);
-            Assert.fail("expected Security Exception Thrown");
-        } catch (Exception ex) {
-            assertTrue(ex instanceof SecurityException);
-            assertTrue(ex.getMessage().contains("getMsisdn"));
-        }
-
-        try {
-            mPhoneSubInfoControllerUT.getMsisdnForSubscriber(1, TAG);
-            Assert.fail("expected Security Exception Thrown");
-        } catch (Exception ex) {
-            assertTrue(ex instanceof SecurityException);
-            assertTrue(ex.getMessage().contains("getMsisdn"));
-        }
-
-        //case 2: no READ_PRIVILEGED_PHONE_STATE & appOsMgr READ_PHONE_PERMISSION
-        mContextFixture.addCallingOrSelfPermission(READ_PHONE_STATE);
         doReturn(AppOpsManager.MODE_ERRORED).when(mAppOsMgr).noteOp(
-                eq(AppOpsManager.OP_READ_PHONE_STATE), anyInt(), eq(TAG));
+                eq(AppOpsManager.OPSTR_READ_SMS), anyInt(), eq(TAG), eq(FEATURE_ID),
+                nullable(String.class));
+        doReturn(AppOpsManager.MODE_ERRORED).when(mAppOsMgr).noteOp(
+                eq(AppOpsManager.OPSTR_WRITE_SMS), anyInt(), eq(TAG), eq(FEATURE_ID),
+                nullable(String.class));
+        doReturn(AppOpsManager.MODE_ERRORED).when(mAppOsMgr).noteOp(
+                eq(AppOpsManager.OPSTR_READ_PHONE_STATE), anyInt(), eq(TAG), eq(FEATURE_ID),
+                nullable(String.class));
+        try {
+            mPhoneSubInfoControllerUT.getMsisdnForSubscriber(0, TAG, FEATURE_ID);
+            Assert.fail("expected Security Exception Thrown");
+        } catch (Exception ex) {
+            assertTrue(ex instanceof SecurityException);
+        }
 
-        assertNull(mPhoneSubInfoControllerUT.getMsisdnForSubscriber(0, TAG));
-        assertNull(mPhoneSubInfoControllerUT.getMsisdnForSubscriber(1, TAG));
+        try {
+            mPhoneSubInfoControllerUT.getMsisdnForSubscriber(1, TAG, FEATURE_ID);
+            Assert.fail("expected Security Exception Thrown");
+        } catch (Exception ex) {
+            assertTrue(ex instanceof SecurityException);
+        }
 
-        //case 3: no READ_PRIVILEGED_PHONE_STATE
+        /* case 2: only enable READ_PHONE_STATE permission */
         mContextFixture.addCallingOrSelfPermission(READ_PHONE_STATE);
+        assertNull(mPhoneSubInfoControllerUT.getMsisdnForSubscriber(0, TAG, FEATURE_ID));
+        assertNull(mPhoneSubInfoControllerUT.getMsisdnForSubscriber(1, TAG, FEATURE_ID));
+
+        /* case 3: enable appOsMgr READ_PHONE_PERMISSION & READ_PHONE_STATE */
         doReturn(AppOpsManager.MODE_ALLOWED).when(mAppOsMgr).noteOp(
-                eq(AppOpsManager.OP_READ_PHONE_STATE), anyInt(), eq(TAG));
-        assertEquals("+18051234567", mPhoneSubInfoControllerUT.getMsisdnForSubscriber(0, TAG));
-        assertEquals("+18052345678", mPhoneSubInfoControllerUT.getMsisdnForSubscriber(1, TAG));
+                eq(AppOpsManager.OPSTR_READ_PHONE_STATE), anyInt(), eq(TAG), eq(FEATURE_ID),
+                nullable(String.class));
+        assertEquals("+18051234567",
+                mPhoneSubInfoControllerUT.getMsisdnForSubscriber(0, TAG, FEATURE_ID));
+        assertEquals("+18052345678",
+                mPhoneSubInfoControllerUT.getMsisdnForSubscriber(1, TAG, FEATURE_ID));
+    }
+
+    @Test
+    @SmallTest
+    public void testGetMsisdnWithOutPermissionTargetR() {
+        mApplicationInfo.targetSdkVersion = Build.VERSION_CODES.R;
+        doReturn("+18051234567").when(mPhone).getMsisdn();
+        doReturn("+18052345678").when(mSecondPhone).getMsisdn();
+
+        /* case 1: no READ_PRIVILEGED_PHONE_STATE & READ_PHONE_STATE &
+        READ_SMS and no OP_WRITE_SMS & OP_READ_SMS from appOsMgr */
+        mContextFixture.removeCallingOrSelfPermission(ContextFixture.PERMISSION_ENABLE_ALL);
+        doReturn(AppOpsManager.MODE_ERRORED).when(mAppOsMgr).noteOp(
+                eq(AppOpsManager.OPSTR_READ_SMS), anyInt(), eq(TAG), eq(FEATURE_ID),
+                nullable(String.class));
+        doReturn(AppOpsManager.MODE_ERRORED).when(mAppOsMgr).noteOp(
+                eq(AppOpsManager.OPSTR_WRITE_SMS), anyInt(), eq(TAG), eq(FEATURE_ID),
+                nullable(String.class));
+        doReturn(AppOpsManager.MODE_ERRORED).when(mAppOsMgr).noteOp(
+                eq(AppOpsManager.OPSTR_READ_PHONE_STATE), anyInt(), eq(TAG), eq(FEATURE_ID),
+                nullable(String.class));
+        try {
+            mPhoneSubInfoControllerUT.getMsisdnForSubscriber(0, TAG, FEATURE_ID);
+            Assert.fail("expected Security Exception Thrown");
+        } catch (Exception ex) {
+            assertTrue(ex instanceof SecurityException);
+        }
+
+        try {
+            mPhoneSubInfoControllerUT.getMsisdnForSubscriber(1, TAG, FEATURE_ID);
+            Assert.fail("expected Security Exception Thrown");
+        } catch (Exception ex) {
+            assertTrue(ex instanceof SecurityException);
+        }
+
+        /* case 2: only enable READ_PHONE_STATE permission */
+        doReturn(AppOpsManager.MODE_ALLOWED).when(mAppOsMgr).noteOp(
+                eq(AppOpsManager.OPSTR_READ_PHONE_STATE), anyInt(), eq(TAG), eq(FEATURE_ID),
+                nullable(String.class));
+        try {
+            mPhoneSubInfoControllerUT.getMsisdnForSubscriber(0, TAG, FEATURE_ID);
+            Assert.fail("expected Security Exception Thrown");
+        } catch (Exception ex) {
+            assertTrue(ex instanceof SecurityException);
+        }
+
+        try {
+            mPhoneSubInfoControllerUT.getMsisdnForSubscriber(1, TAG, FEATURE_ID);
+            Assert.fail("expected Security Exception Thrown");
+        } catch (Exception ex) {
+            assertTrue(ex instanceof SecurityException);
+        }
+
+        /* case 3: enable READ_SMS and OP_READ_SMS */
+        doReturn(AppOpsManager.MODE_ERRORED).when(mAppOsMgr).noteOp(
+                eq(AppOpsManager.OPSTR_READ_PHONE_STATE), anyInt(), eq(TAG), eq(FEATURE_ID),
+                nullable(String.class));
+        mContextFixture.addCallingOrSelfPermission(READ_SMS);
+        doReturn(AppOpsManager.MODE_ALLOWED).when(mAppOsMgr).noteOp(
+                eq(AppOpsManager.OPSTR_READ_SMS), anyInt(), eq(TAG), eq(FEATURE_ID),
+                nullable(String.class));
+        assertEquals("+18051234567",
+                mPhoneSubInfoControllerUT.getMsisdnForSubscriber(0, TAG, FEATURE_ID));
+        assertEquals("+18052345678",
+                mPhoneSubInfoControllerUT.getMsisdnForSubscriber(1, TAG, FEATURE_ID));
     }
 
     @Test
@@ -696,11 +926,11 @@
     public void testGetVoiceMailNumber() {
         doReturn("+18051234567").when(mPhone).getVoiceMailNumber();
         assertEquals("+18051234567", mPhoneSubInfoControllerUT
-                .getVoiceMailNumberForSubscriber(0, TAG));
+                .getVoiceMailNumberForSubscriber(0, TAG, FEATURE_ID));
 
         doReturn("+18052345678").when(mSecondPhone).getVoiceMailNumber();
         assertEquals("+18052345678", mPhoneSubInfoControllerUT
-                .getVoiceMailNumberForSubscriber(1, TAG));
+                .getVoiceMailNumberForSubscriber(1, TAG, FEATURE_ID));
     }
 
     @Test
@@ -712,7 +942,7 @@
         //case 1: no READ_PRIVILEGED_PHONE_STATE, READ_PHONE_STATE & appOsMgr READ_PHONE_PERMISSION
         mContextFixture.removeCallingOrSelfPermission(ContextFixture.PERMISSION_ENABLE_ALL);
         try {
-            mPhoneSubInfoControllerUT.getVoiceMailNumberForSubscriber(0, TAG);
+            mPhoneSubInfoControllerUT.getVoiceMailNumberForSubscriber(0, TAG, FEATURE_ID);
             Assert.fail("expected Security Exception Thrown");
         } catch (Exception ex) {
             assertTrue(ex instanceof SecurityException);
@@ -720,7 +950,7 @@
         }
 
         try {
-            mPhoneSubInfoControllerUT.getVoiceMailNumberForSubscriber(1, TAG);
+            mPhoneSubInfoControllerUT.getVoiceMailNumberForSubscriber(1, TAG, FEATURE_ID);
             Assert.fail("expected Security Exception Thrown");
         } catch (Exception ex) {
             assertTrue(ex instanceof SecurityException);
@@ -730,19 +960,21 @@
         //case 2: no READ_PRIVILEGED_PHONE_STATE & appOsMgr READ_PHONE_PERMISSION
         mContextFixture.addCallingOrSelfPermission(READ_PHONE_STATE);
         doReturn(AppOpsManager.MODE_ERRORED).when(mAppOsMgr).noteOp(
-                eq(AppOpsManager.OP_READ_PHONE_STATE), anyInt(), eq(TAG));
+                eq(AppOpsManager.OPSTR_READ_PHONE_STATE), anyInt(), eq(TAG), eq(FEATURE_ID),
+                nullable(String.class));
 
-        assertNull(mPhoneSubInfoControllerUT.getVoiceMailNumberForSubscriber(0, TAG));
-        assertNull(mPhoneSubInfoControllerUT.getVoiceMailNumberForSubscriber(1, TAG));
+        assertNull(mPhoneSubInfoControllerUT.getVoiceMailNumberForSubscriber(0, TAG, FEATURE_ID));
+        assertNull(mPhoneSubInfoControllerUT.getVoiceMailNumberForSubscriber(1, TAG, FEATURE_ID));
 
         //case 3: no READ_PRIVILEGED_PHONE_STATE
         mContextFixture.addCallingOrSelfPermission(READ_PHONE_STATE);
         doReturn(AppOpsManager.MODE_ALLOWED).when(mAppOsMgr).noteOp(
-                eq(AppOpsManager.OP_READ_PHONE_STATE), anyInt(), eq(TAG));
+                eq(AppOpsManager.OPSTR_READ_PHONE_STATE), anyInt(), eq(TAG), eq(FEATURE_ID),
+                nullable(String.class));
         assertEquals("+18051234567", mPhoneSubInfoControllerUT
-                .getVoiceMailNumberForSubscriber(0, TAG));
+                .getVoiceMailNumberForSubscriber(0, TAG, FEATURE_ID));
         assertEquals("+18052345678", mPhoneSubInfoControllerUT
-                .getVoiceMailNumberForSubscriber(1, TAG));
+                .getVoiceMailNumberForSubscriber(1, TAG, FEATURE_ID));
     }
 
     @Test
@@ -750,11 +982,11 @@
     public void testGetVoiceMailAlphaTag() {
         doReturn("VM_SIM_0").when(mPhone).getVoiceMailAlphaTag();
         assertEquals("VM_SIM_0", mPhoneSubInfoControllerUT
-                .getVoiceMailAlphaTagForSubscriber(0, TAG));
+                .getVoiceMailAlphaTagForSubscriber(0, TAG, FEATURE_ID));
 
         doReturn("VM_SIM_1").when(mSecondPhone).getVoiceMailAlphaTag();
         assertEquals("VM_SIM_1", mPhoneSubInfoControllerUT
-                .getVoiceMailAlphaTagForSubscriber(1, TAG));
+                .getVoiceMailAlphaTagForSubscriber(1, TAG, FEATURE_ID));
     }
 
     @Test
@@ -766,7 +998,7 @@
         //case 1: no READ_PRIVILEGED_PHONE_STATE, READ_PHONE_STATE & appOsMgr READ_PHONE_PERMISSION
         mContextFixture.removeCallingOrSelfPermission(ContextFixture.PERMISSION_ENABLE_ALL);
         try {
-            mPhoneSubInfoControllerUT.getVoiceMailAlphaTagForSubscriber(0, TAG);
+            mPhoneSubInfoControllerUT.getVoiceMailAlphaTagForSubscriber(0, TAG, FEATURE_ID);
             Assert.fail("expected Security Exception Thrown");
         } catch (Exception ex) {
             assertTrue(ex instanceof SecurityException);
@@ -774,7 +1006,7 @@
         }
 
         try {
-            mPhoneSubInfoControllerUT.getVoiceMailAlphaTagForSubscriber(1, TAG);
+            mPhoneSubInfoControllerUT.getVoiceMailAlphaTagForSubscriber(1, TAG, FEATURE_ID);
             Assert.fail("expected Security Exception Thrown");
         } catch (Exception ex) {
             assertTrue(ex instanceof SecurityException);
@@ -784,18 +1016,20 @@
         //case 2: no READ_PRIVILEGED_PHONE_STATE & appOsMgr READ_PHONE_PERMISSION
         mContextFixture.addCallingOrSelfPermission(READ_PHONE_STATE);
         doReturn(AppOpsManager.MODE_ERRORED).when(mAppOsMgr).noteOp(
-                eq(AppOpsManager.OP_READ_PHONE_STATE), anyInt(), eq(TAG));
+                eq(AppOpsManager.OPSTR_READ_PHONE_STATE), anyInt(), eq(TAG), eq(FEATURE_ID),
+                nullable(String.class));
 
-        assertNull(mPhoneSubInfoControllerUT.getVoiceMailAlphaTagForSubscriber(0, TAG));
-        assertNull(mPhoneSubInfoControllerUT.getVoiceMailAlphaTagForSubscriber(1, TAG));
+        assertNull(mPhoneSubInfoControllerUT.getVoiceMailAlphaTagForSubscriber(0, TAG, FEATURE_ID));
+        assertNull(mPhoneSubInfoControllerUT.getVoiceMailAlphaTagForSubscriber(1, TAG, FEATURE_ID));
 
         //case 3: no READ_PRIVILEGED_PHONE_STATE
         mContextFixture.addCallingOrSelfPermission(READ_PHONE_STATE);
         doReturn(AppOpsManager.MODE_ALLOWED).when(mAppOsMgr).noteOp(
-                eq(AppOpsManager.OP_READ_PHONE_STATE), anyInt(), eq(TAG));
+                eq(AppOpsManager.OPSTR_READ_PHONE_STATE), anyInt(), eq(TAG), eq(FEATURE_ID),
+                nullable(String.class));
         assertEquals("VM_SIM_0", mPhoneSubInfoControllerUT
-                .getVoiceMailAlphaTagForSubscriber(0, TAG));
+                .getVoiceMailAlphaTagForSubscriber(0, TAG, FEATURE_ID));
         assertEquals("VM_SIM_1", mPhoneSubInfoControllerUT
-                .getVoiceMailAlphaTagForSubscriber(1, TAG));
+                .getVoiceMailAlphaTagForSubscriber(1, TAG, FEATURE_ID));
     }
 }
diff --git a/tests/telephonytests/src/com/android/internal/telephony/PhoneSwitcherTest.java b/tests/telephonytests/src/com/android/internal/telephony/PhoneSwitcherTest.java
index 5c7f9a8..e13baca 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/PhoneSwitcherTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/PhoneSwitcherTest.java
@@ -16,11 +16,14 @@
 
 package com.android.internal.telephony;
 
+import static android.telephony.CarrierConfigManager.KEY_DATA_SWITCH_VALIDATION_TIMEOUT_LONG;
 import static android.telephony.TelephonyManager.SET_OPPORTUNISTIC_SUB_INACTIVE_SUBSCRIPTION;
 import static android.telephony.TelephonyManager.SET_OPPORTUNISTIC_SUB_SUCCESS;
 import static android.telephony.TelephonyManager.SET_OPPORTUNISTIC_SUB_VALIDATION_FAILED;
 
+import static com.android.internal.telephony.PhoneSwitcher.ECBM_DEFAULT_DATA_SWITCH_BASE_TIME_MS;
 import static com.android.internal.telephony.PhoneSwitcher.EVENT_DATA_ENABLED_CHANGED;
+import static com.android.internal.telephony.PhoneSwitcher.EVENT_MULTI_SIM_CONFIG_CHANGED;
 import static com.android.internal.telephony.PhoneSwitcher.EVENT_PRECISE_CALL_STATE_CHANGED;
 
 import static org.junit.Assert.assertEquals;
@@ -36,7 +39,6 @@
 import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.timeout;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 
@@ -44,18 +46,21 @@
 import android.content.Intent;
 import android.net.ConnectivityManager;
 import android.net.NetworkCapabilities;
+import android.net.NetworkProvider;
 import android.net.NetworkRequest;
-import android.net.StringNetworkSpecifier;
+import android.net.TelephonyNetworkSpecifier;
 import android.os.AsyncResult;
 import android.os.Handler;
-import android.os.HandlerThread;
+import android.os.Looper;
 import android.os.Message;
 import android.os.Messenger;
 import android.telephony.PhoneCapability;
 import android.telephony.SubscriptionManager;
 import android.test.suitebuilder.annotation.SmallTest;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
 
-import androidx.test.runner.AndroidJUnit4;
+import com.android.internal.telephony.dataconnection.DataEnabledSettings;
 
 import org.junit.After;
 import org.junit.Before;
@@ -65,32 +70,22 @@
 import org.mockito.Mock;
 
 import java.util.concurrent.CompletableFuture;
-import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.LinkedBlockingQueue;
-import java.util.concurrent.TimeUnit;
 
-@RunWith(AndroidJUnit4.class)
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper
 public class PhoneSwitcherTest extends TelephonyTest {
-    private static final String[] sNetworkAttributes = new String[] {
-            "mobile,0,0,0,-1,true", "mobile_mms,2,0,2,60000,true",
-            "mobile_supl,3,0,2,60000,true", "mobile_dun,4,0,2,60000,true",
-            "mobile_hipri,5,0,3,60000,true", "mobile_fota,10,0,2,60000,true",
-            "mobile_ims,11,0,2,60000,true", "mobile_cbs,12,0,2,60000,true",
-            "mobile_ia,14,0,2,-1,true", "mobile_emergency,15,0,2,-1,true"};
-
     private static final int ACTIVE_PHONE_SWITCH = 1;
 
     @Mock
-    private ITelephonyRegistry.Stub mTelRegistryMock;
-    @Mock
-    private ITelephonyRegistry mTelRegistryInterfaceMock;
-    @Mock
     private CommandsInterface mCommandsInterface0;
     @Mock
     private CommandsInterface mCommandsInterface1;
     @Mock
     private Phone mPhone2; // mPhone as phone 1 is already defined in TelephonyTest.
     @Mock
+    private DataEnabledSettings mDataEnabledSettings2;
+    @Mock
     private Handler mActivePhoneSwitchHandler;
     @Mock
     private GsmCdmaCall mActiveCall;
@@ -102,18 +97,20 @@
     private ISetOpportunisticDataCallback mSetOpptDataCallback1;
     @Mock
     private ISetOpportunisticDataCallback mSetOpptDataCallback2;
+    @Mock
+    CompletableFuture<Boolean> mFuturePhone;
 
-    // The thread that mPhoneSwitcher will handle events in.
-    private HandlerThread mHandlerThread;
     private PhoneSwitcher mPhoneSwitcher;
-    private IOnSubscriptionsChangedListener mSubChangedListener;
+    private SubscriptionManager.OnSubscriptionsChangedListener mSubChangedListener;
     private ConnectivityManager mConnectivityManager;
     // The messenger of PhoneSwitcher used to receive network requests.
-    private Messenger mNetworkFactoryMessenger = null;
+    private Messenger mNetworkProviderMessenger = null;
     private int mDefaultDataSub = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
-    private CommandsInterface[] mCommandsInterfaces;
     private int[][] mSlotIndexToSubId;
     private boolean[] mDataAllowed;
+    private int mActiveModemCount = 2;
+    private int mSupportedModemCount = 2;
+    private int mMaxDataAttachModemCount = 1;
 
     @Before
     public void setUp() throws Exception {
@@ -121,12 +118,13 @@
 
         PhoneCapability phoneCapability = new PhoneCapability(1, 1, 0, null, false);
         doReturn(phoneCapability).when(mPhoneConfigurationManager).getCurrentPhoneCapability();
-        mServiceManagerMockedServices.put("telephony.registry", mTelRegistryMock);
-        doReturn(mTelRegistryInterfaceMock).when(mTelRegistryMock).queryLocalInterface(any());
 
         doReturn(Call.State.ACTIVE).when(mActiveCall).getState();
         doReturn(Call.State.IDLE).when(mInactiveCall).getState();
         doReturn(Call.State.HOLDING).when(mHoldingCall).getState();
+
+        replaceInstance(Phone.class, "mCi", mPhone, mCommandsInterface0);
+        replaceInstance(Phone.class, "mCi", mPhone2, mCommandsInterface1);
     }
 
     @After
@@ -140,16 +138,13 @@
     @Test
     @SmallTest
     public void testRegister() throws Exception {
-        final int numPhones = 2;
-        final int maxActivePhones = 1;
-        initialize(numPhones, maxActivePhones);
+        initialize();
 
         // verify nothing has been done while there are no inputs
         assertFalse("data allowed initially", mDataAllowed[0]);
         assertFalse("data allowed initially", mDataAllowed[1]);
 
         NetworkRequest internetNetworkRequest = addInternetNetworkRequest(null, 50);
-        waitABit();
 
         assertFalse("phone active after request", mPhoneSwitcher
                 .shouldApplyNetworkRequest(internetNetworkRequest, 0));
@@ -169,7 +164,7 @@
 
         setSlotIndexToSubId(0, 0);
         mSubChangedListener.onSubscriptionsChanged();
-        waitABit();
+        processAllMessages();
 
         verify(mActivePhoneSwitchHandler, times(1)).sendMessageAtTime(any(), anyLong());
         clearInvocations(mActivePhoneSwitchHandler);
@@ -189,7 +184,6 @@
 
         // 1 lose default via default sub change
         setDefaultDataSubId(1);
-        waitABit();
 
         verify(mActivePhoneSwitchHandler, times(1)).sendMessageAtTime(any(), anyLong());
         clearInvocations(mActivePhoneSwitchHandler);
@@ -197,7 +191,7 @@
 
         setSlotIndexToSubId(1, 1);
         mSubChangedListener.onSubscriptionsChanged();
-        waitABit();
+        processAllMessages();
 
         verify(mActivePhoneSwitchHandler, times(1)).sendMessageAtTime(any(), anyLong());
         clearInvocations(mActivePhoneSwitchHandler);
@@ -206,7 +200,6 @@
 
         // 2 gain default via default sub change
         setDefaultDataSubId(0);
-        waitABit();
 
         verify(mActivePhoneSwitchHandler, times(1)).sendMessageAtTime(any(), anyLong());
         clearInvocations(mActivePhoneSwitchHandler);
@@ -216,7 +209,7 @@
         // 3 lose default via sub->phone change
         setSlotIndexToSubId(0, 2);
         mSubChangedListener.onSubscriptionsChanged();
-        waitABit();
+        processAllMessages();
 
         verify(mActivePhoneSwitchHandler, times(1)).sendMessageAtTime(any(), anyLong());
         clearInvocations(mActivePhoneSwitchHandler);
@@ -226,7 +219,7 @@
         // 4 gain default via sub->phone change
         setSlotIndexToSubId(0, 0);
         mSubChangedListener.onSubscriptionsChanged();
-        waitABit();
+        processAllMessages();
 
         verify(mActivePhoneSwitchHandler, times(1)).sendMessageAtTime(any(), anyLong());
         clearInvocations(mActivePhoneSwitchHandler);
@@ -235,7 +228,6 @@
 
         // 5 lose default network request
         releaseNetworkRequest(internetNetworkRequest);
-        waitABit();
 
         verify(mActivePhoneSwitchHandler, times(1)).sendMessageAtTime(any(), anyLong());
         clearInvocations(mActivePhoneSwitchHandler);
@@ -244,7 +236,6 @@
 
         // 6 gain subscription-specific request
         NetworkRequest specificInternetRequest = addInternetNetworkRequest(0, 50);
-        waitABit();
 
         verify(mActivePhoneSwitchHandler, times(1)).sendMessageAtTime(any(), anyLong());
         clearInvocations(mActivePhoneSwitchHandler);
@@ -254,7 +245,7 @@
         // 7 lose via sub->phone change
         setSlotIndexToSubId(0, 1);
         mSubChangedListener.onSubscriptionsChanged();
-        waitABit();
+        processAllMessages();
 
         verify(mActivePhoneSwitchHandler, times(1)).sendMessageAtTime(any(), anyLong());
         clearInvocations(mActivePhoneSwitchHandler);
@@ -264,7 +255,7 @@
         // 8 gain via sub->phone change
         setSlotIndexToSubId(0, 0);
         mSubChangedListener.onSubscriptionsChanged();
-        waitABit();
+        processAllMessages();
 
         verify(mActivePhoneSwitchHandler, times(1)).sendMessageAtTime(any(), anyLong());
         clearInvocations(mActivePhoneSwitchHandler);
@@ -273,7 +264,6 @@
 
         // 9 lose subscription-specific request
         releaseNetworkRequest(specificInternetRequest);
-        waitABit();
 
         verify(mActivePhoneSwitchHandler, times(1)).sendMessageAtTime(any(), anyLong());
         clearInvocations(mActivePhoneSwitchHandler);
@@ -284,7 +274,7 @@
         // not ready yet - Phone turns out to be hard to stub out
 //        phones[0].setInEmergencyCall(true);
 //        connectivityServiceMock.addDefaultRequest();
-//        waitABit();
+//        processAllMessages();
 //        if (testHandler.getActivePhoneSwitchCount() != 11) {
 //            fail("after release of request, ActivePhoneSwitchCount not 11!");
 //        }
@@ -293,14 +283,12 @@
 //
 //        phones[0].setInEmergencyCall(false);
 //        connectivityServiceMock.addDefaultRequest();
-//        waitABit();
+//        processAllMessages();
 //        if (testHandler.getActivePhoneSwitchCount() != 12) {
 //            fail("after release of request, ActivePhoneSwitchCount not 11!");
 //        }
 //        if (commandsInterfaces[0].isDataAllowed()) fail("data allowed");
 //        if (commandsInterfaces[1].isDataAllowed()) fail("data allowed");
-
-        mHandlerThread.quit();
     }
 
     /**
@@ -322,74 +310,59 @@
     @Test
     @SmallTest
     public void testPrioritization() throws Exception {
-        final int numPhones = 2;
-        final int maxActivePhones = 1;
-        initialize(numPhones, maxActivePhones);
+        initialize();
 
         addInternetNetworkRequest(null, 50);
         setSlotIndexToSubId(0, 0);
         setSlotIndexToSubId(1, 1);
         setDefaultDataSubId(0);
-        waitABit();
         mPhoneSwitcher.registerForActivePhoneSwitch(mActivePhoneSwitchHandler,
                 ACTIVE_PHONE_SWITCH, null);
-        waitABit();
+        processAllMessages();
         // verify initial conditions
         verify(mActivePhoneSwitchHandler, times(1)).sendMessageAtTime(any(), anyLong());
 
         assertTrue("data not allowed", mDataAllowed[0]);
         assertFalse("data allowed", mDataAllowed[1]);
 
-        // now start a higher priority conneciton on the other sub
+        // now start a higher priority connection on the other sub
         addMmsNetworkRequest(1);
-        waitABit();
 
         // After gain of network request, mActivePhoneSwitchHandler should be notified 2 times.
         verify(mActivePhoneSwitchHandler, times(2)).sendMessageAtTime(any(), anyLong());
         assertFalse("data allowed", mDataAllowed[0]);
         assertTrue("data not allowed", mDataAllowed[1]);
-
-        mHandlerThread.quit();
     }
 
     /**
-     * Verify we don't send spurious DATA_ALLOWED calls when another NetworkFactory
+     * Verify we don't send spurious DATA_ALLOWED calls when another NetworkProvider
      * wins (ie, switch to wifi).
      */
     @Test
     @SmallTest
     public void testHigherPriorityDefault() throws Exception {
-        final int numPhones = 2;
-        final int maxActivePhones = 1;
-        initialize(numPhones, maxActivePhones);
+        initialize();
 
         addInternetNetworkRequest(null, 50);
-        waitABit();
 
         setSlotIndexToSubId(0, 0);
         setSlotIndexToSubId(1, 1);
         setDefaultDataSubId(0);
-        waitABit();
 
         // Phone 0 should be active
         assertTrue("data not allowed", mDataAllowed[0]);
         assertFalse("data allowed", mDataAllowed[1]);
 
         addInternetNetworkRequest(null, 100);
-        waitABit();
 
         // should be no change
         assertTrue("data not allowed", mDataAllowed[0]);
         assertFalse("data allowed", mDataAllowed[1]);
 
         addInternetNetworkRequest(null, 0);
-        waitABit();
-
         // should be no change
         assertTrue("data not allowed", mDataAllowed[0]);
         assertFalse("data allowed", mDataAllowed[1]);
-
-        mHandlerThread.quit();
     }
 
     /**
@@ -401,9 +374,7 @@
     @Test
     @SmallTest
     public void testSetPreferredData() throws Exception {
-        final int numPhones = 2;
-        final int maxActivePhones = 1;
-        initialize(numPhones, maxActivePhones);
+        initialize();
 
         // Phone 0 has sub 1, phone 1 has sub 2.
         // Sub 1 is default data sub.
@@ -412,35 +383,36 @@
         setSlotIndexToSubId(1, 2);
         setDefaultDataSubId(1);
 
+        doReturn(true).when(mSubscriptionController).isOpportunistic(2);
+
         // Notify phoneSwitcher about default data sub and default network request.
         addInternetNetworkRequest(null, 50);
-        waitABit();
         // Phone 0 (sub 1) should be activated as it has default data sub.
         assertTrue(mDataAllowed[0]);
 
         // Set sub 2 as preferred sub should make phone 1 activated and phone 0 deactivated.
         mPhoneSwitcher.trySetOpportunisticDataSubscription(2, false, null);
-        waitABit();
+        processAllMessages();
+        mPhoneSwitcher.mValidationCallback.onNetworkAvailable(null, 2);
+        processAllMessages();
         assertFalse(mDataAllowed[0]);
         assertTrue(mDataAllowed[1]);
 
         // Unset preferred sub should make default data sub (phone 0 / sub 1) activated again.
         mPhoneSwitcher.trySetOpportunisticDataSubscription(
                 SubscriptionManager.DEFAULT_SUBSCRIPTION_ID, false, null);
-        waitABit();
+        processAllMessages();
+        mPhoneSwitcher.mValidationCallback.onNetworkAvailable(null, 1);
+        processAllMessages();
         assertTrue(mDataAllowed[0]);
         assertFalse(mDataAllowed[1]);
-
-        mHandlerThread.quit();
     }
 
     @Test
     @SmallTest
     public void testSetPreferredDataModemCommand() throws Exception {
-        final int numPhones = 2;
-        final int maxActivePhones = 1;
         doReturn(true).when(mMockRadioConfig).isSetPreferredDataCommandSupported();
-        initialize(numPhones, maxActivePhones);
+        initialize();
         mPhoneSwitcher.registerForActivePhoneSwitch(mActivePhoneSwitchHandler,
                 ACTIVE_PHONE_SWITCH, null);
         mPhoneSwitcher.registerForActivePhoneSwitch(mActivePhoneSwitchHandler,
@@ -455,7 +427,6 @@
         setSlotIndexToSubId(0, 1);
         setSlotIndexToSubId(1, 2);
         setDefaultDataSubId(1);
-        waitABit();
         // Phone 0 (sub 1) should be preferred data phone as it has default data sub.
         verify(mMockRadioConfig).setPreferredDataModem(eq(0), any());
         verify(mActivePhoneSwitchHandler, times(2)).sendMessageAtTime(any(), anyLong());
@@ -467,7 +438,6 @@
         // It shouldn't change anything.
         NetworkRequest internetRequest = addInternetNetworkRequest(null, 50);
         NetworkRequest mmsRequest = addMmsNetworkRequest(2);
-        waitABit();
         verify(mMockRadioConfig, never()).setPreferredDataModem(anyInt(), any());
         verify(mActivePhoneSwitchHandler, never()).sendMessageAtTime(any(), anyLong());
         assertTrue(mPhoneSwitcher.shouldApplyNetworkRequest(internetRequest, 0));
@@ -476,8 +446,11 @@
         assertTrue(mPhoneSwitcher.shouldApplyNetworkRequest(mmsRequest, 1));
 
         // Set sub 2 as preferred sub should make phone 1 preferredDataModem
+        doReturn(true).when(mSubscriptionController).isOpportunistic(2);
         mPhoneSwitcher.trySetOpportunisticDataSubscription(2, false, null);
-        waitABit();
+        processAllMessages();
+        mPhoneSwitcher.mValidationCallback.onNetworkAvailable(null, 2);
+        processAllMessages();
         verify(mMockRadioConfig).setPreferredDataModem(eq(1), any());
         verify(mActivePhoneSwitchHandler, times(2)).sendMessageAtTime(any(), anyLong());
         assertFalse(mPhoneSwitcher.shouldApplyNetworkRequest(internetRequest, 0));
@@ -491,7 +464,10 @@
         // Unset preferred sub should make phone0 preferredDataModem again.
         mPhoneSwitcher.trySetOpportunisticDataSubscription(
                 SubscriptionManager.DEFAULT_SUBSCRIPTION_ID, false, null);
-        waitABit();
+        processAllMessages();
+        mPhoneSwitcher.mValidationCallback.onNetworkAvailable(null, 1);
+        processAllMessages();
+
         verify(mMockRadioConfig).setPreferredDataModem(eq(0), any());
         verify(mActivePhoneSwitchHandler, times(2)).sendMessageAtTime(any(), anyLong());
         assertTrue(mPhoneSwitcher.shouldApplyNetworkRequest(internetRequest, 0));
@@ -502,77 +478,69 @@
         // SetDataAllowed should never be triggered.
         verify(mCommandsInterface0, never()).setDataAllowed(anyBoolean(), any());
         verify(mCommandsInterface1, never()).setDataAllowed(anyBoolean(), any());
-
-        mHandlerThread.quit();
-
     }
 
     @Test
     @SmallTest
     public void testSetPreferredDataWithValidation() throws Exception {
-        final int numPhones = 2;
-        final int maxActivePhones = 1;
         doReturn(true).when(mMockRadioConfig).isSetPreferredDataCommandSupported();
-        initialize(numPhones, maxActivePhones);
+        initialize();
 
+        // Mark sub 2 as opportunistic.
+        doReturn(true).when(mSubscriptionController).isOpportunistic(2);
         // Phone 0 has sub 1, phone 1 has sub 2.
         // Sub 1 is default data sub.
         // Both are active subscriptions are active sub, as they are in both active slots.
         setSlotIndexToSubId(0, 1);
         setSlotIndexToSubId(1, 2);
         setDefaultDataSubId(1);
-        // Mark sub 2 as opportunistic.
-        doReturn(true).when(mSubscriptionController).isOpportunistic(2);
 
         // Phone 0 (sub 1) should be activated as it has default data sub.
         assertEquals(0, mPhoneSwitcher.getPreferredDataPhoneId());
 
         // Set sub 2 as preferred sub should make phone 1 activated and phone 0 deactivated.
         mPhoneSwitcher.trySetOpportunisticDataSubscription(2, true, null);
-        waitABit();
-        verify(mCellularNetworkValidator).validate(eq(2), anyInt(), eq(false),
+        processAllMessages();
+        verify(mCellularNetworkValidator).validate(eq(2), anyLong(), eq(false),
                 eq(mPhoneSwitcher.mValidationCallback));
         // Validation failed. Preferred data sub should remain 1, data phone should remain 0.
-        mPhoneSwitcher.mValidationCallback.onValidationResult(false, 2);
-        waitABit();
+        mPhoneSwitcher.mValidationCallback.onValidationDone(false, 2);
+        processAllMessages();
         assertEquals(0, mPhoneSwitcher.getPreferredDataPhoneId());
 
         // Validation succeeds. Preferred data sub changes to 2, data phone changes to 1.
         mPhoneSwitcher.trySetOpportunisticDataSubscription(2, true, null);
-        waitABit();
-        mPhoneSwitcher.mValidationCallback.onValidationResult(true, 2);
-        waitABit();
+        processAllMessages();
+        mPhoneSwitcher.mValidationCallback.onValidationDone(true, 2);
+        processAllMessages();
         assertEquals(1, mPhoneSwitcher.getPreferredDataPhoneId());
 
-        // Switching data back to primary (subId 1).
+        // Switching data back to primary (subId 1) with customized validation timeout.
+        long timeout = 1234;
+        mContextFixture.getCarrierConfigBundle().putLong(
+                KEY_DATA_SWITCH_VALIDATION_TIMEOUT_LONG, timeout);
         mPhoneSwitcher.trySetOpportunisticDataSubscription(
                 SubscriptionManager.DEFAULT_SUBSCRIPTION_ID, true, null);
-        waitABit();
-        verify(mCellularNetworkValidator).validate(eq(1), anyInt(), eq(false),
+        processAllMessages();
+        verify(mCellularNetworkValidator).validate(eq(1), eq(timeout), eq(false),
                 eq(mPhoneSwitcher.mValidationCallback));
-        mPhoneSwitcher.mValidationCallback.onValidationResult(true, 1);
-        waitABit();
+        mPhoneSwitcher.mValidationCallback.onValidationDone(true, 1);
+        processAllMessages();
         assertEquals(0, mPhoneSwitcher.getPreferredDataPhoneId());
-
-        mHandlerThread.quit();
     }
 
     @Test
     @SmallTest
     public void testNonDefaultDataPhoneInCall() throws Exception {
-        final int numPhones = 2;
-        final int maxActivePhones = 1;
         doReturn(true).when(mMockRadioConfig).isSetPreferredDataCommandSupported();
-        initialize(numPhones, maxActivePhones);
+        initialize();
         // Phone 0 has sub 1, phone 1 has sub 2.
         // Sub 1 is default data sub.
         // Both are active subscriptions are active sub, as they are in both active slots.
         setSlotIndexToSubId(0, 1);
         setSlotIndexToSubId(1, 2);
         setDefaultDataSubId(1);
-        waitABit();
         NetworkRequest internetRequest = addInternetNetworkRequest(null, 50);
-        waitABit();
         assertTrue(mPhoneSwitcher.shouldApplyNetworkRequest(internetRequest, 0));
         assertFalse(mPhoneSwitcher.shouldApplyNetworkRequest(internetRequest, 1));
         clearInvocations(mMockRadioConfig);
@@ -605,164 +573,123 @@
         verify(mMockRadioConfig).setPreferredDataModem(eq(1), any());
         assertTrue(mPhoneSwitcher.shouldApplyNetworkRequest(internetRequest, 1));
         assertFalse(mPhoneSwitcher.shouldApplyNetworkRequest(internetRequest, 0));
-
-        mHandlerThread.quit();
     }
 
 
     @Test
     @SmallTest
     public void testNetworkRequestOnNonDefaultData() throws Exception {
-        final int numPhones = 2;
-        final int maxActivePhones = 1;
         doReturn(true).when(mMockRadioConfig).isSetPreferredDataCommandSupported();
-        initialize(numPhones, maxActivePhones);
+        initialize();
         // Phone 0 has sub 1, phone 1 has sub 2.
         // Sub 1 is default data sub.
         // Both are active subscriptions are active sub, as they are in both active slots.
         setSlotIndexToSubId(0, 1);
         setSlotIndexToSubId(1, 2);
         setDefaultDataSubId(1);
-        waitABit();
         NetworkRequest internetRequest = addInternetNetworkRequest(2, 50);
-        waitABit();
         assertFalse(mPhoneSwitcher.shouldApplyNetworkRequest(internetRequest, 0));
         assertFalse(mPhoneSwitcher.shouldApplyNetworkRequest(internetRequest, 1));
 
         // Restricted network request will should be applied.
         internetRequest = addInternetNetworkRequest(2, 50, true);
-        waitABit();
         assertFalse(mPhoneSwitcher.shouldApplyNetworkRequest(internetRequest, 0));
         assertTrue(mPhoneSwitcher.shouldApplyNetworkRequest(internetRequest, 1));
-
-        mHandlerThread.quit();
     }
 
     @Test
     @SmallTest
     public void testEmergencyOverrideSuccessBeforeCallStarts() throws Exception {
-        final int numPhones = 2;
-        final int maxActivePhones = 1;
         doReturn(true).when(mMockRadioConfig).isSetPreferredDataCommandSupported();
-        initialize(numPhones, maxActivePhones);
+        initialize();
         // Phone 0 has sub 1, phone 1 has sub 2.
         // Sub 1 is default data sub.
         // Both are active subscriptions are active sub, as they are in both active slots.
-        setMsimDefaultDataSubId(numPhones, 1);
+        setMsimDefaultDataSubId(1);
         clearInvocations(mMockRadioConfig);
 
         // override the phone ID in prep for emergency call
-        CountDownLatch latch = new CountDownLatch(1);
-        CompletableFuture<Boolean> futurePhone = new CompletableFuture<>();
-        futurePhone.whenComplete((result, error) -> {
-            assertTrue(result);
-            latch.countDown();
-        });
-        mPhoneSwitcher.overrideDefaultDataForEmergency(1, 1, futurePhone);
+        mPhoneSwitcher.overrideDefaultDataForEmergency(1, 1, mFuturePhone);
         sendPreferredDataSuccessResult(1);
-        assertTrue(latch.await(2, TimeUnit.SECONDS));
+        processAllMessages();
+        verify(mFuturePhone).complete(true);
         // Make sure the correct broadcast is sent out for the overridden phone ID
-        verify(mTelRegistryInterfaceMock).notifyActiveDataSubIdChanged(eq(2));
-
-        mHandlerThread.quit();
+        verify(mTelephonyRegistryManager).notifyActiveDataSubIdChanged(eq(2));
     }
 
     @Test
     @SmallTest
     public void testEmergencyOverrideNoDdsChange() throws Exception {
-        final int numPhones = 2;
-        final int maxActivePhones = 1;
         doReturn(true).when(mMockRadioConfig).isSetPreferredDataCommandSupported();
-        initialize(numPhones, maxActivePhones);
+        initialize();
         // Phone 0 has sub 1, phone 1 has sub 2.
         // Sub 1 is default data sub.
         // Both are active subscriptions are active sub, as they are in both active slots.
-        setMsimDefaultDataSubId(numPhones, 1);
+        setMsimDefaultDataSubId(1);
         clearInvocations(mMockRadioConfig);
 
         // override the phone ID in prep for emergency call
-        CountDownLatch latch = new CountDownLatch(1);
-        CompletableFuture<Boolean> futurePhone = new CompletableFuture<>();
-        futurePhone.whenComplete((result, error) -> {
-            assertTrue(result);
-            latch.countDown();
-        });
-        mPhoneSwitcher.overrideDefaultDataForEmergency(0, 1, futurePhone);
-        waitABit();
+        mPhoneSwitcher.overrideDefaultDataForEmergency(0, 1, mFuturePhone);
+        processAllMessages();
         // The radio command should never be called because the DDS hasn't changed.
         verify(mMockRadioConfig, never()).setPreferredDataModem(eq(0), any());
-        assertTrue(latch.await(2, TimeUnit.SECONDS));
-
-        mHandlerThread.quit();
+        processAllMessages();
+        verify(mFuturePhone).complete(true);
     }
 
     @Test
     @SmallTest
     public void testEmergencyOverrideEndSuccess() throws Exception {
         PhoneSwitcher.ECBM_DEFAULT_DATA_SWITCH_BASE_TIME_MS = 500;
-        final int numPhones = 2;
-        final int maxActivePhones = 1;
         doReturn(true).when(mMockRadioConfig).isSetPreferredDataCommandSupported();
-        initialize(numPhones, maxActivePhones);
+        initialize();
         // Phone 0 has sub 1, phone 1 has sub 2.
         // Sub 1 is default data sub.
         // Both are active subscriptions are active sub, as they are in both active slots.
-        setMsimDefaultDataSubId(numPhones, 1);
+        setMsimDefaultDataSubId(1);
         setAllPhonesInactive();
         clearInvocations(mMockRadioConfig);
-        clearInvocations(mTelRegistryInterfaceMock);
+        clearInvocations(mTelephonyRegistryManager);
 
         // override the phone ID in prep for emergency call
-        CountDownLatch latch = new CountDownLatch(1);
-        CompletableFuture<Boolean> futurePhone = new CompletableFuture<>();
-        futurePhone.whenComplete((result, error) -> {
-            assertTrue(result);
-            latch.countDown();
-        });
-        mPhoneSwitcher.overrideDefaultDataForEmergency(1, 1, futurePhone);
+
+        mPhoneSwitcher.overrideDefaultDataForEmergency(1, 1, mFuturePhone);
         sendPreferredDataSuccessResult(1);
-        assertTrue(latch.await(1, TimeUnit.SECONDS));
+        processAllMessages();
+        verify(mFuturePhone).complete(true);
 
         // Start and end the emergency call, which will start override timer
         notifyPhoneAsInCall(mPhone2);
         notifyPhoneAsInactive(mPhone2);
 
+        clearInvocations(mTelephonyRegistryManager);
         // Verify that the DDS is successfully switched back after 1 second + base ECBM timeout
-        verify(mMockRadioConfig,
-                timeout(PhoneSwitcher.ECBM_DEFAULT_DATA_SWITCH_BASE_TIME_MS + 2000))
-                .setPreferredDataModem(eq(0), any());
+        moveTimeForward(ECBM_DEFAULT_DATA_SWITCH_BASE_TIME_MS + 1000);
+        processAllMessages();
+        verify(mMockRadioConfig).setPreferredDataModem(eq(0), any());
         // Make sure the correct broadcast is sent out for the phone ID
-        verify(mTelRegistryInterfaceMock).notifyActiveDataSubIdChanged(eq(1));
-
-        mHandlerThread.quit();
+        verify(mTelephonyRegistryManager).notifyActiveDataSubIdChanged(eq(1));
     }
 
     @Test
     @SmallTest
     public void testEmergencyOverrideEcbmStartEnd() throws Exception {
         PhoneSwitcher.ECBM_DEFAULT_DATA_SWITCH_BASE_TIME_MS = 500;
-        final int numPhones = 2;
-        final int maxActivePhones = 1;
         doReturn(true).when(mMockRadioConfig).isSetPreferredDataCommandSupported();
-        initialize(numPhones, maxActivePhones);
+        initialize();
         // Phone 0 has sub 1, phone 1 has sub 2.
         // Sub 1 is default data sub.
         // Both are active subscriptions are active sub, as they are in both active slots.
-        setMsimDefaultDataSubId(numPhones, 1);
+        setMsimDefaultDataSubId(1);
         setAllPhonesInactive();
         clearInvocations(mMockRadioConfig);
-        clearInvocations(mTelRegistryInterfaceMock);
+        clearInvocations(mTelephonyRegistryManager);
 
         // override the phone ID in prep for emergency call
-        CountDownLatch latch = new CountDownLatch(1);
-        CompletableFuture<Boolean> futurePhone = new CompletableFuture<>();
-        futurePhone.whenComplete((result, error) -> {
-            assertTrue(result);
-            latch.countDown();
-        });
-        mPhoneSwitcher.overrideDefaultDataForEmergency(1, 1, futurePhone);
+        mPhoneSwitcher.overrideDefaultDataForEmergency(1, 1, mFuturePhone);
         sendPreferredDataSuccessResult(1);
-        assertTrue(latch.await(1, TimeUnit.SECONDS));
+        processAllMessages();
+        verify(mFuturePhone).complete(true);
 
         // Start and end the emergency call, which will start override timer
         notifyPhoneAsInCall(mPhone2);
@@ -773,76 +700,65 @@
 
         // DDS should not be switched back until ECBM ends, make sure there is no further
         // interaction.
-        Thread.sleep(PhoneSwitcher.ECBM_DEFAULT_DATA_SWITCH_BASE_TIME_MS + 2000);
+        moveTimeForward(ECBM_DEFAULT_DATA_SWITCH_BASE_TIME_MS + 2000);
+        processAllMessages();
         verify(mMockRadioConfig, never()).setPreferredDataModem(eq(0), any());
         // Make sure the correct broadcast is sent out for the phone ID
-        verify(mTelRegistryInterfaceMock).notifyActiveDataSubIdChanged(eq(2));
+        verify(mTelephonyRegistryManager).notifyActiveDataSubIdChanged(eq(2));
 
         // End ECBM
+        clearInvocations(mTelephonyRegistryManager);
         ecbmMessage = getEcbmRegistration(mPhone2);
         notifyEcbmEnd(mPhone2, ecbmMessage);
         // Verify that the DDS is successfully switched back after 1 second.
-        verify(mMockRadioConfig, timeout(2000)).setPreferredDataModem(eq(0), any());
+        moveTimeForward(1000);
+        processAllMessages();
+        verify(mMockRadioConfig).setPreferredDataModem(eq(0), any());
         // Make sure the correct broadcast is sent out for the phone ID
-        verify(mTelRegistryInterfaceMock).notifyActiveDataSubIdChanged(eq(1));
-
-
-        mHandlerThread.quit();
+        verify(mTelephonyRegistryManager).notifyActiveDataSubIdChanged(eq(1));
     }
 
     @Test
     @SmallTest
     public void testEmergencyOverrideNoCallStart() throws Exception {
         PhoneSwitcher.DEFAULT_DATA_OVERRIDE_TIMEOUT_MS = 500;
-        final int numPhones = 2;
-        final int maxActivePhones = 1;
         doReturn(true).when(mMockRadioConfig).isSetPreferredDataCommandSupported();
-        initialize(numPhones, maxActivePhones);
+        initialize();
         // Phone 0 has sub 1, phone 1 has sub 2.
         // Sub 1 is default data sub.
         // Both are active subscriptions are active sub, as they are in both active slots.
-        setMsimDefaultDataSubId(numPhones, 1);
+        setMsimDefaultDataSubId(1);
         setAllPhonesInactive();
         clearInvocations(mMockRadioConfig);
-        clearInvocations(mTelRegistryInterfaceMock);
+        clearInvocations(mTelephonyRegistryManager);
 
         // override the phone ID in prep for emergency call
-        CountDownLatch latch = new CountDownLatch(1);
-        CompletableFuture<Boolean> futurePhone = new CompletableFuture<>();
-        futurePhone.whenComplete((result, error) -> {
-            assertTrue(result);
-            latch.countDown();
-        });
-        mPhoneSwitcher.overrideDefaultDataForEmergency(1, 1, futurePhone);
+        mPhoneSwitcher.overrideDefaultDataForEmergency(1, 1, mFuturePhone);
         sendPreferredDataSuccessResult(1);
-        assertTrue(latch.await(1, TimeUnit.SECONDS));
+        processAllMessages();
+        verify(mFuturePhone).complete(true);
 
         // Do not start the call and make sure the override is removed once the timeout expires
-        verify(mMockRadioConfig,
-                timeout(PhoneSwitcher.DEFAULT_DATA_OVERRIDE_TIMEOUT_MS + 1000))
-                .setPreferredDataModem(eq(0), any());
+        moveTimeForward(PhoneSwitcher.DEFAULT_DATA_OVERRIDE_TIMEOUT_MS);
+        processAllMessages();
+        verify(mMockRadioConfig).setPreferredDataModem(eq(0), any());
         // Make sure the correct broadcast is sent out for the phone ID
-        verify(mTelRegistryInterfaceMock).notifyActiveDataSubIdChanged(eq(1));
-
-
-        mHandlerThread.quit();
+        verify(mTelephonyRegistryManager).notifyActiveDataSubIdChanged(eq(1));
     }
 
     @Test
     @SmallTest
     public void testEmergencyOverrideMultipleOverrideRequests() throws Exception {
         PhoneSwitcher.ECBM_DEFAULT_DATA_SWITCH_BASE_TIME_MS = 500;
-        final int numPhones = 2;
-        final int maxActivePhones = 1;
         doReturn(true).when(mMockRadioConfig).isSetPreferredDataCommandSupported();
-        initialize(numPhones, maxActivePhones);
+        initialize();
         // Phone 0 has sub 1, phone 1 has sub 2.
         // Sub 1 is default data sub.
         // Both are active subscriptions are active sub, as they are in both active slots.
-        setMsimDefaultDataSubId(numPhones, 1);
+        setMsimDefaultDataSubId(1);
         setAllPhonesInactive();
         clearInvocations(mMockRadioConfig);
-        clearInvocations(mTelRegistryInterfaceMock);
+        clearInvocations(mTelephonyRegistryManager);
 
         // override the phone ID in prep for emergency call
         LinkedBlockingQueue<Boolean> queue = new LinkedBlockingQueue<>();
@@ -850,7 +766,8 @@
         futurePhone.whenComplete((r, error) -> queue.offer(r));
         mPhoneSwitcher.overrideDefaultDataForEmergency(1, 1, futurePhone);
         sendPreferredDataSuccessResult(1);
-        Boolean result = queue.poll(1, TimeUnit.SECONDS);
+        processAllMessages();
+        Boolean result = queue.poll();
         assertNotNull(result);
         assertTrue(result);
 
@@ -858,7 +775,8 @@
         futurePhone = new CompletableFuture<>();
         futurePhone.whenComplete((r, error) -> queue.offer(r));
         mPhoneSwitcher.overrideDefaultDataForEmergency(0, 1, futurePhone);
-        result = queue.poll(1, TimeUnit.SECONDS);
+        processAllMessages();
+        result = queue.poll();
         assertNotNull(result);
         assertFalse(result);
         verify(mMockRadioConfig, never()).setPreferredDataModem(eq(0), any());
@@ -868,23 +786,18 @@
         notifyPhoneAsInactive(mPhone2);
 
         // Verify that the DDS is successfully switched back after 1 second + base ECBM timeout
-        verify(mMockRadioConfig,
-                timeout(PhoneSwitcher.ECBM_DEFAULT_DATA_SWITCH_BASE_TIME_MS + 2000))
-                .setPreferredDataModem(eq(0), any());
+        moveTimeForward(ECBM_DEFAULT_DATA_SWITCH_BASE_TIME_MS + 1000);
+        processAllMessages();
+        verify(mMockRadioConfig).setPreferredDataModem(eq(0), any());
         // Make sure the correct broadcast is sent out for the phone ID
-        verify(mTelRegistryInterfaceMock).notifyActiveDataSubIdChanged(eq(1));
-
-
-        mHandlerThread.quit();
+        verify(mTelephonyRegistryManager).notifyActiveDataSubIdChanged(eq(1));
     }
 
     @Test
     @SmallTest
     public void testSetPreferredDataCallback() throws Exception {
-        final int numPhones = 2;
-        final int maxActivePhones = 1;
         doReturn(true).when(mMockRadioConfig).isSetPreferredDataCommandSupported();
-        initialize(numPhones, maxActivePhones);
+        initialize();
 
         // Mark sub 2 as opportunistic.
         doReturn(true).when(mSubscriptionController).isOpportunistic(2);
@@ -894,89 +807,193 @@
         setSlotIndexToSubId(0, 1);
         setSlotIndexToSubId(1, 2);
         setDefaultDataSubId(1);
-        waitABit();
 
         // Validating on sub 10 which is inactive.
         mPhoneSwitcher.trySetOpportunisticDataSubscription(10, true, mSetOpptDataCallback1);
-        waitABit();
+        processAllMessages();
         verify(mSetOpptDataCallback1).onComplete(SET_OPPORTUNISTIC_SUB_INACTIVE_SUBSCRIPTION);
 
         // Switch to active subId without validating. Should always succeed.
         mPhoneSwitcher.trySetOpportunisticDataSubscription(2, false, mSetOpptDataCallback1);
-        waitABit();
+        processAllMessages();
+        mPhoneSwitcher.mValidationCallback.onNetworkAvailable(null, 2);
+        processAllMessages();
         verify(mSetOpptDataCallback1).onComplete(SET_OPPORTUNISTIC_SUB_SUCCESS);
 
         // Validating on sub 1 and fails.
         clearInvocations(mSetOpptDataCallback1);
         mPhoneSwitcher.trySetOpportunisticDataSubscription(1, true, mSetOpptDataCallback1);
-        waitABit();
-        mPhoneSwitcher.mValidationCallback.onValidationResult(false, 1);
-        waitABit();
+        processAllMessages();
+        mPhoneSwitcher.mValidationCallback.onValidationDone(false, 1);
+        processAllMessages();
         verify(mSetOpptDataCallback1).onComplete(SET_OPPORTUNISTIC_SUB_VALIDATION_FAILED);
 
         // Validating on sub 2 and succeeds.
         mPhoneSwitcher.trySetOpportunisticDataSubscription(2, true, mSetOpptDataCallback2);
-        waitABit();
-        mPhoneSwitcher.mValidationCallback.onValidationResult(true, 2);
-        waitABit();
+        processAllMessages();
+        mPhoneSwitcher.mValidationCallback.onValidationDone(true, 2);
+        processAllMessages();
         verify(mSetOpptDataCallback2).onComplete(SET_OPPORTUNISTIC_SUB_SUCCESS);
 
         // Switching data back to primary and validation fails.
         clearInvocations(mSetOpptDataCallback2);
         mPhoneSwitcher.trySetOpportunisticDataSubscription(
                 SubscriptionManager.DEFAULT_SUBSCRIPTION_ID, true, mSetOpptDataCallback2);
-        waitABit();
-        mPhoneSwitcher.mValidationCallback.onValidationResult(false, 1);
-        waitABit();
+        processAllMessages();
+        mPhoneSwitcher.mValidationCallback.onValidationDone(false, 1);
+        processAllMessages();
         verify(mSetOpptDataCallback1).onComplete(SET_OPPORTUNISTIC_SUB_VALIDATION_FAILED);
 
         // Switching data back to primary and succeeds.
         clearInvocations(mSetOpptDataCallback2);
         mPhoneSwitcher.trySetOpportunisticDataSubscription(
                 SubscriptionManager.DEFAULT_SUBSCRIPTION_ID, true, mSetOpptDataCallback2);
-        waitABit();
-        mPhoneSwitcher.mValidationCallback.onValidationResult(true, 1);
-        waitABit();
+        processAllMessages();
+        mPhoneSwitcher.mValidationCallback.onValidationDone(true, 1);
+        processAllMessages();
         verify(mSetOpptDataCallback2).onComplete(SET_OPPORTUNISTIC_SUB_SUCCESS);
 
         // Back to back call on same subId.
         clearInvocations(mSetOpptDataCallback1);
         clearInvocations(mSetOpptDataCallback2);
+        clearInvocations(mCellularNetworkValidator);
         mPhoneSwitcher.trySetOpportunisticDataSubscription(2, true, mSetOpptDataCallback1);
-        waitABit();
-        verify(mCellularNetworkValidator).validate(eq(2), anyInt(), eq(false),
+        processAllMessages();
+        verify(mCellularNetworkValidator).validate(eq(2), anyLong(), eq(false),
                 eq(mPhoneSwitcher.mValidationCallback));
         doReturn(true).when(mCellularNetworkValidator).isValidating();
         mPhoneSwitcher.trySetOpportunisticDataSubscription(2, true, mSetOpptDataCallback2);
-        waitABit();
+        processAllMessages();
         verify(mSetOpptDataCallback1).onComplete(SET_OPPORTUNISTIC_SUB_VALIDATION_FAILED);
         verify(mSetOpptDataCallback2, never()).onComplete(anyInt());
         // Validation succeeds.
         doReturn(false).when(mCellularNetworkValidator).isValidating();
-        mPhoneSwitcher.mValidationCallback.onValidationResult(true, 2);
-        waitABit();
+        mPhoneSwitcher.mValidationCallback.onValidationDone(true, 2);
+        processAllMessages();
         verify(mSetOpptDataCallback2).onComplete(SET_OPPORTUNISTIC_SUB_SUCCESS);
 
         mPhoneSwitcher.trySetOpportunisticDataSubscription(
                 SubscriptionManager.DEFAULT_SUBSCRIPTION_ID, false, null);
-        waitABit();
+        processAllMessages();
+        mPhoneSwitcher.mValidationCallback.onNetworkAvailable(null, 1);
+        processAllMessages();
         clearInvocations(mSetOpptDataCallback1);
         clearInvocations(mSetOpptDataCallback2);
         clearInvocations(mCellularNetworkValidator);
         // Back to back call, call 1 to switch to subId 2, call 2 to switch back.
         mPhoneSwitcher.trySetOpportunisticDataSubscription(2, true, mSetOpptDataCallback1);
-        waitABit();
-        verify(mCellularNetworkValidator).validate(eq(2), anyInt(), eq(false),
+        processAllMessages();
+        verify(mCellularNetworkValidator).validate(eq(2), anyLong(), eq(false),
                 eq(mPhoneSwitcher.mValidationCallback));
         doReturn(true).when(mCellularNetworkValidator).isValidating();
         mPhoneSwitcher.trySetOpportunisticDataSubscription(
                 SubscriptionManager.DEFAULT_SUBSCRIPTION_ID, true, mSetOpptDataCallback2);
-        waitABit();
+        processAllMessages();
         // Call 1 should be cancelled and failed. Call 2 return success immediately as there's no
         // change.
         verify(mSetOpptDataCallback1).onComplete(SET_OPPORTUNISTIC_SUB_VALIDATION_FAILED);
         verify(mSetOpptDataCallback2).onComplete(SET_OPPORTUNISTIC_SUB_SUCCESS);
-        mHandlerThread.quit();
+    }
+
+    @Test
+    @SmallTest
+    public void testMultiSimConfigChange() throws Exception {
+        doReturn(true).when(mMockRadioConfig).isSetPreferredDataCommandSupported();
+        mActiveModemCount = 1;
+        initialize();
+        sendPreferredDataSuccessResult(0);
+
+        // Phone 0 has sub 1, phone 1 has sub 2.
+        // Sub 1 is default data sub.
+        // Both are active subscriptions are active sub, as they are in both active slots.
+        setSlotIndexToSubId(0, 1);
+        setDefaultDataSubId(1);
+
+        setNumPhones(2, 2);
+        AsyncResult result = new AsyncResult(null, 2, null);
+        Message.obtain(mPhoneSwitcher, EVENT_MULTI_SIM_CONFIG_CHANGED, result).sendToTarget();
+        processAllMessages();
+
+        verify(mPhone2).registerForEmergencyCallToggle(any(), anyInt(), any());
+        verify(mPhone2).registerForPreciseCallStateChanged(any(), anyInt(), any());
+        verify(mDataEnabledSettings2).registerForDataEnabledChanged(any(), anyInt(), any());
+
+        clearInvocations(mMockRadioConfig);
+        setSlotIndexToSubId(1, 2);
+        setDefaultDataSubId(2);
+        verify(mMockRadioConfig).setPreferredDataModem(eq(1), any());
+    }
+
+    @Test
+    @SmallTest
+    public void testValidationOffSwitch_shouldSwitchOnNetworkAvailable() throws Exception {
+        doReturn(true).when(mMockRadioConfig).isSetPreferredDataCommandSupported();
+        initialize();
+        // Phone 0 has sub 1, phone 1 has sub 2.
+        // Sub 1 is default data sub.
+        // Both are active subscriptions are active sub, as they are in both active slots.
+        setSlotIndexToSubId(0, 1);
+        setSlotIndexToSubId(1, 2);
+        setDefaultDataSubId(1);
+        NetworkRequest internetRequest = addInternetNetworkRequest(null, 50);
+        assertTrue(mPhoneSwitcher.shouldApplyNetworkRequest(internetRequest, 0));
+        assertFalse(mPhoneSwitcher.shouldApplyNetworkRequest(internetRequest, 1));
+        clearInvocations(mMockRadioConfig);
+        setAllPhonesInactive();
+        // Initialization done.
+
+        doReturn(true).when(mSubscriptionController).isOpportunistic(2);
+        mPhoneSwitcher.trySetOpportunisticDataSubscription(2, false, mSetOpptDataCallback1);
+        processAllMessages();
+        verify(mCellularNetworkValidator).validate(eq(2), anyLong(), eq(false),
+                eq(mPhoneSwitcher.mValidationCallback));
+        doReturn(true).when(mCellularNetworkValidator).isValidating();
+
+        // Network available on different sub. Should do nothing.
+        mPhoneSwitcher.mValidationCallback.onNetworkAvailable(null, 1);
+        processAllMessages();
+        verify(mMockRadioConfig, never()).setPreferredDataModem(anyInt(), any());
+
+        // Network available on corresponding sub. Should confirm switch.
+        mPhoneSwitcher.mValidationCallback.onNetworkAvailable(null, 2);
+        processAllMessages();
+        verify(mMockRadioConfig).setPreferredDataModem(eq(1), any());
+    }
+
+    @Test
+    @SmallTest
+    public void testValidationOffSwitch_shouldSwitchOnTimeOut() throws Exception {
+        doReturn(true).when(mMockRadioConfig).isSetPreferredDataCommandSupported();
+        initialize();
+        // Phone 0 has sub 1, phone 1 has sub 2.
+        // Sub 1 is default data sub.
+        // Both are active subscriptions are active sub, as they are in both active slots.
+        setSlotIndexToSubId(0, 1);
+        setSlotIndexToSubId(1, 2);
+        setDefaultDataSubId(1);
+        NetworkRequest internetRequest = addInternetNetworkRequest(null, 50);
+        assertTrue(mPhoneSwitcher.shouldApplyNetworkRequest(internetRequest, 0));
+        assertFalse(mPhoneSwitcher.shouldApplyNetworkRequest(internetRequest, 1));
+        clearInvocations(mMockRadioConfig);
+        setAllPhonesInactive();
+        // Initialization done.
+
+        doReturn(true).when(mSubscriptionController).isOpportunistic(2);
+        mPhoneSwitcher.trySetOpportunisticDataSubscription(2, false, mSetOpptDataCallback1);
+        processAllMessages();
+        verify(mCellularNetworkValidator).validate(eq(2), anyLong(), eq(false),
+                eq(mPhoneSwitcher.mValidationCallback));
+        doReturn(true).when(mCellularNetworkValidator).isValidating();
+
+        // Validation failed on different sub. Should do nothing.
+        mPhoneSwitcher.mValidationCallback.onValidationDone(false, 1);
+        processAllMessages();
+        verify(mMockRadioConfig, never()).setPreferredDataModem(anyInt(), any());
+
+        // Network available on corresponding sub. Should confirm switch.
+        mPhoneSwitcher.mValidationCallback.onValidationDone(false, 2);
+        processAllMessages();
+        verify(mMockRadioConfig).setPreferredDataModem(eq(1), any());
     }
 
     /* Private utility methods start here */
@@ -993,25 +1010,26 @@
     private void notifyPhoneAsInCall(Phone phone) {
         doReturn(mActiveCall).when(phone).getForegroundCall();
         mPhoneSwitcher.sendEmptyMessage(EVENT_PRECISE_CALL_STATE_CHANGED);
-        waitABit();
+        processAllMessages();
     }
 
     private void notifyPhoneAsInHoldingCall(Phone phone) {
         doReturn(mHoldingCall).when(phone).getBackgroundCall();
         mPhoneSwitcher.sendEmptyMessage(EVENT_PRECISE_CALL_STATE_CHANGED);
-        waitABit();
+        processAllMessages();
     }
 
     private void notifyPhoneAsInactive(Phone phone) {
         doReturn(mInactiveCall).when(phone).getForegroundCall();
         mPhoneSwitcher.sendEmptyMessage(EVENT_PRECISE_CALL_STATE_CHANGED);
-        waitABit();
+        processAllMessages();
     }
 
     private void notifyDataEnabled(boolean dataEnabled) {
         doReturn(dataEnabled).when(mDataEnabledSettings).isDataEnabled(anyInt());
+        doReturn(dataEnabled).when(mDataEnabledSettings2).isDataEnabled(anyInt());
         mPhoneSwitcher.sendEmptyMessage(EVENT_DATA_ENABLED_CHANGED);
-        waitABit();
+        processAllMessages();
     }
 
     private Message getEcbmRegistration(Phone phone) {
@@ -1030,36 +1048,34 @@
         doReturn(mInactiveCall).when(phone).getForegroundCall();
         doReturn(true).when(phone).isInEcm();
         ecmMessage.sendToTarget();
-        waitABit();
+        processAllMessages();
     }
 
     private void notifyEcbmEnd(Phone phone, Message ecmMessage) {
         doReturn(false).when(phone).isInEcm();
         ecmMessage.sendToTarget();
-        waitABit();
+        processAllMessages();
     }
 
     private void sendPreferredDataSuccessResult(int phoneId) {
         // make sure the radio command is called and then send a success result
+        processAllMessages();
         ArgumentCaptor<Message> msgCaptor = ArgumentCaptor.forClass(Message.class);
-        verify(mMockRadioConfig, timeout(500)).setPreferredDataModem(eq(phoneId),
-                msgCaptor.capture());
+        verify(mMockRadioConfig).setPreferredDataModem(eq(phoneId), msgCaptor.capture());
         assertNotNull(msgCaptor.getValue());
         // Send back successful result
         AsyncResult.forMessage(msgCaptor.getValue(), null, null);
         msgCaptor.getValue().sendToTarget();
-        waitABit();
+        processAllMessages();
     }
 
-    private void setMsimDefaultDataSubId(int numPhones, int defaultDataSub) throws Exception {
-        for (int i = 0; i < numPhones; i++) {
+    private void setMsimDefaultDataSubId(int defaultDataSub) throws Exception {
+        for (int i = 0; i < mActiveModemCount; i++) {
             setSlotIndexToSubId(i, i + 1);
         }
         setDefaultDataSubId(defaultDataSub);
-        waitABit();
         NetworkRequest internetRequest = addInternetNetworkRequest(null, 50);
-        waitABit();
-        for (int i = 0; i < numPhones; i++) {
+        for (int i = 0; i < mActiveModemCount; i++) {
             if (defaultDataSub == (i + 1)) {
                 // sub id is always phoneId+1 for testing
                 assertTrue(mPhoneSwitcher.shouldApplyNetworkRequest(internetRequest, i));
@@ -1072,62 +1088,52 @@
     private void sendDefaultDataSubChanged() {
         final Intent intent = new Intent(TelephonyIntents.ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED);
         mContext.sendBroadcast(intent);
+        processAllMessages();
     }
 
-    private void initialize(int numPhones, int maxActivePhones) throws Exception {
-        mContextFixture.putStringArrayResource(com.android.internal.R.array.networkAttributes,
-                sNetworkAttributes);
-
-        setNumPhones(numPhones);
+    private void initialize() throws Exception {
+        setNumPhones(mActiveModemCount, mSupportedModemCount);
 
         initializeSubControllerMock();
-        initializeCommandInterfacesMock(numPhones);
+        initializeCommandInterfacesMock();
         initializeTelRegistryMock();
         initializeConnManagerMock();
 
-        mHandlerThread = new HandlerThread("PhoneSwitcherTestThread") {
-            @Override
-            public void onLooperPrepared() {
-                mPhoneSwitcher = new PhoneSwitcher(maxActivePhones, numPhones,
-                        mContext, mSubscriptionController, this.getLooper(),
-                        mTelRegistryMock, mCommandsInterfaces, mPhones);
-            }
-        };
+        mPhoneSwitcher = new PhoneSwitcher(mMaxDataAttachModemCount, mContext, Looper.myLooper());
+        processAllMessages();
 
-        mHandlerThread.start();
-        waitABit();
-
-        verify(mTelRegistryMock).addOnSubscriptionsChangedListener(
-                eq(mContext.getOpPackageName()), any());
+        verify(mTelephonyRegistryManager).addOnSubscriptionsChangedListener(any(), any());
     }
 
     /**
      * Certain variables needs initialized depending on number of phones.
      */
-    private void setNumPhones(int numPhones) {
-        mDataAllowed = new boolean[numPhones];
-        mSlotIndexToSubId = new int[numPhones][];
+    private void setNumPhones(int activeModemCount, int supportedModemCount) throws Exception {
+        mDataAllowed = new boolean[supportedModemCount];
+        mSlotIndexToSubId = new int[supportedModemCount][];
         doReturn(0).when(mPhone).getPhoneId();
         doReturn(1).when(mPhone2).getPhoneId();
         doReturn(true).when(mPhone2).isUserDataEnabled();
-        doReturn(mDataEnabledSettings).when(mPhone2).getDataEnabledSettings();
-        for (int i = 0; i < numPhones; i++) {
+        doReturn(mDataEnabledSettings2).when(mPhone2).getDataEnabledSettings();
+        for (int i = 0; i < supportedModemCount; i++) {
             mSlotIndexToSubId[i] = new int[1];
             mSlotIndexToSubId[i][0] = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
         }
 
-        doReturn(numPhones).when(mTelephonyManager).getPhoneCount();
-        if (numPhones == 1) {
-            mCommandsInterfaces = new CommandsInterface[] {mCommandsInterface0};
-            mPhones = new Phone[] {mPhone};
-        } else if (numPhones == 2) {
-            mCommandsInterfaces =
-                    new CommandsInterface[] {mCommandsInterface0, mCommandsInterface1};
-            mPhones = new Phone[] {mPhone, mPhone2};
+        doReturn(activeModemCount).when(mTelephonyManager).getPhoneCount();
+        doReturn(activeModemCount).when(mTelephonyManager).getActiveModemCount();
+        doReturn(supportedModemCount).when(mTelephonyManager).getSupportedModemCount();
+
+        if (activeModemCount == 1) {
+            mPhones = new Phone[]{mPhone};
+        } else if (activeModemCount == 2) {
+            mPhones = new Phone[]{mPhone, mPhone2};
         }
+
+        replaceInstance(PhoneFactory.class, "sPhones", null, mPhones);
     }
 
-    private void initializeCommandInterfacesMock(int numPhones) {
+    private void initializeCommandInterfacesMock() {
         // Tell PhoneSwitcher that radio is on.
         doAnswer(invocation -> {
             Handler handler = (Handler) invocation.getArguments()[0];
@@ -1143,7 +1149,7 @@
             return null;
         }).when(mCommandsInterface0).setDataAllowed(anyBoolean(), any());
 
-        if (numPhones == 2) {
+        if (mSupportedModemCount > 1) {
             doAnswer(invocation -> {
                 mDataAllowed[1] = (boolean) invocation.getArguments()[0];
                 return null;
@@ -1157,16 +1163,16 @@
      */
     private void initializeTelRegistryMock() throws Exception {
         doAnswer(invocation -> {
-            IOnSubscriptionsChangedListener subChangedListener =
-                    (IOnSubscriptionsChangedListener) invocation.getArguments()[1];
+            SubscriptionManager.OnSubscriptionsChangedListener subChangedListener =
+                    (SubscriptionManager.OnSubscriptionsChangedListener) invocation.getArguments()[0];
             mSubChangedListener = subChangedListener;
             mSubChangedListener.onSubscriptionsChanged();
             return null;
-        }).when(mTelRegistryMock).addOnSubscriptionsChangedListener(any(), any());
+        }).when(mTelephonyRegistryManager).addOnSubscriptionsChangedListener(any(), any());
     }
 
     /**
-     * Capture mNetworkFactoryMessenger so that testing can request or release
+     * Capture mNetworkProviderMessenger so that testing can request or release
      * network requests on PhoneSwitcher.
      */
     private void initializeConnManagerMock() {
@@ -1174,13 +1180,14 @@
                 mContext.getSystemService(Context.CONNECTIVITY_SERVICE);
 
         doAnswer(invocation -> {
-            mNetworkFactoryMessenger = invocation.getArgument(0);
+            mNetworkProviderMessenger =
+                    ((NetworkProvider) invocation.getArgument(0)).getMessenger();
             return null;
-        }).when(mConnectivityManager).registerNetworkFactory(any(), any());
+        }).when(mConnectivityManager).registerNetworkProvider(any());
     }
 
     /**
-     * Capture mNetworkFactoryMessenger so that testing can request or release
+     * Capture mNetworkProviderMessenger so that testing can request or release
      * network requests on PhoneSwitcher.
      */
     private void initializeSubControllerMock() {
@@ -1235,16 +1242,18 @@
         }
 
         if (subId != null) {
-            netCap.setNetworkSpecifier(new StringNetworkSpecifier(Integer.toString(subId)));
+            netCap.setNetworkSpecifier(new TelephonyNetworkSpecifier.Builder()
+                    .setSubscriptionId(subId).build());
         }
         NetworkRequest networkRequest = new NetworkRequest(netCap, ConnectivityManager.TYPE_NONE,
                 0, NetworkRequest.Type.REQUEST);
 
         Message message = Message.obtain();
-        message.what = android.net.NetworkFactory.CMD_REQUEST_NETWORK;
+        message.what = android.net.NetworkProvider.CMD_REQUEST_NETWORK;
         message.arg1 = score;
         message.obj = networkRequest;
-        mNetworkFactoryMessenger.send(message);
+        mNetworkProviderMessenger.send(message);
+        processAllMessages();
 
         return networkRequest;
     }
@@ -1257,18 +1266,19 @@
                 .addCapability(NetworkCapabilities.NET_CAPABILITY_MMS)
                 .addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED)
                 .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR);
-        netCap.setNetworkSpecifier(new StringNetworkSpecifier(Integer.toString(subId)));
         if (subId != null) {
-            netCap.setNetworkSpecifier(new StringNetworkSpecifier(Integer.toString(subId)));
+            netCap.setNetworkSpecifier(new TelephonyNetworkSpecifier.Builder()
+                    .setSubscriptionId(subId).build());
         }
         NetworkRequest networkRequest = new NetworkRequest(netCap, ConnectivityManager.TYPE_NONE,
                 1, NetworkRequest.Type.REQUEST);
 
         Message message = Message.obtain();
-        message.what = android.net.NetworkFactory.CMD_REQUEST_NETWORK;
+        message.what = android.net.NetworkProvider.CMD_REQUEST_NETWORK;
         message.arg1 = 50; // Score
         message.obj = networkRequest;
-        mNetworkFactoryMessenger.send(message);
+        mNetworkProviderMessenger.send(message);
+        processAllMessages();
 
         return networkRequest;
     }
@@ -1278,15 +1288,9 @@
      */
     private void releaseNetworkRequest(NetworkRequest networkRequest) throws Exception {
         Message message = Message.obtain();
-        message.what = android.net.NetworkFactory.CMD_CANCEL_REQUEST;
+        message.what = android.net.NetworkProvider.CMD_CANCEL_REQUEST;
         message.obj = networkRequest;
-        mNetworkFactoryMessenger.send(message);
-    }
-
-    private void waitABit() {
-        try {
-            Thread.sleep(250);
-        } catch (Exception e) {
-        }
+        mNetworkProviderMessenger.send(message);
+        processAllMessages();
     }
 }
diff --git a/tests/telephonytests/src/com/android/internal/telephony/ProxyControllerTest.java b/tests/telephonytests/src/com/android/internal/telephony/ProxyControllerTest.java
new file mode 100644
index 0000000..128e6d8
--- /dev/null
+++ b/tests/telephonytests/src/com/android/internal/telephony/ProxyControllerTest.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2019 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;
+
+import static com.android.internal.telephony.ProxyController.EVENT_MULTI_SIM_CONFIG_CHANGED;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.verify;
+
+import android.os.Handler;
+import android.os.Message;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper
+public class ProxyControllerTest extends TelephonyTest {
+    @Mock
+    Phone mPhone2;
+
+    ProxyController mProxyController;
+
+    @Before
+    public void setUp() throws Exception {
+        super.setUp(getClass().getSimpleName());
+        replaceInstance(ProxyController.class, "sProxyController", null, null);
+        mProxyController = ProxyController.getInstance(mContext);
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        // Restore system properties.
+        super.tearDown();
+    }
+
+    @Test
+    @SmallTest
+    public void testMultiSimConfigChange() throws Exception {
+        ArgumentCaptor<Handler> handlerCaptor = ArgumentCaptor.forClass(Handler.class);
+        ArgumentCaptor<Integer> intCaptor = ArgumentCaptor.forClass(int.class);
+
+        // Switch to dual-SIM and send multi sim config change callback.
+        replaceInstance(PhoneFactory.class, "sPhones", null, new Phone[] {mPhone, mPhone2});
+        Message.obtain(mProxyController.mHandler, EVENT_MULTI_SIM_CONFIG_CHANGED).sendToTarget();
+        processAllMessages();
+        verify(mPhone2).registerForRadioCapabilityChanged(any(), anyInt(), any());
+
+        // Switch to single-SIM and verify there's at least no crash.
+        replaceInstance(PhoneFactory.class, "sPhones", null, new Phone[] {mPhone});
+        Message.obtain(mProxyController.mHandler, EVENT_MULTI_SIM_CONFIG_CHANGED).sendToTarget();
+        processAllMessages();
+    }
+}
diff --git a/tests/telephonytests/src/com/android/internal/telephony/RILTest.java b/tests/telephonytests/src/com/android/internal/telephony/RILTest.java
index e08d2f1..792e525 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/RILTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/RILTest.java
@@ -26,6 +26,7 @@
 import static com.android.internal.telephony.RILConstants.RIL_REQUEST_DELETE_SMS_ON_SIM;
 import static com.android.internal.telephony.RILConstants.RIL_REQUEST_DEVICE_IDENTITY;
 import static com.android.internal.telephony.RILConstants.RIL_REQUEST_DTMF;
+import static com.android.internal.telephony.RILConstants.RIL_REQUEST_ENABLE_UICC_APPLICATIONS;
 import static com.android.internal.telephony.RILConstants.RIL_REQUEST_ENTER_NETWORK_DEPERSONALIZATION;
 import static com.android.internal.telephony.RILConstants.RIL_REQUEST_ENTER_SIM_PIN;
 import static com.android.internal.telephony.RILConstants.RIL_REQUEST_ENTER_SIM_PIN2;
@@ -33,6 +34,7 @@
 import static com.android.internal.telephony.RILConstants.RIL_REQUEST_ENTER_SIM_PUK2;
 import static com.android.internal.telephony.RILConstants.RIL_REQUEST_EXIT_EMERGENCY_CALLBACK_MODE;
 import static com.android.internal.telephony.RILConstants.RIL_REQUEST_GET_ACTIVITY_INFO;
+import static com.android.internal.telephony.RILConstants.RIL_REQUEST_GET_BARRING_INFO;
 import static com.android.internal.telephony.RILConstants.RIL_REQUEST_GET_CELL_INFO_LIST;
 import static com.android.internal.telephony.RILConstants.RIL_REQUEST_GET_CURRENT_CALLS;
 import static com.android.internal.telephony.RILConstants.RIL_REQUEST_GET_HARDWARE_CONFIG;
@@ -40,6 +42,7 @@
 import static com.android.internal.telephony.RILConstants.RIL_REQUEST_GET_RADIO_CAPABILITY;
 import static com.android.internal.telephony.RILConstants.RIL_REQUEST_GET_SIM_STATUS;
 import static com.android.internal.telephony.RILConstants.RIL_REQUEST_GET_SMSC_ADDRESS;
+import static com.android.internal.telephony.RILConstants.RIL_REQUEST_GET_UICC_APPLICATIONS_ENABLEMENT;
 import static com.android.internal.telephony.RILConstants.RIL_REQUEST_HANGUP;
 import static com.android.internal.telephony.RILConstants.RIL_REQUEST_HANGUP_FOREGROUND_RESUME_BACKGROUND;
 import static com.android.internal.telephony.RILConstants.RIL_REQUEST_HANGUP_WAITING_OR_BACKGROUND;
@@ -69,6 +72,7 @@
 import static com.android.internal.telephony.RILConstants.RIL_REQUEST_SIM_CLOSE_CHANNEL;
 import static com.android.internal.telephony.RILConstants.RIL_REQUEST_SIM_OPEN_CHANNEL;
 import static com.android.internal.telephony.RILConstants.RIL_REQUEST_START_LCE;
+import static com.android.internal.telephony.RILConstants.RIL_REQUEST_START_NETWORK_SCAN;
 import static com.android.internal.telephony.RILConstants.RIL_REQUEST_STK_HANDLE_CALL_SETUP_REQUESTED_FROM_SIM;
 import static com.android.internal.telephony.RILConstants.RIL_REQUEST_STOP_LCE;
 import static com.android.internal.telephony.RILConstants.RIL_REQUEST_SWITCH_WAITING_OR_HOLDING_AND_ACTIVE;
@@ -80,13 +84,17 @@
 import static junit.framework.Assert.assertEquals;
 import static junit.framework.Assert.assertFalse;
 import static junit.framework.Assert.assertNotNull;
+import static junit.framework.Assert.assertNull;
 import static junit.framework.Assert.assertTrue;
 
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.Matchers.any;
 import static org.mockito.Matchers.eq;
 import static org.mockito.Mockito.atLeast;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
@@ -97,20 +105,21 @@
 import android.hardware.radio.V1_0.CdmaSmsMessage;
 import android.hardware.radio.V1_0.DataProfileInfo;
 import android.hardware.radio.V1_0.GsmSmsMessage;
-import android.hardware.radio.V1_0.IRadio;
 import android.hardware.radio.V1_0.ImsSmsMessage;
 import android.hardware.radio.V1_0.NvWriteItem;
 import android.hardware.radio.V1_0.RadioError;
 import android.hardware.radio.V1_0.RadioResponseInfo;
 import android.hardware.radio.V1_0.RadioResponseType;
 import android.hardware.radio.V1_0.SmsWriteArgs;
+import android.hardware.radio.V1_5.IRadio;
 import android.hardware.radio.deprecated.V1_0.IOemHook;
 import android.net.ConnectivityManager;
+import android.net.InetAddresses;
 import android.net.LinkAddress;
-import android.net.NetworkUtils;
 import android.os.Handler;
-import android.os.HandlerThread;
 import android.os.IPowerManager;
+import android.os.IThermalService;
+import android.os.Looper;
 import android.os.Message;
 import android.os.PowerManager;
 import android.os.WorkSource;
@@ -119,12 +128,14 @@
 import android.telephony.CellIdentityCdma;
 import android.telephony.CellIdentityGsm;
 import android.telephony.CellIdentityLte;
+import android.telephony.CellIdentityNr;
 import android.telephony.CellIdentityTdscdma;
 import android.telephony.CellIdentityWcdma;
 import android.telephony.CellInfo;
 import android.telephony.CellInfoCdma;
 import android.telephony.CellInfoGsm;
 import android.telephony.CellInfoLte;
+import android.telephony.CellInfoNr;
 import android.telephony.CellInfoTdscdma;
 import android.telephony.CellInfoWcdma;
 import android.telephony.CellSignalStrengthCdma;
@@ -133,6 +144,8 @@
 import android.telephony.CellSignalStrengthNr;
 import android.telephony.CellSignalStrengthTdscdma;
 import android.telephony.CellSignalStrengthWcdma;
+import android.telephony.NetworkScanRequest;
+import android.telephony.RadioAccessSpecifier;
 import android.telephony.ServiceState;
 import android.telephony.SignalStrength;
 import android.telephony.SmsManager;
@@ -140,22 +153,30 @@
 import android.telephony.data.ApnSetting;
 import android.telephony.data.DataCallResponse;
 import android.telephony.data.DataProfile;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
 
 import androidx.test.filters.FlakyTest;
 
-import com.android.internal.telephony.RIL.RilHandler;
 import com.android.internal.telephony.dataconnection.DcTracker;
 
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
+import org.junit.runner.RunWith;
 import org.mockito.ArgumentCaptor;
 import org.mockito.Mock;
 
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashSet;
 import java.util.List;
+import java.util.Set;
+import java.util.function.Consumer;
 
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper
 public class RILTest extends TelephonyTest {
 
     // refer to RIL#DEFAULT_BLOCKING_MESSAGE_RESPONSE_TIMEOUT_MS
@@ -164,22 +185,24 @@
     // refer to RIL#DEFAULT_WAKE_LOCK_TIMEOUT_MS
     private static final int DEFAULT_WAKE_LOCK_TIMEOUT_MS = 60000;
 
-    // timer for CountDownLatch timeout
-    private static final int WAIT_TIMER = 10; //
-
     @Mock
     private ConnectivityManager mConnectionManager;
     @Mock
+    private TelephonyManager mTelephonyManager;
+    @Mock
     private IRadio mRadioProxy;
     @Mock
     private IOemHook mOemHookProxy;
 
-    private HalVersion mRadioVersion = new HalVersion(1, 0);
+    private HalVersion mRadioVersionV10 = new HalVersion(1, 0);
+    private HalVersion mRadioVersionV11 = new HalVersion(1, 1);
+    private HalVersion mRadioVersionV12 = new HalVersion(1, 2);
+    private HalVersion mRadioVersionV13 = new HalVersion(1, 3);
+    private HalVersion mRadioVersionV14 = new HalVersion(1, 4);
+    private HalVersion mRadioVersionV15 = new HalVersion(1, 5);
 
-    private RilHandler mRilHandler;
     private RIL mRILInstance;
     private RIL mRILUnderTest;
-    private RILTestHandler mTestHandler;
     ArgumentCaptor<Integer> mSerialNumberCaptor = ArgumentCaptor.forClass(Integer.class);
 
     // Constants
@@ -194,6 +217,7 @@
     private static final int CQI = 2147483647;
     private static final int DBM = -74;
     private static final int EARFCN = 262140;
+    private static final List<Integer> BANDS = Arrays.asList(1, 2);
     private static final int BANDWIDTH = 5000;
     private static final int ECIO = -124;
     private static final String EMPTY_ALPHA_LONG = "";
@@ -206,6 +230,7 @@
     private static final int MNC = 260;
     private static final String MNC_STR = "260";
     private static final int NETWORK_ID = 65534;
+    private static final int NRARFCN = 3279165;
     private static final int PCI = 503;
     private static final int PSC = 500;
     private static final int RIL_TIMESTAMP_TYPE_OEM_RIL = 3;
@@ -247,71 +272,54 @@
     private static final int MTU = 1234;
     private static final boolean PERSISTENT = true;
 
-    private class RILTestHandler extends HandlerThread {
+    private static final String[] ADDITIONAL_PLMNS = new String[] {"00101", "001001", "12345"};
 
-        RILTestHandler(String name) {
-            super(name);
-        }
-
-        @Override
-        protected void onLooperPrepared() {
-            super.onLooperPrepared();
-            createTelephonyDevController();
-            Context context = new ContextFixture().getTestDouble();
-            doReturn(true).when(mConnectionManager)
-                    .isNetworkSupported(ConnectivityManager.TYPE_MOBILE);
-            doReturn(mConnectionManager).when(context)
-                    .getSystemService(Context.CONNECTIVITY_SERVICE);
-            PowerManager powerManager = createPowerManager(context);
-            doReturn(powerManager).when(context).getSystemService(Context.POWER_SERVICE);
-            doReturn(new ApplicationInfo()).when(context).getApplicationInfo();
-
-            mRILInstance = new RIL(context, RILConstants.PREFERRED_NETWORK_MODE,
-                    Phone.PREFERRED_CDMA_SUBSCRIPTION, 0);
-            mRILUnderTest = spy(mRILInstance);
-            doReturn(mRadioProxy).when(mRILUnderTest).getRadioProxy(any());
-            doReturn(mOemHookProxy).when(mRILUnderTest).getOemHookProxy(any());
-
-            try {
-                replaceInstance(RIL.class, "mRadioVersion", mRILUnderTest, mRadioVersion);
-            } catch (Exception e) {
-            }
-
-            mRilHandler = mRILUnderTest.getRilHandler();
-
-            setReady(true);
-        }
-
-        private PowerManager createPowerManager(Context context) {
-            return new PowerManager(context, mock(IPowerManager.class), new Handler(getLooper()));
-        }
-
-        private void createTelephonyDevController() {
-            try {
-                TelephonyDevController.create();
-            } catch (RuntimeException e) {
-            }
-        }
-    }
+    private static final boolean CSG_INDICATION = true;
+    private static final String HOME_NODEB_NAME = "Android Network";
+    private static final int CSG_IDENTITY = 0xC0FFEE;
 
     @Before
     public void setUp() throws Exception {
         super.setUp(RILTest.class.getSimpleName());
-        mTestHandler = new RILTestHandler(getClass().getSimpleName());
-        mTestHandler.start();
-        waitUntilReady();
+        try {
+            TelephonyDevController.create();
+        } catch (RuntimeException e) {
+        }
+        Context context = new ContextFixture().getTestDouble();
+        doReturn(true).when(mConnectionManager)
+            .isNetworkSupported(ConnectivityManager.TYPE_MOBILE);
+        doReturn(mConnectionManager).when(context)
+            .getSystemService(Context.CONNECTIVITY_SERVICE);
+        doReturn(mTelephonyManager).when(context)
+                .getSystemService(Context.TELEPHONY_SERVICE);
+        doReturn(true).when(mTelephonyManager).isDataCapable();
+        PowerManager powerManager = new PowerManager(context, mock(IPowerManager.class),
+                mock(IThermalService.class), new Handler(Looper.myLooper()));
+        doReturn(powerManager).when(context).getSystemService(Context.POWER_SERVICE);
+        doReturn(new ApplicationInfo()).when(context).getApplicationInfo();
+
+        mRILInstance = new RIL(context, RILConstants.PREFERRED_NETWORK_MODE,
+            Phone.PREFERRED_CDMA_SUBSCRIPTION, 0);
+        mRILUnderTest = spy(mRILInstance);
+        doReturn(mRadioProxy).when(mRILUnderTest).getRadioProxy(any());
+        doReturn(mOemHookProxy).when(mRILUnderTest).getOemHookProxy(any());
+
+        try {
+            replaceInstance(RIL.class, "mRadioVersion", mRILUnderTest, mRadioVersionV10);
+        } catch (Exception e) {
+        }
     }
 
     @After
     public void tearDown() throws Exception {
-        mTestHandler.quit();
-        mTestHandler.join();
+        mRILUnderTest.mWakeLock.release();
+        mRILUnderTest.mAckWakeLock.release();
         super.tearDown();
     }
 
     @Test
-    public void testRadioErrorWithWakelockTimeout() throws Exception {
-        RadioBugDetector detector = mRILInstance.getRadioBugDetector();
+    public void testRadioErrorWithWakelockTimeout() {
+        RadioBugDetector detector = mRILUnderTest.getRadioBugDetector();
         int wakelockTimeoutThreshold = detector.getWakelockTimeoutThreshold();
         for (int i = 0; i < wakelockTimeoutThreshold; i++) {
             invokeMethod(
@@ -320,8 +328,8 @@
                     new Class<?>[]{Integer.TYPE, Message.class, WorkSource.class},
                     new Object[]{RIL_REQUEST_GET_SIM_STATUS, obtainMessage(), new WorkSource()});
         }
-
-        waitForHandlerActionDelayed(mRilHandler, WAIT_TIMER, DEFAULT_WAKE_LOCK_TIMEOUT_MS);
+        moveTimeForward(DEFAULT_WAKE_LOCK_TIMEOUT_MS);
+        processAllMessages();
         assertTrue(1 == detector.getWakelockTimoutCount());
     }
 
@@ -543,6 +551,108 @@
                 RIL_REQUEST_VOICE_REGISTRATION_STATE);
     }
 
+    private RadioAccessSpecifier getRadioAccessSpecifier(CellInfo cellInfo) {
+        RadioAccessSpecifier ras;
+        if (cellInfo instanceof CellInfoLte) {
+            int ranLte = AccessNetworkConstants.AccessNetworkType.EUTRAN;
+            int[] lteChannels = {((CellInfoLte) cellInfo).getCellIdentity().getEarfcn()};
+            ras = new RadioAccessSpecifier(ranLte, null /* bands */, lteChannels);
+        } else if (cellInfo instanceof CellInfoWcdma) {
+            int ranLte = AccessNetworkConstants.AccessNetworkType.UTRAN;
+            int[] wcdmaChannels = {((CellInfoWcdma) cellInfo).getCellIdentity().getUarfcn()};
+            ras = new RadioAccessSpecifier(ranLte, null /* bands */, wcdmaChannels);
+        } else if (cellInfo instanceof CellInfoGsm) {
+            int ranGsm = AccessNetworkConstants.AccessNetworkType.GERAN;
+            int[] gsmChannels = {((CellInfoGsm) cellInfo).getCellIdentity().getArfcn()};
+            ras = new RadioAccessSpecifier(ranGsm, null /* bands */, gsmChannels);
+        } else {
+            ras = null;
+        }
+        return ras;
+    }
+
+    private NetworkScanRequest getNetworkScanRequestForTesting() {
+        // Construct a NetworkScanRequest for testing
+        List<CellInfo> allCellInfo = mTelephonyManager.getAllCellInfo();
+        List<RadioAccessSpecifier> radioAccessSpecifier = new ArrayList<>();
+        for (int i = 0; i < allCellInfo.size(); i++) {
+            RadioAccessSpecifier ras = getRadioAccessSpecifier(allCellInfo.get(i));
+            if (ras != null) {
+                radioAccessSpecifier.add(ras);
+            }
+        }
+        if (radioAccessSpecifier.size() == 0) {
+            RadioAccessSpecifier gsm = new RadioAccessSpecifier(
+                    AccessNetworkConstants.AccessNetworkType.GERAN,
+                    null /* bands */,
+                    null /* channels */);
+            radioAccessSpecifier.add(gsm);
+        }
+        RadioAccessSpecifier[] radioAccessSpecifierArray =
+                new RadioAccessSpecifier[radioAccessSpecifier.size()];
+        return new NetworkScanRequest(
+                NetworkScanRequest.SCAN_TYPE_ONE_SHOT /* scan type */,
+                radioAccessSpecifier.toArray(radioAccessSpecifierArray),
+                5 /* search periodicity */,
+                60 /* max search time */,
+                true /*enable incremental results*/,
+                5 /* incremental results periodicity */,
+                null /* List of PLMN ids (MCC-MNC) */);
+    }
+
+    @FlakyTest
+    @Test
+    public void testStartNetworkScanWithUnsupportedResponse() throws Exception {
+        // Use Radio HAL v1.5
+        try {
+            replaceInstance(RIL.class, "mRadioVersion", mRILUnderTest, mRadioVersionV15);
+        } catch (Exception e) {
+        }
+        NetworkScanRequest nsr = getNetworkScanRequestForTesting();
+        mRILUnderTest.startNetworkScan(nsr, obtainMessage());
+
+        // Verify the v1.5 HAL methed is called firstly
+        verify(mRadioProxy).startNetworkScan_1_5(mSerialNumberCaptor.capture(), any());
+
+        // Before we find a way to trigger real RadioResponse method, emulate the behaivor.
+        Consumer<RILRequest> unsupportedResponseEmulator = rr -> {
+            mRILUnderTest.setCompatVersion(rr.getRequest(), RIL.RADIO_HAL_VERSION_1_4);
+            mRILUnderTest.startNetworkScan(nsr, Message.obtain(rr.getResult()));
+        };
+
+        verifyRILUnsupportedResponse(mRILUnderTest, mSerialNumberCaptor.getValue(),
+                RIL_REQUEST_START_NETWORK_SCAN, unsupportedResponseEmulator);
+
+        // Verify the fallback method is invoked
+        verify(mRadioProxy).startNetworkScan_1_4(eq(mSerialNumberCaptor.getValue() + 1), any());
+    }
+
+    @FlakyTest
+    @Test
+    public void testGetVoiceRegistrationStateWithUnsupportedResponse() throws Exception {
+        // Use Radio HAL v1.5
+        try {
+            replaceInstance(RIL.class, "mRadioVersion", mRILUnderTest, mRadioVersionV15);
+        } catch (Exception e) {
+        }
+        mRILUnderTest.getVoiceRegistrationState(obtainMessage());
+
+        // Verify the v1.5 HAL method is called
+        verify(mRadioProxy).getVoiceRegistrationState_1_5(mSerialNumberCaptor.capture());
+
+        // Before we find a way to trigger real RadioResponse method, emulate the behaivor.
+        Consumer<RILRequest> unsupportedResponseEmulator = rr -> {
+            mRILUnderTest.setCompatVersion(rr.getRequest(), RIL.RADIO_HAL_VERSION_1_4);
+            mRILUnderTest.getVoiceRegistrationState(Message.obtain(rr.getResult()));
+        };
+
+        verifyRILUnsupportedResponse(mRILUnderTest, mSerialNumberCaptor.getValue(),
+                RIL_REQUEST_VOICE_REGISTRATION_STATE, unsupportedResponseEmulator);
+
+        // Verify the fallback method is invoked
+        verify(mRadioProxy).getVoiceRegistrationState(mSerialNumberCaptor.getValue() + 1);
+    }
+
     @FlakyTest
     @Test
     public void testGetDataRegistrationState() throws Exception {
@@ -554,6 +664,32 @@
 
     @FlakyTest
     @Test
+    public void testGetDataRegistrationStateWithUnsupportedResponse() throws Exception {
+        // Use Radio HAL v1.5
+        try {
+            replaceInstance(RIL.class, "mRadioVersion", mRILUnderTest, mRadioVersionV15);
+        } catch (Exception e) {
+        }
+
+        // Verify the v1.5 HAL method is called
+        mRILUnderTest.getDataRegistrationState(obtainMessage());
+        verify(mRadioProxy).getDataRegistrationState_1_5(mSerialNumberCaptor.capture());
+
+        // Before we find a way to trigger real RadioResponse method, emulate the behaivor.
+        Consumer<RILRequest> unsupportedResponseEmulator = rr -> {
+            mRILUnderTest.setCompatVersion(rr.getRequest(), RIL.RADIO_HAL_VERSION_1_4);
+            mRILUnderTest.getDataRegistrationState(Message.obtain(rr.getResult()));
+        };
+
+        verifyRILUnsupportedResponse(mRILUnderTest, mSerialNumberCaptor.getValue(),
+                RIL_REQUEST_DATA_REGISTRATION_STATE, unsupportedResponseEmulator);
+
+        // Verify the fallback method is invoked
+        verify(mRadioProxy).getDataRegistrationState(mSerialNumberCaptor.getValue() + 1);
+    }
+
+    @FlakyTest
+    @Test
     public void testGetOperator() throws Exception {
         mRILUnderTest.getOperator(obtainMessage());
         verify(mRadioProxy).getOperator(mSerialNumberCaptor.capture());
@@ -1007,7 +1143,8 @@
     public void testGetModemActivityInfoTimeout() {
         mRILUnderTest.getModemActivityInfo(obtainMessage(), new WorkSource());
         assertEquals(1, mRILUnderTest.getRilRequestList().size());
-        waitForHandlerActionDelayed(mRilHandler, 10, DEFAULT_BLOCKING_MESSAGE_RESPONSE_TIMEOUT_MS);
+        moveTimeForward(DEFAULT_BLOCKING_MESSAGE_RESPONSE_TIMEOUT_MS);
+        processAllMessages();
         assertEquals(0, mRILUnderTest.getRilRequestList().size());
     }
 
@@ -1070,7 +1207,7 @@
 
     @FlakyTest
     @Test
-    public void testWakeLockTimeout() throws Exception {
+    public void testWakeLockTimeout() {
         invokeMethod(
                 mRILInstance,
                 "obtainRequest",
@@ -1080,13 +1217,31 @@
         // The wake lock should be held when obtain a RIL request.
         assertTrue(mRILInstance.getWakeLock(RIL.FOR_WAKELOCK).isHeld());
 
-        waitForHandlerActionDelayed(mRilHandler, 10, DEFAULT_WAKE_LOCK_TIMEOUT_MS);
+        moveTimeForward(DEFAULT_WAKE_LOCK_TIMEOUT_MS);
+        processAllMessages();
 
         // The wake lock should be released after processed the time out event.
         assertFalse(mRILInstance.getWakeLock(RIL.FOR_WAKELOCK).isHeld());
     }
 
     @Test
+    public void testGetBarringInfo() throws Exception {
+        // Not supported on Radio 1.0.
+        mRILUnderTest.getBarringInfo(obtainMessage());
+        verify(mRadioProxy, never()).getBarringInfo(anyInt());
+
+        // Make radio version 1.5 to support the operation.
+        try {
+            replaceInstance(RIL.class, "mRadioVersion", mRILUnderTest, mRadioVersionV15);
+        } catch (Exception e) {
+        }
+        mRILUnderTest.getBarringInfo(obtainMessage());
+        verify(mRadioProxy).getBarringInfo(mSerialNumberCaptor.capture());
+        verifyRILResponse(
+                mRILUnderTest, mSerialNumberCaptor.getValue(), RIL_REQUEST_GET_BARRING_INFO);
+    }
+
+    @Test
     public void testInvokeOemRilRequestStrings() throws Exception {
         String[] strings = new String[]{"a", "b", "c"};
         mRILUnderTest.invokeOemRilRequestStrings(strings, obtainMessage());
@@ -1103,7 +1258,7 @@
     }
 
     private Message obtainMessage() {
-        return mTestHandler.getThreadHandler().obtainMessage();
+        return mRILUnderTest.getRilHandler().obtainMessage();
     }
 
     private static void verifyRILResponse(RIL ril, int serial, int requestType) {
@@ -1138,6 +1293,27 @@
         assertFalse(ril.getWakeLock(RIL.FOR_WAKELOCK).isHeld());
     }
 
+    private static void verifyRILUnsupportedResponse(RIL ril, int serial, int requestType,
+            Consumer<RILRequest> unsupportedResponseEmulator) {
+        RadioResponseInfo responseInfo =
+                createFakeRadioResponseInfo(serial, RadioError.REQUEST_NOT_SUPPORTED,
+                        RadioResponseType.SOLICITED);
+
+        RILRequest rr = ril.processResponse(responseInfo);
+        assertNotNull(rr);
+
+        assertEquals(serial, rr.getSerial());
+        assertEquals(requestType, rr.getRequest());
+        assertTrue(ril.getWakeLock(RIL.FOR_WAKELOCK).isHeld());
+
+        unsupportedResponseEmulator.accept(rr);
+
+        ril.processResponseDone(rr, responseInfo, null);
+
+        assertEquals(1, ril.getRilRequestList().size());
+        assertTrue(ril.getWakeLock(RIL.FOR_WAKELOCK).isHeld());
+    }
+
     private static RadioResponseInfo createFakeRadioResponseInfo(int serial, int error, int type) {
         RadioResponseInfo respInfo = new RadioResponseInfo();
         respInfo.serial = serial;
@@ -1147,7 +1323,7 @@
     }
 
     @Test
-    public void testConvertHalCellInfoListForLTE() throws Exception {
+    public void testConvertHalCellInfoListForLTE() {
         android.hardware.radio.V1_0.CellInfoLte lte = new android.hardware.radio.V1_0.CellInfoLte();
         lte.cellIdentityLte.ci = CI;
         lte.cellIdentityLte.pci = PCI;
@@ -1178,8 +1354,9 @@
         CellInfoLte expected = new CellInfoLte();
         expected.setRegistered(false);
         expected.setTimeStamp(TIMESTAMP);
-        CellIdentityLte cil = new CellIdentityLte(CI, PCI, TAC, EARFCN, Integer.MAX_VALUE, MCC_STR,
-                MNC_STR, EMPTY_ALPHA_LONG, EMPTY_ALPHA_SHORT);
+        CellIdentityLte cil = new CellIdentityLte(CI, PCI, TAC, EARFCN, new int[] {},
+                Integer.MAX_VALUE, MCC_STR, MNC_STR, EMPTY_ALPHA_LONG, EMPTY_ALPHA_SHORT,
+                Collections.emptyList(), null);
         CellSignalStrengthLte css = new CellSignalStrengthLte(
                 RSSI, RSRP, RSRQ, RSSNR, CQI, TIMING_ADVANCE);
         expected.setCellIdentity(cil);
@@ -1190,7 +1367,7 @@
     }
 
     @Test
-    public void testConvertHalCellInfoListForGSM() throws Exception {
+    public void testConvertHalCellInfoListForGSM() {
         android.hardware.radio.V1_0.CellInfoGsm cellinfo =
                 new android.hardware.radio.V1_0.CellInfoGsm();
         cellinfo.cellIdentityGsm.lac = LAC;
@@ -1220,7 +1397,8 @@
         expected.setRegistered(false);
         expected.setTimeStamp(TIMESTAMP);
         CellIdentityGsm ci = new CellIdentityGsm(
-                LAC, CID, ARFCN, BSIC, MCC_STR, MNC_STR, EMPTY_ALPHA_LONG, EMPTY_ALPHA_SHORT);
+                LAC, CID, ARFCN, BSIC, MCC_STR, MNC_STR, EMPTY_ALPHA_LONG, EMPTY_ALPHA_SHORT,
+                Collections.emptyList());
         CellSignalStrengthGsm cs = new CellSignalStrengthGsm(
                 RSSI, BIT_ERROR_RATE, TIMING_ADVANCE);
         expected.setCellIdentity(ci);
@@ -1231,7 +1409,7 @@
     }
 
     @Test
-    public void testConvertHalCellInfoListForWcdma() throws Exception {
+    public void testConvertHalCellInfoListForWcdma() {
         android.hardware.radio.V1_0.CellInfoWcdma cellinfo =
                 new android.hardware.radio.V1_0.CellInfoWcdma();
         cellinfo.cellIdentityWcdma.lac = LAC;
@@ -1260,7 +1438,8 @@
         expected.setRegistered(false);
         expected.setTimeStamp(TIMESTAMP);
         CellIdentityWcdma ci = new CellIdentityWcdma(
-                LAC, CID, PSC, UARFCN, MCC_STR, MNC_STR, EMPTY_ALPHA_LONG, EMPTY_ALPHA_SHORT);
+                LAC, CID, PSC, UARFCN, MCC_STR, MNC_STR, EMPTY_ALPHA_LONG, EMPTY_ALPHA_SHORT,
+                Collections.emptyList(), null);
         CellSignalStrengthWcdma cs = new CellSignalStrengthWcdma(
                 RSSI, BIT_ERROR_RATE, Integer.MAX_VALUE, Integer.MAX_VALUE);
         expected.setCellIdentity(ci);
@@ -1270,16 +1449,24 @@
         assertEquals(expected, cellInfoWcdma);
     }
 
+    private static void initializeCellIdentityTdscdma_1_2(
+            android.hardware.radio.V1_2.CellIdentityTdscdma cid) {
+        cid.base.lac = LAC;
+        cid.base.cid = CID;
+        cid.base.cpid = PSC;
+        cid.base.mcc = MCC_STR;
+        cid.base.mnc = MNC_STR;
+        cid.uarfcn = UARFCN;
+        cid.operatorNames.alphaLong = ALPHA_LONG;
+        cid.operatorNames.alphaShort = ALPHA_SHORT;
+    }
+
     @Test
-    public void testConvertHalCellInfoListForTdscdma() throws Exception {
+    public void testConvertHalCellInfoListForTdscdma() {
         android.hardware.radio.V1_2.CellInfoTdscdma cellinfo =
                 new android.hardware.radio.V1_2.CellInfoTdscdma();
-        cellinfo.cellIdentityTdscdma.base.lac = LAC;
-        cellinfo.cellIdentityTdscdma.base.cid = CID;
-        cellinfo.cellIdentityTdscdma.base.cpid = PSC;
-        cellinfo.cellIdentityTdscdma.uarfcn = UARFCN;
-        cellinfo.cellIdentityTdscdma.base.mcc = MCC_STR;
-        cellinfo.cellIdentityTdscdma.base.mnc = MNC_STR;
+        initializeCellIdentityTdscdma_1_2(cellinfo.cellIdentityTdscdma);
+
         cellinfo.signalStrengthTdscdma.signalStrength = RSSI_ASU;
         cellinfo.signalStrengthTdscdma.bitErrorRate = BIT_ERROR_RATE;
         cellinfo.signalStrengthTdscdma.rscp = RSCP_ASU;
@@ -1302,7 +1489,8 @@
         expected.setTimeStamp(TIMESTAMP);
         expected.setCellConnectionStatus(CellInfo.CONNECTION_NONE);
         CellIdentityTdscdma ci = new CellIdentityTdscdma(
-                MCC_STR, MNC_STR, LAC, CID, PSC, UARFCN, EMPTY_ALPHA_LONG, EMPTY_ALPHA_SHORT);
+                MCC_STR, MNC_STR, LAC, CID, PSC, UARFCN, ALPHA_LONG, ALPHA_SHORT,
+                Collections.emptyList(), null);
         CellSignalStrengthTdscdma cs = new CellSignalStrengthTdscdma(
                 RSSI, BIT_ERROR_RATE, RSCP);
         expected.setCellIdentity(ci);
@@ -1312,7 +1500,7 @@
     }
 
     @Test
-    public void testConvertHalCellInfoListForCdma() throws Exception {
+    public void testConvertHalCellInfoListForCdma() {
         android.hardware.radio.V1_0.CellInfoCdma cellinfo =
                 new android.hardware.radio.V1_0.CellInfoCdma();
         cellinfo.cellIdentityCdma.networkId = NETWORK_ID;
@@ -1355,7 +1543,7 @@
     }
 
     @Test
-    public void testConvertHalCellInfoList_1_2ForLTE() throws Exception {
+    public void testConvertHalCellInfoList_1_2ForLTE() {
         ArrayList<CellInfo> ret = getCellInfoListForLTE(MCC_STR, MNC_STR, ALPHA_LONG, ALPHA_SHORT);
 
         assertEquals(1, ret.size());
@@ -1364,7 +1552,8 @@
         expected.setRegistered(false);
         expected.setTimeStamp(TIMESTAMP);
         CellIdentityLte cil = new CellIdentityLte(
-                CI, PCI, TAC, EARFCN, BANDWIDTH, MCC_STR, MNC_STR, ALPHA_LONG, ALPHA_SHORT);
+                CI, PCI, TAC, EARFCN, new int[] {}, BANDWIDTH, MCC_STR, MNC_STR,
+                ALPHA_LONG, ALPHA_SHORT, Collections.emptyList(), null);
         CellSignalStrengthLte css = new CellSignalStrengthLte(
                 RSSI, RSRP, RSRQ, RSSNR, CQI, TIMING_ADVANCE);
         expected.setCellIdentity(cil);
@@ -1375,7 +1564,7 @@
     }
 
     @Test
-    public void testConvertHalCellInfoList_1_2_ForLTEWithEmptyOperatorInfo() throws Exception {
+    public void testConvertHalCellInfoList_1_2_ForLTEWithEmptyOperatorInfo() {
         ArrayList<CellInfo> ret = getCellInfoListForLTE(
                 MCC_STR, MNC_STR, EMPTY_ALPHA_LONG, EMPTY_ALPHA_SHORT);
 
@@ -1384,8 +1573,9 @@
         CellInfoLte expected = new CellInfoLte();
         expected.setRegistered(false);
         expected.setTimeStamp(TIMESTAMP);
-        CellIdentityLte cil = new CellIdentityLte(CI, PCI, TAC, EARFCN, BANDWIDTH, MCC_STR, MNC_STR,
-                EMPTY_ALPHA_LONG, EMPTY_ALPHA_SHORT);
+        CellIdentityLte cil = new CellIdentityLte(CI, PCI, TAC, EARFCN, new int[] {},
+                BANDWIDTH, MCC_STR, MNC_STR, EMPTY_ALPHA_LONG, EMPTY_ALPHA_SHORT,
+                Collections.emptyList(), null);
         CellSignalStrengthLte css = new CellSignalStrengthLte(
                 RSSI, RSRP, RSRQ, RSSNR, CQI, TIMING_ADVANCE);
         expected.setCellIdentity(cil);
@@ -1396,7 +1586,7 @@
     }
 
     @Test
-    public void testConvertHalCellInfoList_1_2ForLTEWithEmptyMccMnc() throws Exception {
+    public void testConvertHalCellInfoList_1_2ForLTEWithEmptyMccMnc() {
         // MCC/MNC will be set as INT_MAX if unknown
         ArrayList<CellInfo> ret = getCellInfoListForLTE(
                 String.valueOf(Integer.MAX_VALUE), String.valueOf(Integer.MAX_VALUE),
@@ -1408,7 +1598,8 @@
         expected.setRegistered(false);
         expected.setTimeStamp(TIMESTAMP);
         CellIdentityLte cil = new CellIdentityLte(
-                CI, PCI, TAC, EARFCN, BANDWIDTH, null, null, ALPHA_LONG, ALPHA_SHORT);
+                CI, PCI, TAC, EARFCN, new int[] {}, BANDWIDTH, null, null, ALPHA_LONG,
+                ALPHA_SHORT, Collections.emptyList(), null);
         CellSignalStrengthLte css = new CellSignalStrengthLte(
                 RSSI, RSRP, RSRQ, RSSNR, CQI, TIMING_ADVANCE);
         expected.setCellIdentity(cil);
@@ -1419,7 +1610,7 @@
     }
 
     @Test
-    public void testConvertHalCellInfoList_1_2ForGSM() throws Exception {
+    public void testConvertHalCellInfoList_1_2ForGSM() {
         ArrayList<CellInfo> ret = getCellInfoListForGSM(MCC_STR, MNC_STR, ALPHA_LONG, ALPHA_SHORT);
 
         assertEquals(1, ret.size());
@@ -1428,7 +1619,8 @@
         expected.setRegistered(false);
         expected.setTimeStamp(TIMESTAMP);
         CellIdentityGsm ci = new CellIdentityGsm(
-                LAC, CID, ARFCN, BSIC, MCC_STR, MNC_STR, ALPHA_LONG, ALPHA_SHORT);
+                LAC, CID, ARFCN, BSIC, MCC_STR, MNC_STR, ALPHA_LONG, ALPHA_SHORT,
+                Collections.emptyList());
         CellSignalStrengthGsm cs = new CellSignalStrengthGsm(
                 RSSI, BIT_ERROR_RATE, TIMING_ADVANCE);
         expected.setCellIdentity(ci);
@@ -1439,7 +1631,7 @@
     }
 
     @Test
-    public void testConvertHalCellInfoList_1_2ForGSMWithEmptyOperatorInfo() throws Exception {
+    public void testConvertHalCellInfoList_1_2ForGSMWithEmptyOperatorInfo() {
         ArrayList<CellInfo> ret = getCellInfoListForGSM(
                 MCC_STR, MNC_STR, EMPTY_ALPHA_LONG, EMPTY_ALPHA_SHORT);
 
@@ -1449,7 +1641,8 @@
         expected.setRegistered(false);
         expected.setTimeStamp(TIMESTAMP);
         CellIdentityGsm ci = new CellIdentityGsm(
-                LAC, CID, ARFCN, BSIC, MCC_STR, MNC_STR, EMPTY_ALPHA_LONG, EMPTY_ALPHA_SHORT);
+                LAC, CID, ARFCN, BSIC, MCC_STR, MNC_STR, EMPTY_ALPHA_LONG, EMPTY_ALPHA_SHORT,
+                Collections.emptyList());
         CellSignalStrengthGsm cs = new CellSignalStrengthGsm(
                 RSSI, BIT_ERROR_RATE, TIMING_ADVANCE);
         expected.setCellIdentity(ci);
@@ -1460,7 +1653,7 @@
     }
 
     @Test
-    public void testConvertHalCellInfoList_1_2ForGSMWithEmptyMccMnc() throws Exception {
+    public void testConvertHalCellInfoList_1_2ForGSMWithEmptyMccMnc() {
         // MCC/MNC will be set as INT_MAX if unknown
         ArrayList<CellInfo> ret = getCellInfoListForGSM(
                 String.valueOf(Integer.MAX_VALUE), String.valueOf(Integer.MAX_VALUE),
@@ -1472,7 +1665,8 @@
         expected.setRegistered(false);
         expected.setTimeStamp(TIMESTAMP);
         CellIdentityGsm ci = new CellIdentityGsm(
-                LAC, CID, ARFCN, BSIC, null, null, ALPHA_LONG, ALPHA_SHORT);
+                LAC, CID, ARFCN, BSIC, null, null, ALPHA_LONG, ALPHA_SHORT,
+                Collections.emptyList());
         CellSignalStrengthGsm cs = new CellSignalStrengthGsm(
                 RSSI, BIT_ERROR_RATE, TIMING_ADVANCE);
         expected.setCellIdentity(ci);
@@ -1483,7 +1677,7 @@
     }
 
     @Test
-    public void testConvertHalCellInfoList_1_2ForWcdma() throws Exception {
+    public void testConvertHalCellInfoList_1_2ForWcdma() {
         ArrayList<CellInfo> ret = getCellInfoListForWcdma(
                 MCC_STR, MNC_STR, ALPHA_LONG, ALPHA_SHORT);
 
@@ -1493,7 +1687,8 @@
         expected.setRegistered(false);
         expected.setTimeStamp(TIMESTAMP);
         CellIdentityWcdma ci = new CellIdentityWcdma(
-                LAC, CID, PSC, UARFCN, MCC_STR, MNC_STR, ALPHA_LONG, ALPHA_SHORT);
+                LAC, CID, PSC, UARFCN, MCC_STR, MNC_STR, ALPHA_LONG, ALPHA_SHORT,
+                Collections.emptyList(), null);
         CellSignalStrengthWcdma cs =
                 new CellSignalStrengthWcdma(RSSI, BIT_ERROR_RATE, RSCP, ECNO);
         expected.setCellIdentity(ci);
@@ -1504,7 +1699,7 @@
     }
 
     @Test
-    public void testConvertHalCellInfoList_1_2ForWcdmaWithEmptyOperatorInfo() throws Exception {
+    public void testConvertHalCellInfoList_1_2ForWcdmaWithEmptyOperatorInfo() {
         ArrayList<CellInfo> ret = getCellInfoListForWcdma(
                 MCC_STR, MNC_STR, EMPTY_ALPHA_LONG, EMPTY_ALPHA_SHORT);
 
@@ -1514,7 +1709,8 @@
         expected.setRegistered(false);
         expected.setTimeStamp(TIMESTAMP);
         CellIdentityWcdma ci = new CellIdentityWcdma(
-                LAC, CID, PSC, UARFCN, MCC_STR, MNC_STR, EMPTY_ALPHA_LONG, EMPTY_ALPHA_SHORT);
+                LAC, CID, PSC, UARFCN, MCC_STR, MNC_STR, EMPTY_ALPHA_LONG, EMPTY_ALPHA_SHORT,
+                Collections.emptyList(), null);
         CellSignalStrengthWcdma cs = new CellSignalStrengthWcdma(
                 RSSI, BIT_ERROR_RATE, RSCP, ECNO);
         expected.setCellIdentity(ci);
@@ -1525,11 +1721,9 @@
     }
 
     @Test
-    public void testConvertHalCellInfoList_1_2ForWcdmaWithEmptyMccMnc() throws Exception {
+    public void testConvertHalCellInfoList_1_2ForWcdmaWithEmptyMccMnc() {
         // MCC/MNC will be set as INT_MAX if unknown
-        ArrayList<CellInfo> ret = getCellInfoListForWcdma(
-                String.valueOf(Integer.MAX_VALUE), String.valueOf(Integer.MAX_VALUE),
-                ALPHA_LONG, ALPHA_SHORT);
+        ArrayList<CellInfo> ret = getCellInfoListForWcdma(null, null, ALPHA_LONG, ALPHA_SHORT);
 
         assertEquals(1, ret.size());
         CellInfoWcdma cellInfoWcdma = (CellInfoWcdma) ret.get(0);
@@ -1537,7 +1731,8 @@
         expected.setRegistered(false);
         expected.setTimeStamp(TIMESTAMP);
         CellIdentityWcdma ci = new CellIdentityWcdma(
-                LAC, CID, PSC, UARFCN, null, null, ALPHA_LONG, ALPHA_SHORT);
+                LAC, CID, PSC, UARFCN, null, null, ALPHA_LONG, ALPHA_SHORT,
+                Collections.emptyList(), null);
         CellSignalStrengthWcdma cs = new CellSignalStrengthWcdma(
                 RSSI, BIT_ERROR_RATE, RSCP, ECNO);
         expected.setCellIdentity(ci);
@@ -1548,7 +1743,7 @@
     }
 
     @Test
-    public void testConvertHalCellInfoList_1_2ForCdma() throws Exception {
+    public void testConvertHalCellInfoList_1_2ForCdma() {
         ArrayList<CellInfo> ret = getCellInfoListForCdma(ALPHA_LONG, ALPHA_SHORT);
 
         assertEquals(1, ret.size());
@@ -1569,7 +1764,7 @@
     }
 
     @Test
-    public void testConvertHalCellInfoList_1_2ForCdmaWithEmptyOperatorInfo() throws Exception {
+    public void testConvertHalCellInfoList_1_2ForCdmaWithEmptyOperatorInfo() {
         ArrayList<CellInfo> ret = getCellInfoListForCdma(EMPTY_ALPHA_LONG, EMPTY_ALPHA_SHORT);
 
         assertEquals(1, ret.size());
@@ -1590,7 +1785,198 @@
     }
 
     @Test
-    public void testConvertDataCallResult() throws Exception {
+    public void testConvertHalCellInfoList_1_4ForNr() {
+        android.hardware.radio.V1_4.CellInfoNr cellinfo =
+                new android.hardware.radio.V1_4.CellInfoNr();
+        cellinfo.cellidentity.nci = CI;
+        cellinfo.cellidentity.pci = PCI;
+        cellinfo.cellidentity.tac = TAC;
+        cellinfo.cellidentity.nrarfcn = NRARFCN;
+        cellinfo.cellidentity.mcc = MCC_STR;
+        cellinfo.cellidentity.mnc = MNC_STR;
+        cellinfo.cellidentity.operatorNames.alphaLong = ALPHA_LONG;
+        cellinfo.cellidentity.operatorNames.alphaShort = ALPHA_SHORT;
+        cellinfo.signalStrength.ssRsrp = RSRP;
+        cellinfo.signalStrength.ssRsrq = RSRQ;
+        cellinfo.signalStrength.ssSinr = SIGNAL_NOISE_RATIO;
+        cellinfo.signalStrength.csiRsrp = RSRP;
+        cellinfo.signalStrength.csiRsrq = RSRQ;
+        cellinfo.signalStrength.csiSinr = SIGNAL_NOISE_RATIO;
+
+        android.hardware.radio.V1_4.CellInfo record = new android.hardware.radio.V1_4.CellInfo();
+        record.info.nr(cellinfo);
+
+        ArrayList<android.hardware.radio.V1_4.CellInfo> records = new ArrayList<>();
+        records.add(record);
+
+        ArrayList<CellInfo> ret = RIL.convertHalCellInfoList_1_4(records);
+
+        CellInfoNr cellInfoNr = (CellInfoNr) ret.get(0);
+        CellIdentityNr cellIdentityNr = (CellIdentityNr) cellInfoNr.getCellIdentity();
+        CellSignalStrengthNr signalStrengthNr =
+                (CellSignalStrengthNr) cellInfoNr.getCellSignalStrength();
+
+        CellIdentityNr expectedCellIdentity = new CellIdentityNr(PCI, TAC, NRARFCN,
+                new int[] {}, MCC_STR, MNC_STR, CI, ALPHA_LONG, ALPHA_SHORT,
+                Collections.emptyList());
+        CellSignalStrengthNr expectedSignalStrength = new CellSignalStrengthNr(-RSRP, -RSRQ,
+                SIGNAL_NOISE_RATIO, -RSRP, -RSRQ, SIGNAL_NOISE_RATIO);
+
+        assertEquals(expectedCellIdentity, cellIdentityNr);
+        assertEquals(expectedSignalStrength, signalStrengthNr);
+    }
+
+    private static android.hardware.radio.V1_5.ClosedSubscriberGroupInfo getHalCsgInfo() {
+        android.hardware.radio.V1_5.ClosedSubscriberGroupInfo csgInfo =
+                new android.hardware.radio.V1_5.ClosedSubscriberGroupInfo();
+
+        csgInfo.csgIndication = CSG_INDICATION;
+        csgInfo.homeNodebName = HOME_NODEB_NAME;
+        csgInfo.csgIdentity = CSG_IDENTITY;
+
+        return csgInfo;
+    }
+
+    private static void initializeCellIdentityLte_1_5(
+            android.hardware.radio.V1_5.CellIdentityLte id,
+            boolean addAdditionalPlmns, boolean addCsgInfo) {
+
+        initializeCellIdentityLte_1_2(id.base);
+
+        if (addAdditionalPlmns) {
+            id.additionalPlmns = new ArrayList<>(
+                    Arrays.asList(ADDITIONAL_PLMNS));
+        }
+
+        if (addCsgInfo) {
+            id.optionalCsgInfo.csgInfo(getHalCsgInfo());
+        }
+    }
+
+    @Test
+    public void testCellIdentityLte_1_5_CsgInfo() {
+        android.hardware.radio.V1_5.CellIdentityLte halCellIdentity =
+                new android.hardware.radio.V1_5.CellIdentityLte();
+        initializeCellIdentityLte_1_5(halCellIdentity, false, true);
+
+        CellIdentityLte cellIdentity = new CellIdentityLte(halCellIdentity);
+
+        assertEquals(CSG_INDICATION,
+                cellIdentity.getClosedSubscriberGroupInfo().getCsgIndicator());
+        assertEquals(HOME_NODEB_NAME,
+                cellIdentity.getClosedSubscriberGroupInfo().getHomeNodebName());
+        assertEquals(CSG_IDENTITY,
+                cellIdentity.getClosedSubscriberGroupInfo().getCsgIdentity());
+    }
+
+    @Test
+    public void testCellIdentityLte_1_5_MultiPlmn() {
+        android.hardware.radio.V1_5.CellIdentityLte halCellIdentity =
+                new android.hardware.radio.V1_5.CellIdentityLte();
+        initializeCellIdentityLte_1_5(halCellIdentity, true, false);
+
+        CellIdentityLte cellIdentity = new CellIdentityLte(halCellIdentity);
+
+        Set<String> additionalPlmns = new HashSet<>();
+        Collections.addAll(additionalPlmns, ADDITIONAL_PLMNS);
+
+        assertEquals(cellIdentity.getAdditionalPlmns(), additionalPlmns);
+    }
+
+    private static void initializeCellIdentityWcdma_1_5(
+            android.hardware.radio.V1_5.CellIdentityWcdma id,
+            boolean addAdditionalPlmns, boolean addCsgInfo) {
+
+        initializeCellIdentityWcdma_1_2(id.base);
+
+        if (addAdditionalPlmns) {
+            id.additionalPlmns = new ArrayList<>(
+                    Arrays.asList(ADDITIONAL_PLMNS));
+        }
+
+        if (addCsgInfo) {
+            id.optionalCsgInfo.csgInfo(getHalCsgInfo());
+        }
+    }
+
+    @Test
+    public void testCellIdentityWcdma_1_5_CsgInfo() {
+        android.hardware.radio.V1_5.CellIdentityWcdma halCellIdentity =
+                new android.hardware.radio.V1_5.CellIdentityWcdma();
+        initializeCellIdentityWcdma_1_5(halCellIdentity, false, true);
+
+        CellIdentityWcdma cellIdentity = new CellIdentityWcdma(halCellIdentity);
+
+        assertEquals(CSG_INDICATION,
+                cellIdentity.getClosedSubscriberGroupInfo().getCsgIndicator());
+        assertEquals(HOME_NODEB_NAME,
+                cellIdentity.getClosedSubscriberGroupInfo().getHomeNodebName());
+        assertEquals(CSG_IDENTITY,
+                cellIdentity.getClosedSubscriberGroupInfo().getCsgIdentity());
+    }
+
+    @Test
+    public void testCellIdentityWcdma_1_5_MultiPlmn() {
+        android.hardware.radio.V1_5.CellIdentityWcdma halCellIdentity =
+                new android.hardware.radio.V1_5.CellIdentityWcdma();
+        initializeCellIdentityWcdma_1_5(halCellIdentity, true, false);
+
+        CellIdentityWcdma cellIdentity = new CellIdentityWcdma(halCellIdentity);
+
+        Set<String> additionalPlmns = new HashSet<>();
+        Collections.addAll(additionalPlmns, ADDITIONAL_PLMNS);
+
+        assertEquals(cellIdentity.getAdditionalPlmns(), additionalPlmns);
+    }
+
+    private static void initializeCellIdentityTdscdma_1_5(
+            android.hardware.radio.V1_5.CellIdentityTdscdma id,
+            boolean addAdditionalPlmns, boolean addCsgInfo) {
+
+        initializeCellIdentityTdscdma_1_2(id.base);
+
+        if (addAdditionalPlmns) {
+            id.additionalPlmns = new ArrayList<>(
+                    Arrays.asList(ADDITIONAL_PLMNS));
+        }
+
+        if (addCsgInfo) {
+            id.optionalCsgInfo.csgInfo(getHalCsgInfo());
+        }
+    }
+
+    @Test
+    public void testCellIdentityTdscdma_1_5_CsgInfo() {
+        android.hardware.radio.V1_5.CellIdentityTdscdma halCellIdentity =
+                new android.hardware.radio.V1_5.CellIdentityTdscdma();
+        initializeCellIdentityTdscdma_1_5(halCellIdentity, false, true);
+
+        CellIdentityTdscdma cellIdentity = new CellIdentityTdscdma(halCellIdentity);
+
+        assertEquals(CSG_INDICATION,
+                cellIdentity.getClosedSubscriberGroupInfo().getCsgIndicator());
+        assertEquals(HOME_NODEB_NAME,
+                cellIdentity.getClosedSubscriberGroupInfo().getHomeNodebName());
+        assertEquals(CSG_IDENTITY,
+                cellIdentity.getClosedSubscriberGroupInfo().getCsgIdentity());
+    }
+
+    @Test
+    public void testCellIdentityTdscdma_1_5_MultiPlmn() {
+        android.hardware.radio.V1_5.CellIdentityTdscdma halCellIdentity =
+                new android.hardware.radio.V1_5.CellIdentityTdscdma();
+        initializeCellIdentityTdscdma_1_5(halCellIdentity, true, false);
+
+        CellIdentityTdscdma cellIdentity = new CellIdentityTdscdma(halCellIdentity);
+
+        Set<String> additionalPlmns = new HashSet<>();
+        Collections.addAll(additionalPlmns, ADDITIONAL_PLMNS);
+
+        assertEquals(cellIdentity.getAdditionalPlmns(), additionalPlmns);
+    }
+
+    @Test
+    public void testConvertDataCallResult() {
         // Test V1.0 SetupDataCallResult
         android.hardware.radio.V1_0.SetupDataCallResult result10 =
                 new android.hardware.radio.V1_0.SetupDataCallResult();
@@ -1606,17 +1992,28 @@
         result10.pcscf = "fd00:976a:c206:20::6   fd00:976a:c206:20::9    fd00:976a:c202:1d::9";
         result10.mtu = 1500;
 
-        DataCallResponse response = new DataCallResponse(0, -1, 0, 2, ApnSetting.PROTOCOL_IPV4V6,
-                "ifname",
-                Arrays.asList(new LinkAddress(NetworkUtils.numericToInetAddress("10.0.2.15"), 32),
-                        new LinkAddress("2607:fb90:a620:651d:eabe:f8da:c107:44be/64")),
-                Arrays.asList(NetworkUtils.numericToInetAddress("10.0.2.3"),
-                        NetworkUtils.numericToInetAddress("fd00:976a::9")),
-                Arrays.asList(NetworkUtils.numericToInetAddress("10.0.2.15"),
-                        NetworkUtils.numericToInetAddress("fe80::2")),
-                Arrays.asList(NetworkUtils.numericToInetAddress("fd00:976a:c206:20::6"),
-                        NetworkUtils.numericToInetAddress("fd00:976a:c206:20::9"),
-                        NetworkUtils.numericToInetAddress("fd00:976a:c202:1d::9")), 1500);
+        DataCallResponse response = new DataCallResponse.Builder()
+                .setCause(0)
+                .setSuggestedRetryTime(-1)
+                .setId(0)
+                .setLinkStatus(2)
+                .setProtocolType(ApnSetting.PROTOCOL_IPV4V6)
+                .setInterfaceName("ifname")
+                .setAddresses(Arrays.asList(
+                        new LinkAddress(InetAddresses.parseNumericAddress("10.0.2.15"), 32),
+                        new LinkAddress("2607:fb90:a620:651d:eabe:f8da:c107:44be/64")))
+                .setDnsAddresses(Arrays.asList(InetAddresses.parseNumericAddress("10.0.2.3"),
+                        InetAddresses.parseNumericAddress("fd00:976a::9")))
+                .setGatewayAddresses(Arrays.asList(InetAddresses.parseNumericAddress("10.0.2.15"),
+                        InetAddresses.parseNumericAddress("fe80::2")))
+                .setPcscfAddresses(Arrays.asList(
+                        InetAddresses.parseNumericAddress("fd00:976a:c206:20::6"),
+                        InetAddresses.parseNumericAddress("fd00:976a:c206:20::9"),
+                        InetAddresses.parseNumericAddress("fd00:976a:c202:1d::9")))
+                .setMtu(1500)
+                .setMtuV4(1500)
+                .setMtuV6(1500)
+                .build();
 
         assertEquals(response, RIL.convertDataCallResult(result10));
 
@@ -1638,6 +2035,60 @@
         result14.mtu = 1500;
 
         assertEquals(response, RIL.convertDataCallResult(result14));
+
+        // Test V1.5 SetupDataCallResult
+        android.hardware.radio.V1_5.SetupDataCallResult result15 =
+                new android.hardware.radio.V1_5.SetupDataCallResult();
+        result15.cause = android.hardware.radio.V1_4.DataCallFailCause.NONE;
+        result15.suggestedRetryTime = -1;
+        result15.cid = 0;
+        result15.active = android.hardware.radio.V1_4.DataConnActiveStatus.ACTIVE;
+        result15.type = android.hardware.radio.V1_4.PdpProtocolType.IPV4V6;
+        result15.ifname = "ifname";
+
+        android.hardware.radio.V1_5.LinkAddress la1 = new android.hardware.radio.V1_5.LinkAddress();
+        la1.address = "10.0.2.15";
+        la1.properties = 0;
+        la1.deprecationTime = -1;
+        la1.expirationTime = -1;
+
+        android.hardware.radio.V1_5.LinkAddress la2 = new android.hardware.radio.V1_5.LinkAddress();
+        la2.address = "2607:fb90:a620:651d:eabe:f8da:c107:44be/64";
+        la2.properties = 0;
+        la2.deprecationTime = -1;
+        la2.expirationTime = -1;
+        result15.addresses = new ArrayList<>(Arrays.asList(la1, la2));
+        result15.dnses = new ArrayList<>(Arrays.asList("10.0.2.3", "fd00:976a::9"));
+        result15.gateways = new ArrayList<>(Arrays.asList("10.0.2.15", "fe80::2"));
+        result15.pcscf = new ArrayList<>(Arrays.asList(
+                "fd00:976a:c206:20::6", "fd00:976a:c206:20::9", "fd00:976a:c202:1d::9"));
+        result15.mtuV4 = 1500;
+        result15.mtuV6 = 3000;
+
+        response = new DataCallResponse.Builder()
+                .setCause(0)
+                .setSuggestedRetryTime(-1)
+                .setId(0)
+                .setLinkStatus(2)
+                .setProtocolType(ApnSetting.PROTOCOL_IPV4V6)
+                .setInterfaceName("ifname")
+                .setAddresses(Arrays.asList(
+                        new LinkAddress(InetAddresses.parseNumericAddress("10.0.2.15"), 32),
+                        new LinkAddress("2607:fb90:a620:651d:eabe:f8da:c107:44be/64")))
+                .setDnsAddresses(Arrays.asList(InetAddresses.parseNumericAddress("10.0.2.3"),
+                        InetAddresses.parseNumericAddress("fd00:976a::9")))
+                .setGatewayAddresses(Arrays.asList(InetAddresses.parseNumericAddress("10.0.2.15"),
+                        InetAddresses.parseNumericAddress("fe80::2")))
+                .setPcscfAddresses(Arrays.asList(
+                        InetAddresses.parseNumericAddress("fd00:976a:c206:20::6"),
+                        InetAddresses.parseNumericAddress("fd00:976a:c206:20::9"),
+                        InetAddresses.parseNumericAddress("fd00:976a:c202:1d::9")))
+                .setMtu(3000)
+                .setMtuV4(1500)
+                .setMtuV6(3000)
+                .build();
+
+        assertEquals(response, RIL.convertDataCallResult(result15));
     }
 
     @Test
@@ -1661,7 +2112,7 @@
         ws = new WorkSource();
         ws.createWorkChain().addNode(100, "foo").addNode(200, "bar");
         request = RILRequest.obtain(0, null, ws);
-        assertEquals("100:foo", request.getWorkSourceClientId());
+        assertEquals("WorkChain{(100, foo), (200, bar)}", request.getWorkSourceClientId());
     }
 
     @Test
@@ -1717,23 +2168,31 @@
         }
     }
 
+    private static void initializeCellIdentityLte_1_2(
+            android.hardware.radio.V1_2.CellIdentityLte id) {
+        // 1.0 fields
+        id.base.mcc = MCC_STR;
+        id.base.mnc = MNC_STR;
+        id.base.ci = CI;
+        id.base.pci = PCI;
+        id.base.tac = TAC;
+        id.base.earfcn = EARFCN;
+
+        // 1.2 fields
+        id.bandwidth = BANDWIDTH;
+        id.operatorNames.alphaLong = ALPHA_LONG;
+        id.operatorNames.alphaShort = ALPHA_SHORT;
+    }
+
     private static void initializeCellInfoLte_1_2(android.hardware.radio.V1_2.CellInfoLte lte) {
-        lte.cellIdentityLte.base.ci = CI;
-        lte.cellIdentityLte.base.pci = PCI;
-        lte.cellIdentityLte.base.tac = TAC;
-        lte.cellIdentityLte.base.earfcn = EARFCN;
-        lte.cellIdentityLte.bandwidth = BANDWIDTH;
+        initializeCellIdentityLte_1_2(lte.cellIdentityLte);
+
         lte.signalStrengthLte.signalStrength = RSSI_ASU;
         lte.signalStrengthLte.rsrp = -RSRP;
         lte.signalStrengthLte.rsrq = -RSRQ;
         lte.signalStrengthLte.rssnr = RSSNR;
         lte.signalStrengthLte.cqi = CQI;
         lte.signalStrengthLte.timingAdvance = TIMING_ADVANCE;
-
-        lte.cellIdentityLte.operatorNames.alphaLong = ALPHA_LONG;
-        lte.cellIdentityLte.operatorNames.alphaShort = ALPHA_SHORT;
-        lte.cellIdentityLte.base.mcc = MCC_STR;
-        lte.cellIdentityLte.base.mnc = MNC_STR;
     }
 
     private ArrayList<CellInfo> getCellInfoListForLTE(
@@ -1789,18 +2248,31 @@
         return RIL.convertHalCellInfoList_1_2(records);
     }
 
+    private static void initializeCellIdentityWcdma_1_2(
+            android.hardware.radio.V1_2.CellIdentityWcdma cid) {
+        initializeCellIdentityWcdma_1_2(cid, MCC_STR, MNC_STR, ALPHA_LONG, ALPHA_SHORT);
+    }
+
+    private static void initializeCellIdentityWcdma_1_2(
+            android.hardware.radio.V1_2.CellIdentityWcdma cid,
+                String mcc, String mnc, String alphaLong, String alphaShort) {
+        cid.base.lac = LAC;
+        cid.base.cid = CID;
+        cid.base.psc = PSC;
+        cid.base.uarfcn = UARFCN;
+        cid.base.mcc = mcc;
+        cid.base.mnc = mnc;
+        cid.operatorNames.alphaLong = alphaLong;
+        cid.operatorNames.alphaShort = alphaShort;
+    }
+
     private ArrayList<CellInfo> getCellInfoListForWcdma(
             String mcc, String mnc, String alphaLong, String alphaShort) {
         android.hardware.radio.V1_2.CellInfoWcdma cellinfo =
                 new android.hardware.radio.V1_2.CellInfoWcdma();
-        cellinfo.cellIdentityWcdma.base.lac = LAC;
-        cellinfo.cellIdentityWcdma.base.cid = CID;
-        cellinfo.cellIdentityWcdma.base.psc = PSC;
-        cellinfo.cellIdentityWcdma.base.uarfcn = UARFCN;
-        cellinfo.cellIdentityWcdma.base.mcc = mcc;
-        cellinfo.cellIdentityWcdma.base.mnc = mnc;
-        cellinfo.cellIdentityWcdma.operatorNames.alphaLong = alphaLong;
-        cellinfo.cellIdentityWcdma.operatorNames.alphaShort = alphaShort;
+        initializeCellIdentityWcdma_1_2(
+                cellinfo.cellIdentityWcdma, mcc, mnc, alphaLong, alphaShort);
+
         cellinfo.signalStrengthWcdma.base.signalStrength = RSSI_ASU;
         cellinfo.signalStrengthWcdma.base.bitErrorRate = BIT_ERROR_RATE;
         cellinfo.signalStrengthWcdma.rscp = RSCP_ASU;
@@ -1849,7 +2321,6 @@
     }
 
     @Test
-    @FlakyTest
     public void testSetupDataCall() throws Exception {
         DataProfile dp = new DataProfile.Builder()
                 .setProfileId(PROFILE_ID)
@@ -1893,7 +2364,9 @@
         assertEquals(APN_ENABLED, dpi.enabled);
         assertEquals(SUPPORTED_APNT_YPES_BITMAK, dpi.supportedApnTypesBitmap);
         assertEquals(ROAMING_PROTOCOL, ApnSetting.getProtocolIntFromString(dpi.protocol));
-        assertEquals(BEARER_BITMASK, dpi.bearerBitmap);
+        assertEquals(
+                BEARER_BITMASK,
+                ServiceState.convertBearerBitmaskToNetworkTypeBitmask(dpi.bearerBitmap >> 1));
         assertEquals(MTU, dpi.mtu);
     }
 
@@ -1982,4 +2455,71 @@
 
         assertTrue(result.equals(expected));
     }
+
+    @Test
+    public void testEnableUiccApplications() throws Exception {
+        // Not supported on Radio 1.0.
+        mRILUnderTest.enableUiccApplications(false, obtainMessage());
+        verify(mRadioProxy, never()).enableUiccApplications(anyInt(), anyBoolean());
+
+        // Make radio version 1.5 to support the operation.
+        try {
+            replaceInstance(RIL.class, "mRadioVersion", mRILUnderTest, mRadioVersionV15);
+        } catch (Exception e) {
+        }
+        mRILUnderTest.enableUiccApplications(false, obtainMessage());
+        verify(mRadioProxy).enableUiccApplications(mSerialNumberCaptor.capture(), anyBoolean());
+        verifyRILResponse(mRILUnderTest, mSerialNumberCaptor.getValue(),
+                RIL_REQUEST_ENABLE_UICC_APPLICATIONS);
+    }
+
+    @Test
+    public void testAreUiccApplicationsEnabled() throws Exception {
+        // Not supported on Radio 1.0.
+        mRILUnderTest.areUiccApplicationsEnabled(obtainMessage());
+        verify(mRadioProxy, never()).areUiccApplicationsEnabled(mSerialNumberCaptor.capture());
+
+        // Make radio version 1.5 to support the operation.
+        try {
+            replaceInstance(RIL.class, "mRadioVersion", mRILUnderTest, mRadioVersionV15);
+        } catch (Exception e) {
+        }
+        mRILUnderTest.areUiccApplicationsEnabled(obtainMessage());
+        verify(mRadioProxy).areUiccApplicationsEnabled(mSerialNumberCaptor.capture());
+        verifyRILResponse(mRILUnderTest, mSerialNumberCaptor.getValue(),
+                RIL_REQUEST_GET_UICC_APPLICATIONS_ENABLEMENT);
+    }
+
+    @Test
+    public void testAreUiccApplicationsEnabled_nullRadioProxy() throws Exception {
+        // Not supported on Radio 1.0.
+        doReturn(null).when(mRILUnderTest).getRadioProxy(any());
+        Message message = obtainMessage();
+        mRILUnderTest.areUiccApplicationsEnabled(message);
+        processAllMessages();
+        verify(mRadioProxy, never()).areUiccApplicationsEnabled(mSerialNumberCaptor.capture());
+        // Sending message is handled by getRadioProxy when proxy is null.
+        // areUiccApplicationsEnabled shouldn't explicitly send another callback.
+        assertEquals(null, message.obj);
+    }
+
+    @Test
+    public void testSetGetCompatVersion() throws Exception {
+        final int testRequest = RIL_REQUEST_GET_UICC_APPLICATIONS_ENABLEMENT;
+
+        // getCompactVersion should return null before first setting
+        assertNull(mRILUnderTest.getCompatVersion(testRequest));
+
+        // first time setting any valid HalVersion will success
+        mRILUnderTest.setCompatVersion(testRequest, RIL.RADIO_HAL_VERSION_1_4);
+        assertEquals(RIL.RADIO_HAL_VERSION_1_4, mRILUnderTest.getCompatVersion(testRequest));
+
+        // try to set a lower HalVersion will success
+        mRILUnderTest.setCompatVersion(testRequest, RIL.RADIO_HAL_VERSION_1_3);
+        assertEquals(RIL.RADIO_HAL_VERSION_1_3, mRILUnderTest.getCompatVersion(testRequest));
+
+        // try to set a greater HalVersion will not success
+        mRILUnderTest.setCompatVersion(testRequest, RIL.RADIO_HAL_VERSION_1_5);
+        assertEquals(RIL.RADIO_HAL_VERSION_1_3, mRILUnderTest.getCompatVersion(testRequest));
+    }
 }
diff --git a/tests/telephonytests/src/com/android/internal/telephony/RadioAccessFamilyTest.java b/tests/telephonytests/src/com/android/internal/telephony/RadioAccessFamilyTest.java
new file mode 100644
index 0000000..83c362b
--- /dev/null
+++ b/tests/telephonytests/src/com/android/internal/telephony/RadioAccessFamilyTest.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright 2020 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;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import android.telephony.RadioAccessFamily;
+import android.telephony.TelephonyManager;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.testing.AndroidTestingRunner;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidTestingRunner.class)
+public class RadioAccessFamilyTest extends TelephonyTest {
+    @Test
+    @SmallTest
+    public void testCompareSameFamily() throws Exception {
+        // same family same number results in no clear winner
+        assertEquals(0, RadioAccessFamily.compare(
+                    TelephonyManager.NETWORK_TYPE_BITMASK_HSUPA,
+                    TelephonyManager.NETWORK_TYPE_BITMASK_HSDPA));
+
+        // same family, return the one with more total bits, in this case RHS,
+        // so compare should be negative.
+        assertTrue(0 > RadioAccessFamily.compare(
+                TelephonyManager.NETWORK_TYPE_BITMASK_HSUPA,
+                TelephonyManager.NETWORK_TYPE_BITMASK_HSDPA
+                        | TelephonyManager.NETWORK_TYPE_BITMASK_HSPA));
+    }
+
+    @Test
+    @SmallTest
+    public void testComparedGreatestUnique() throws Exception {
+        // Because LHS supports a unique higher-generation RAT, prefer that to a large list of
+        // older RATs. Since RHS is greater, compare should be positive.
+        assertTrue(0 < RadioAccessFamily.compare(
+                TelephonyManager.NETWORK_TYPE_BITMASK_LTE
+                        | TelephonyManager.NETWORK_TYPE_BITMASK_LTE_CA,
+                TelephonyManager.NETWORK_TYPE_BITMASK_LTE
+                        | TelephonyManager.NETWORK_TYPE_BITMASK_HSUPA
+                        | TelephonyManager.NETWORK_TYPE_BITMASK_HSDPA
+                        | TelephonyManager.NETWORK_TYPE_BITMASK_HSPA));
+    }
+}
diff --git a/tests/telephonytests/src/com/android/internal/telephony/RatRatcheterTest.java b/tests/telephonytests/src/com/android/internal/telephony/RatRatcheterTest.java
index 56cd8db..b0cc83e 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/RatRatcheterTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/RatRatcheterTest.java
@@ -18,23 +18,37 @@
 import static junit.framework.Assert.assertFalse;
 import static junit.framework.Assert.assertTrue;
 
+import android.os.PersistableBundle;
+import android.telephony.AccessNetworkConstants;
+import android.telephony.CarrierConfigManager;
+import android.telephony.LteVopsSupportInfo;
+import android.telephony.NetworkRegistrationInfo;
 import android.telephony.ServiceState;
+import android.telephony.TelephonyManager;
 
+import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
 
 import java.util.Arrays;
 
 /** Tests for RatRatcheter. */
-public class RatRatcheterTest {
+public class RatRatcheterTest extends TelephonyTest {
 
     private ServiceState mServiceState;
+    private PersistableBundle mBundle;
 
     @Before
-    public void setUp() {
+    public void setUp() throws Exception {
+        super.setUp(getClass().getSimpleName());
         mServiceState = new ServiceState();
     }
 
+    @After
+    public void tearDown() throws Exception {
+        super.tearDown();
+    }
+
     @Test
     public void testUpdateBandwidthsSuccess() {
         int[] bandwidths = new int[] {1400, 5000};
@@ -68,4 +82,84 @@
         assertFalse(updated);
         assertTrue(Arrays.equals(mServiceState.getCellBandwidths(), originalBandwidths));
     }
+
+    private NetworkRegistrationInfo createNetworkRegistrationInfo(
+            int domain, int accessNetworkTechnology, boolean isUsingCarrierAggregation) {
+
+        LteVopsSupportInfo lteVopsSupportInfo =
+                new LteVopsSupportInfo(LteVopsSupportInfo.LTE_STATUS_SUPPORTED,
+                        LteVopsSupportInfo.LTE_STATUS_NOT_SUPPORTED);
+
+        return new NetworkRegistrationInfo(
+                domain,  // domain
+                AccessNetworkConstants.TRANSPORT_TYPE_WWAN,  // transportType
+                0,  // registrationState
+                accessNetworkTechnology,  // accessNetworkTechnology
+                0,  // rejectCause
+                false,  // emergencyOnly
+                null,  // availableServices
+                null,  // cellIdentity
+                null,  // rplmn
+                0,  // maxDataCalls
+                false,  // isDcNrRestricted
+                false,  // isNrAvailable
+                false,  // isEndcAvailable
+                lteVopsSupportInfo,  // lteVopsSupportInfo
+                isUsingCarrierAggregation);  // isUsingCarrierAggregation
+    }
+
+    private void setNetworkRegistrationInfo(ServiceState ss, int accessNetworkTechnology) {
+
+        NetworkRegistrationInfo nri1;
+        NetworkRegistrationInfo nri2;
+
+        boolean isUsingCarrierAggregation = false;
+
+        if (accessNetworkTechnology == TelephonyManager.NETWORK_TYPE_LTE_CA) {
+            isUsingCarrierAggregation = true;
+        }
+
+        nri1 = createNetworkRegistrationInfo(NetworkRegistrationInfo.DOMAIN_PS,
+                accessNetworkTechnology, isUsingCarrierAggregation);
+        nri2 = createNetworkRegistrationInfo(NetworkRegistrationInfo.DOMAIN_CS,
+                accessNetworkTechnology, isUsingCarrierAggregation);
+
+        ss.addNetworkRegistrationInfo(nri1);
+        ss.addNetworkRegistrationInfo(nri2);
+    }
+
+    @Test
+    public void testRatchetIsFamily() {
+        ServiceState oldSS = new ServiceState();
+        ServiceState newSS = new ServiceState();
+
+        mBundle = mContextFixture.getCarrierConfigBundle();
+        mBundle.putStringArray(CarrierConfigManager.KEY_RATCHET_RAT_FAMILIES,
+                new String[]{"14,19"});
+
+        setNetworkRegistrationInfo(oldSS, TelephonyManager.NETWORK_TYPE_LTE_CA);
+        setNetworkRegistrationInfo(newSS, TelephonyManager.NETWORK_TYPE_LTE);
+
+        RatRatcheter ratRatcheter = new RatRatcheter(mPhone);
+        ratRatcheter.ratchet(oldSS, newSS, false);
+
+        assertTrue(newSS.isUsingCarrierAggregation());
+    }
+
+    @Test
+    public void testRatchetIsNotFamily() {
+        ServiceState oldSS = new ServiceState();
+        ServiceState newSS = new ServiceState();
+
+        mBundle = mContextFixture.getCarrierConfigBundle();
+        mBundle.putStringArray(CarrierConfigManager.KEY_RATCHET_RAT_FAMILIES, new String[]{});
+
+        setNetworkRegistrationInfo(oldSS, TelephonyManager.NETWORK_TYPE_LTE_CA);
+        setNetworkRegistrationInfo(newSS, TelephonyManager.NETWORK_TYPE_LTE);
+
+        RatRatcheter ratRatcheter = new RatRatcheter(mPhone);
+        ratRatcheter.ratchet(oldSS, newSS, false);
+
+        assertFalse(newSS.isUsingCarrierAggregation());
+    }
 }
diff --git a/tests/telephonytests/src/com/android/internal/telephony/ServiceStateTest.java b/tests/telephonytests/src/com/android/internal/telephony/ServiceStateTest.java
index 60494a8..7bb6476 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/ServiceStateTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/ServiceStateTest.java
@@ -36,15 +36,6 @@
     @SmallTest
     public void testRoaming() {
         ServiceState ss = new ServiceState();
-        // add data registration state
-        NetworkRegistrationInfo nri = new NetworkRegistrationInfo.Builder()
-                .setDomain(NetworkRegistrationInfo.DOMAIN_PS)
-                .setTransportType(AccessNetworkConstants.TRANSPORT_TYPE_WWAN)
-                .setRegistrationState(NetworkRegistrationInfo.REGISTRATION_STATE_ROAMING)
-                .build();
-        ss.addNetworkRegistrationInfo(nri);
-
-        assertTrue(ss.getDataRoamingFromRegistration());
 
         ss.setCdmaDefaultRoamingIndicator(1);
         assertEquals(1, ss.getCdmaDefaultRoamingIndicator());
@@ -62,6 +53,9 @@
         assertTrue(ss.getDataRoaming());
         assertEquals(ServiceState.ROAMING_TYPE_DOMESTIC, ss.getDataRoamingType());
 
+        ss.setDataRoamingFromRegistration(true);
+        assertTrue(ss.getDataRoamingFromRegistration());
+
         ss.setVoiceRoamingType(ServiceState.ROAMING_TYPE_DOMESTIC);
         assertTrue(ss.getVoiceRoaming());
         assertEquals(ServiceState.ROAMING_TYPE_DOMESTIC, ss.getVoiceRoamingType());
@@ -72,10 +66,10 @@
         ServiceState ss = new ServiceState();
 
         ss.setDataRegState(ServiceState.STATE_IN_SERVICE);
-        assertEquals(ServiceState.STATE_IN_SERVICE, ss.getDataRegState());
+        assertEquals(ServiceState.STATE_IN_SERVICE, ss.getDataRegistrationState());
 
         ss.setVoiceRegState(ServiceState.STATE_IN_SERVICE);
-        assertEquals(ServiceState.STATE_IN_SERVICE, ss.getVoiceRegState());
+        assertEquals(ServiceState.STATE_IN_SERVICE, ss.getState());
     }
 
     @SmallTest
@@ -171,6 +165,7 @@
         rats.add(new Pair<Integer, Boolean>(ServiceState.RIL_RADIO_TECHNOLOGY_GSM, false));
         rats.add(new Pair<Integer, Boolean>(ServiceState.RIL_RADIO_TECHNOLOGY_TD_SCDMA, false));
         rats.add(new Pair<Integer, Boolean>(ServiceState.RIL_RADIO_TECHNOLOGY_IWLAN, false));
+        rats.add(new Pair<Integer, Boolean>(ServiceState.RIL_RADIO_TECHNOLOGY_NR, false));
 
         for (Pair<Integer, Boolean> rat : rats) {
             boolean isCdma = rat.second;
@@ -200,19 +195,13 @@
     public void testOperatorName() {
         ServiceState ss = new ServiceState();
 
-        ss.setDataOperatorAlphaLong("abc");
-        assertEquals("abc", ss.getDataOperatorAlphaLong());
-
-        ss.setDataOperatorName("def", "xyz", "123456");
-        assertEquals("xyz", ss.getDataOperatorAlphaShort());
+        ss.setOperatorAlphaLong("abc");
+        assertEquals("abc", ss.getOperatorAlphaLong());
 
         ss.setOperatorName("long", "short", "numeric");
-        assertEquals("long", ss.getVoiceOperatorAlphaLong());
-        assertEquals("short", ss.getVoiceOperatorAlphaShort());
-        assertEquals("numeric", ss.getVoiceOperatorNumeric());
-        assertEquals("long", ss.getDataOperatorAlphaLong());
-        assertEquals("short", ss.getDataOperatorAlphaShort());
-        assertEquals("numeric", ss.getDataOperatorNumeric());
+        assertEquals("long", ss.getOperatorAlphaLong());
+        assertEquals("short", ss.getOperatorAlphaShort());
+        assertEquals("numeric", ss.getOperatorNumeric());
         assertEquals("long", ss.getOperatorAlpha());
 
         ss.setOperatorName("", "short", "");
@@ -245,6 +234,7 @@
         ss.setDataRegState(ServiceState.STATE_OUT_OF_SERVICE);
         ss.setVoiceRoamingType(ServiceState.ROAMING_TYPE_INTERNATIONAL);
         ss.setDataRoamingType(ServiceState.ROAMING_TYPE_UNKNOWN);
+        ss.setDataRoamingFromRegistration(true);
         ss.setOperatorName("long", "short", "numeric");
         ss.setIsManualSelection(true);
         ss.setCssIndicator(1);
@@ -288,6 +278,7 @@
 
         ss.setVoiceRoamingType(ServiceState.ROAMING_TYPE_INTERNATIONAL);
         ss.setDataRoamingType(ServiceState.ROAMING_TYPE_UNKNOWN);
+        ss.setDataRoamingFromRegistration(true);
         ss.setOperatorName("long", "short", "numeric");
         ss.setIsManualSelection(true);
 
@@ -313,7 +304,7 @@
         NetworkRegistrationInfo wwanVoiceRegState = new NetworkRegistrationInfo(
                 NetworkRegistrationInfo.DOMAIN_CS, AccessNetworkConstants.TRANSPORT_TYPE_WWAN,
                 0, 0, 0, false,
-                null, null, true, 0, 0, 0);
+                null, null, "", true, 0, 0, 0);
 
         LteVopsSupportInfo lteVopsSupportInfo =
                 new LteVopsSupportInfo(LteVopsSupportInfo.LTE_STATUS_NOT_AVAILABLE,
@@ -344,7 +335,7 @@
 
         wwanDataRegState = new NetworkRegistrationInfo(
                 NetworkRegistrationInfo.DOMAIN_PS, AccessNetworkConstants.TRANSPORT_TYPE_WWAN,
-                0, 0, 0, true, null, null, 0, false, false, false, lteVopsSupportInfo, false);
+                0, 0, 0, true, null, null, "", 0, false, false, false, lteVopsSupportInfo, false);
         ss.addNetworkRegistrationInfo(wwanDataRegState);
         assertEquals(ss.getNetworkRegistrationInfo(NetworkRegistrationInfo.DOMAIN_PS,
                 AccessNetworkConstants.TRANSPORT_TYPE_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 6def71e..cc7c8e9 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/ServiceStateTrackerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/ServiceStateTrackerTest.java
@@ -16,8 +16,6 @@
 
 package com.android.internal.telephony;
 
-import static com.android.internal.telephony.TelephonyTestUtils.waitForMs;
-
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.junit.Assert.assertEquals;
@@ -58,6 +56,7 @@
 import android.os.PersistableBundle;
 import android.os.Process;
 import android.os.SystemClock;
+import android.os.TimestampedValue;
 import android.os.UserHandle;
 import android.os.WorkSource;
 import android.telephony.AccessNetworkConstants;
@@ -89,8 +88,8 @@
 import android.telephony.gsm.GsmCellLocation;
 import android.test.suitebuilder.annotation.MediumTest;
 import android.test.suitebuilder.annotation.SmallTest;
+import android.text.TextUtils;
 import android.util.Pair;
-import android.util.TimestampedValue;
 
 import androidx.test.filters.FlakyTest;
 
@@ -110,9 +109,11 @@
 
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Collections;
 import java.util.HashSet;
 import java.util.List;
 
+
 public class ServiceStateTrackerTest extends TelephonyTest {
     @Mock
     private ProxyController mProxyController;
@@ -150,7 +151,7 @@
 
     private static final int PHONE_ID = 0;
 
-    private static final String CARRIER_NAME_DISPLAY_NO_SERVICE = "no service";
+    private static final String CARRIER_NAME_DISPLAY_NO_SERVICE = "No service";
     private static final String CARRIER_NAME_DISPLAY_EMERGENCY_CALL = "emergency call";
     private static final String WIFI_CALLING_VOICE_FORMAT = "%s wifi calling";
     private static final String WIFI_CALLING_DATA_FORMAT = "%s wifi data";
@@ -258,12 +259,12 @@
         mSSTTestHandler = new ServiceStateTrackerTestHandler(getClass().getSimpleName());
         mSSTTestHandler.start();
         waitUntilReady();
-        waitForMs(600);
+        waitForLastHandlerAction(mSSTTestHandler.getThreadHandler());
 
         Intent intent = new Intent(CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED);
         intent.putExtra(CarrierConfigManager.EXTRA_SLOT_INDEX, 0);
         mContext.sendBroadcast(intent);
-        waitForMs(100);
+        waitForLastHandlerAction(mSSTTestHandler.getThreadHandler());
 
         // Override SPN related resource
         mContextFixture.putResource(
@@ -304,6 +305,27 @@
                 com.android.internal.R.array.config_display_no_service_when_sim_unready,
                 new String[0]);
 
+        mBundle.putIntArray(CarrierConfigManager.KEY_5G_NR_SSRSRP_THRESHOLDS_INT_ARRAY,
+                new int[] {
+                    -110, /* SIGNAL_STRENGTH_POOR */
+                    -90, /* SIGNAL_STRENGTH_MODERATE */
+                    -80, /* SIGNAL_STRENGTH_GOOD */
+                    -65,  /* SIGNAL_STRENGTH_GREAT */
+                });
+        mBundle.putIntArray(CarrierConfigManager.KEY_5G_NR_SSRSRQ_THRESHOLDS_INT_ARRAY,
+                new int[] {
+                    -16, /* SIGNAL_STRENGTH_POOR */
+                    -12, /* SIGNAL_STRENGTH_MODERATE */
+                    -9, /* SIGNAL_STRENGTH_GOOD */
+                    -6  /* SIGNAL_STRENGTH_GREAT */
+                });
+        mBundle.putIntArray(CarrierConfigManager.KEY_5G_NR_SSSINR_THRESHOLDS_INT_ARRAY,
+                new int[] {
+                    -5, /* SIGNAL_STRENGTH_POOR */
+                    5, /* SIGNAL_STRENGTH_MODERATE */
+                    15, /* SIGNAL_STRENGTH_GOOD */
+                    30  /* SIGNAL_STRENGTH_GREAT */
+                });
         logd("ServiceStateTrackerTest -Setup!");
     }
 
@@ -312,25 +334,69 @@
         sst = null;
         mSSTTestHandler.quit();
         mSSTTestHandler.join();
+        if (mCellularNetworkService != null) {
+            mCellularNetworkService.onDestroy();
+        }
         super.tearDown();
     }
 
+    private static String getPlmnFromCellIdentity(final CellIdentity ci) {
+        if (ci == null || ci instanceof CellIdentityCdma) return "";
+
+        final String mcc = ci.getMccString();
+        final String mnc = ci.getMncString();
+
+        if (TextUtils.isEmpty(mcc) || TextUtils.isEmpty(mnc)) return "";
+
+        return mcc + mnc;
+    }
+
     @Test
     @MediumTest
     public void testSetRadioPower() {
         boolean oldState = (mSimulatedCommands.getRadioState() == TelephonyManager.RADIO_POWER_ON);
         sst.setRadioPower(!oldState);
-        waitForMs(100);
+        waitForLastHandlerAction(mSSTTestHandler.getThreadHandler());
         assertTrue(oldState
                 != (mSimulatedCommands.getRadioState() == TelephonyManager.RADIO_POWER_ON));
     }
 
     @Test
+    @SmallTest
+    public void testSetRadioPowerOnForEmergencyCall() {
+        // Turn off radio first.
+        sst.setRadioPower(false);
+        waitForLastHandlerAction(mSSTTestHandler.getThreadHandler());
+        assertTrue(mSimulatedCommands.getRadioState() == TelephonyManager.RADIO_POWER_OFF);
+
+        // Turn on radio for emergency call.
+        sst.setRadioPower(true, true, true, false);
+        waitForLastHandlerAction(mSSTTestHandler.getThreadHandler());
+        assertTrue(mSimulatedCommands.mSetRadioPowerForEmergencyCall);
+        assertTrue(mSimulatedCommands.mSetRadioPowerAsSelectedPhoneForEmergencyCall);
+        assertTrue(mSimulatedCommands.getRadioState() == TelephonyManager.RADIO_POWER_ON);
+
+        // If we try again without forceApply=true, no command should be sent to modem. Because
+        // radio power is already ON.
+        sst.setRadioPower(true, false, false, false);
+        waitForLastHandlerAction(mSSTTestHandler.getThreadHandler());
+        assertTrue(mSimulatedCommands.mSetRadioPowerForEmergencyCall);
+        assertTrue(mSimulatedCommands.mSetRadioPowerAsSelectedPhoneForEmergencyCall);
+
+        // Call setRadioPower on with forceApply=true. ForEmergencyCall and isSelectedPhone should
+        // be cleared.
+        sst.setRadioPower(true, false, false, true);
+        waitForLastHandlerAction(mSSTTestHandler.getThreadHandler());
+        assertFalse(mSimulatedCommands.mSetRadioPowerForEmergencyCall);
+        assertFalse(mSimulatedCommands.mSetRadioPowerAsSelectedPhoneForEmergencyCall);
+    }
+
+    @Test
     @MediumTest
     public void testSetRadioPowerFromCarrier() {
         // Carrier disable radio power
         sst.setRadioPowerFromCarrier(false);
-        waitForMs(100);
+        waitForLastHandlerAction(mSSTTestHandler.getThreadHandler());
         assertFalse(mSimulatedCommands.getRadioState()
                 == TelephonyManager.RADIO_POWER_ON);
         assertTrue(sst.getDesiredPowerState());
@@ -338,7 +404,7 @@
 
         // User toggle radio power will not overrides carrier settings
         sst.setRadioPower(true);
-        waitForMs(100);
+        waitForLastHandlerAction(mSSTTestHandler.getThreadHandler());
         assertFalse(mSimulatedCommands.getRadioState()
                 == TelephonyManager.RADIO_POWER_ON);
         assertTrue(sst.getDesiredPowerState());
@@ -346,7 +412,7 @@
 
         // Carrier re-enable radio power
         sst.setRadioPowerFromCarrier(true);
-        waitForMs(100);
+        waitForLastHandlerAction(mSSTTestHandler.getThreadHandler());
         assertTrue(mSimulatedCommands.getRadioState() == TelephonyManager.RADIO_POWER_ON);
         assertTrue(sst.getDesiredPowerState());
         assertTrue(sst.getPowerStateFromCarrier());
@@ -354,7 +420,7 @@
         // User toggle radio power off (airplane mode) and set carrier on
         sst.setRadioPower(false);
         sst.setRadioPowerFromCarrier(true);
-        waitForMs(100);
+        waitForLastHandlerAction(mSSTTestHandler.getThreadHandler());
         assertFalse(mSimulatedCommands.getRadioState()
                 == TelephonyManager.RADIO_POWER_ON);
         assertFalse(sst.getDesiredPowerState());
@@ -374,9 +440,9 @@
                 mSimulatedCommands.getGetNetworkSelectionModeCallCount();
         sst.setRadioPower(false);
 
-        waitForMs(500);
+        waitForLastHandlerAction(mSSTTestHandler.getThreadHandler());
         sst.pollState();
-        waitForMs(250);
+        waitForLastHandlerAction(mSSTTestHandler.getThreadHandler());
 
         // This test was meant to be for *no* ril traffic. However, RADIO_STATE_CHANGED is
         // considered a modem triggered action and that causes a pollState() to be done
@@ -391,7 +457,7 @@
         // Note that if the poll is triggered by a network change notification
         // and the modem is supposed to be off, we should still do the poll
         mSimulatedCommands.notifyNetworkStateChanged();
-        waitForMs(250);
+        waitForLastHandlerAction(mSSTTestHandler.getThreadHandler());
 
         assertEquals(getOperatorCallCount + 2 , mSimulatedCommands.getGetOperatorCallCount());
         assertEquals(getDataRegistrationStateCallCount + 2,
@@ -413,35 +479,35 @@
 
         sst.sendMessage(sst.obtainMessage(ServiceStateTracker.EVENT_NETWORK_STATE_CHANGED, null));
 
-        waitForMs(750);
+        waitForLastHandlerAction(mSSTTestHandler.getThreadHandler());
 
         ArgumentCaptor<Intent> intentArgumentCaptor = ArgumentCaptor.forClass(Intent.class);
         verify(mContextFixture.getTestDouble(), times(3))
                 .sendStickyBroadcastAsUser(intentArgumentCaptor.capture(), eq(UserHandle.ALL));
 
-        // We only want to verify the intent SPN_STRINGS_UPDATED_ACTION.
+        // We only want to verify the intent SERVICE_PROVIDERS_UPDATED.
         List<Intent> intents = intentArgumentCaptor.getAllValues();
         logd("Total " + intents.size() + " intents");
         for (Intent intent : intents) {
             logd("  " + intent.getAction());
         }
         Intent intent = intents.get(2);
-        assertEquals(TelephonyIntents.SPN_STRINGS_UPDATED_ACTION, intent.getAction());
+        assertEquals(TelephonyManager.ACTION_SERVICE_PROVIDERS_UPDATED, intent.getAction());
 
         Bundle b = intent.getExtras();
 
         // For boolean we need to make sure the key exists first
-        assertTrue(b.containsKey(TelephonyIntents.EXTRA_SHOW_SPN));
-        assertFalse(b.getBoolean(TelephonyIntents.EXTRA_SHOW_SPN));
+        assertTrue(b.containsKey(TelephonyManager.EXTRA_SHOW_SPN));
+        assertFalse(b.getBoolean(TelephonyManager.EXTRA_SHOW_SPN));
 
-        assertEquals(null, b.getString(TelephonyIntents.EXTRA_SPN));
-        assertEquals(null, b.getString(TelephonyIntents.EXTRA_DATA_SPN));
+        assertEquals(null, b.getString(TelephonyManager.EXTRA_SPN));
+        assertEquals(null, b.getString(TelephonyManager.EXTRA_DATA_SPN));
 
         // For boolean we need to make sure the key exists first
-        assertTrue(b.containsKey(TelephonyIntents.EXTRA_SHOW_PLMN));
-        assertTrue(b.getBoolean(TelephonyIntents.EXTRA_SHOW_PLMN));
+        assertTrue(b.containsKey(TelephonyManager.EXTRA_SHOW_PLMN));
+        assertTrue(b.getBoolean(TelephonyManager.EXTRA_SHOW_PLMN));
 
-        assertEquals(SimulatedCommands.FAKE_LONG_NAME, b.getString(TelephonyIntents.EXTRA_PLMN));
+        assertEquals(SimulatedCommands.FAKE_LONG_NAME, b.getString(TelephonyManager.EXTRA_PLMN));
 
         ArgumentCaptor<Integer> intArgumentCaptor = ArgumentCaptor.forClass(Integer.class);
         verify(mTelephonyManager).setDataNetworkTypeForPhone(anyInt(), intArgumentCaptor.capture());
@@ -451,7 +517,8 @@
 
     private CellInfoGsm getCellInfoGsm() {
         CellInfoGsm tmp = new CellInfoGsm();
-        tmp.setCellIdentity(new CellIdentityGsm(0, 1, 900, 5, "001", "01", "test", "tst"));
+        tmp.setCellIdentity(new CellIdentityGsm(0, 1, 900, 5, "001", "01", "test", "tst",
+                    Collections.emptyList()));
         tmp.setCellSignalStrength(new CellSignalStrengthGsm(-85, 2, 3));
         return tmp;
     }
@@ -468,7 +535,7 @@
 
         // null worksource and no response message will update the writethrough cache
         sst.requestAllCellInfo(null, null);
-        waitForMs(200);
+        waitForLastHandlerAction(mSSTTestHandler.getThreadHandler());
         assertEquals(sst.getAllCellInfo(), list);
     }
 
@@ -537,7 +604,7 @@
         mSimulatedCommands.setImsRegistrationState(new int[]{1, PhoneConstants.PHONE_TYPE_GSM});
 
         sst.sendMessage(sst.obtainMessage(ServiceStateTracker.EVENT_IMS_STATE_CHANGED, null));
-        waitForMs(200);
+        waitForLastHandlerAction(mSSTTestHandler.getThreadHandler());
 
         assertTrue(sst.isImsRegistered());
 
@@ -545,7 +612,7 @@
         mSimulatedCommands.setImsRegistrationState(new int[]{0, PhoneConstants.PHONE_TYPE_GSM});
 
         sst.sendMessage(sst.obtainMessage(ServiceStateTracker.EVENT_IMS_STATE_CHANGED, null));
-        waitForMs(200);
+        waitForLastHandlerAction(mSSTTestHandler.getThreadHandler());
 
         assertFalse(sst.isImsRegistered());
     }
@@ -558,7 +625,7 @@
 
         sst.mSS = ss;
         sst.sendMessage(sst.obtainMessage(ServiceStateTracker.EVENT_IMS_SERVICE_STATE_CHANGED));
-        waitForMs(200);
+        waitForLastHandlerAction(mSSTTestHandler.getThreadHandler());
 
         // The listener will be notified that the service state was changed.
         verify(mPhone).notifyServiceStateChanged(any(ServiceState.class));
@@ -569,7 +636,7 @@
         sst.mSS = ss;
 
         sst.sendMessage(sst.obtainMessage(ServiceStateTracker.EVENT_IMS_SERVICE_STATE_CHANGED));
-        waitForMs(200);
+        waitForLastHandlerAction(mSSTTestHandler.getThreadHandler());
 
         // Nothing happened because the IMS service state was not affected the merged service state.
         verify(mPhone, times(1)).notifyServiceStateChanged(any(ServiceState.class));
@@ -578,7 +645,7 @@
     private void sendSignalStrength(SignalStrength ss) {
         mSimulatedCommands.setSignalStrength(ss);
         mSimulatedCommands.notifySignalStrength();
-        waitForMs(300);
+        waitForLastHandlerAction(mSSTTestHandler.getThreadHandler());
     }
 
     @Test
@@ -636,7 +703,7 @@
         Intent intent = new Intent().setAction(CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED);
         intent.putExtra(CarrierConfigManager.EXTRA_SLOT_INDEX, PHONE_ID);
         mContext.sendBroadcast(intent);
-        waitForMs(300);
+        waitForLastHandlerAction(mSSTTestHandler.getThreadHandler());
     }
 
     @Test
@@ -662,7 +729,7 @@
 
         mSimulatedCommands.setSignalStrength(ss);
         mSimulatedCommands.notifySignalStrength();
-        waitForMs(300);
+        waitForLastHandlerAction(mSSTTestHandler.getThreadHandler());
         // Default thresholds are POOR=-115 MODERATE=-105 GOOD=-95 GREAT=-85
         assertEquals(CellSignalStrength.SIGNAL_STRENGTH_POOR, sst.getSignalStrength().getLevel());
 
@@ -678,12 +745,111 @@
 
         mSimulatedCommands.setSignalStrength(ss);
         mSimulatedCommands.notifySignalStrength();
-        waitForMs(300);
+        waitForLastHandlerAction(mSSTTestHandler.getThreadHandler());
         assertEquals(sst.getSignalStrength().getLevel(),
                 CellSignalStrength.SIGNAL_STRENGTH_MODERATE);
     }
 
     @Test
+    public void test5gNrSignalStrengthReportingCriteria_UseSsRsrp() {
+        SignalStrength ss = new SignalStrength(
+                new CellSignalStrengthCdma(),
+                new CellSignalStrengthGsm(),
+                new CellSignalStrengthWcdma(),
+                new CellSignalStrengthTdscdma(),
+                new CellSignalStrengthLte(),
+                new CellSignalStrengthNr(
+                    -139, /** csiRsrp NONE */
+                    -20, /** csiRsrq NONE */
+                    -23, /** CsiSinr NONE */
+                    -44, /** SsRsrp SIGNAL_STRENGTH_GREAT */
+                    -20, /** SsRsrq NONE */
+                    -23) /** SsSinr NONE */
+         );
+
+        // SSRSRP = 1 << 0
+        mBundle.putInt(CarrierConfigManager.KEY_PARAMETERS_USE_FOR_5G_NR_SIGNAL_BAR_INT,
+                CellSignalStrengthNr.USE_SSRSRP);
+        sendCarrierConfigUpdate();
+        mSimulatedCommands.setSignalStrength(ss);
+        mSimulatedCommands.notifySignalStrength();
+        waitForLastHandlerAction(mSSTTestHandler.getThreadHandler());
+        assertEquals(CellSignalStrength.SIGNAL_STRENGTH_GREAT, sst.getSignalStrength().getLevel());
+    }
+
+    @Test
+    public void test5gNrSignalStrengthReportingCriteria_UseSsRsrpAndSsRsrq() {
+        SignalStrength ss = new SignalStrength(
+                new CellSignalStrengthCdma(),
+                new CellSignalStrengthGsm(),
+                new CellSignalStrengthWcdma(),
+                new CellSignalStrengthTdscdma(),
+                new CellSignalStrengthLte(),
+                new CellSignalStrengthNr(
+                    -139, /** csiRsrp NONE */
+                    -20, /** csiRsrq NONE */
+                    -23, /** CsiSinr NONE */
+                    -44, /** SsRsrp SIGNAL_STRENGTH_GREAT */
+                    -20, /** SsRsrq NONE */
+                    -23) /** SsSinr NONE */
+        );
+
+        // SSRSRP = 1 << 0 | SSSINR = 1 << 2
+        mBundle.putInt(CarrierConfigManager.KEY_PARAMETERS_USE_FOR_5G_NR_SIGNAL_BAR_INT,
+                CellSignalStrengthNr.USE_SSRSRP | CellSignalStrengthNr.USE_SSRSRQ);
+        sendCarrierConfigUpdate();
+        mSimulatedCommands.setSignalStrength(ss);
+        mSimulatedCommands.notifySignalStrength();
+        waitForLastHandlerAction(mSSTTestHandler.getThreadHandler());
+        assertEquals(CellSignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN,
+                sst.getSignalStrength().getLevel());
+    }
+
+    @Test
+    public void test5gNrSignalStrengthReportingCriteria_ConfiguredThresholds() {
+        SignalStrength ss = new SignalStrength(
+                new CellSignalStrengthCdma(),
+                new CellSignalStrengthGsm(),
+                new CellSignalStrengthWcdma(),
+                new CellSignalStrengthTdscdma(),
+                new CellSignalStrengthLte(),
+                new CellSignalStrengthNr(
+                    -139, /** csiRsrp NONE */
+                    -20, /** csiRsrq NONE */
+                    -23, /** CsiSinr NONE */
+                    -44, /** SsRsrp SIGNAL_STRENGTH_GREAT */
+                    -20, /** SsRsrq NONE */
+                    -23) /** SsSinr NONE */
+        );
+
+        // SSRSRP = 1 << 0
+        mBundle.putInt(CarrierConfigManager.KEY_PARAMETERS_USE_FOR_5G_NR_SIGNAL_BAR_INT,
+                CellSignalStrengthNr.USE_SSRSRP);
+        sendCarrierConfigUpdate();
+        mSimulatedCommands.setSignalStrength(ss);
+        mSimulatedCommands.notifySignalStrength();
+        waitForLastHandlerAction(mSSTTestHandler.getThreadHandler());
+        assertEquals(CellSignalStrength.SIGNAL_STRENGTH_GREAT, sst.getSignalStrength().getLevel());
+
+        int[] nrSsRsrpThresholds = {
+                -45, // SIGNAL_STRENGTH_POOR
+                -40, // SIGNAL_STRENGTH_MODERATE
+                -37, // SIGNAL_STRENGTH_GOOD
+                -34,  // SIGNAL_STRENGTH_GREAT
+        };
+        mBundle.putIntArray(CarrierConfigManager.KEY_5G_NR_SSRSRP_THRESHOLDS_INT_ARRAY,
+                nrSsRsrpThresholds);
+        mBundle.putInt(CarrierConfigManager.KEY_PARAMETERS_USE_FOR_5G_NR_SIGNAL_BAR_INT,
+                CellSignalStrengthNr.USE_SSRSRP);
+        sendCarrierConfigUpdate();
+        mSimulatedCommands.setSignalStrength(ss);
+        mSimulatedCommands.notifySignalStrength();
+        waitForLastHandlerAction(mSSTTestHandler.getThreadHandler());
+        assertEquals(CellSignalStrength.SIGNAL_STRENGTH_POOR,
+                sst.getSignalStrength().getLevel());
+    }
+
+    @Test
     public void testWcdmaSignalStrengthReportingCriteria() {
         SignalStrength ss = new SignalStrength(
                 new CellSignalStrengthCdma(),
@@ -695,7 +861,7 @@
 
         mSimulatedCommands.setSignalStrength(ss);
         mSimulatedCommands.notifySignalStrength();
-        waitForMs(300);
+        waitForLastHandlerAction(mSSTTestHandler.getThreadHandler());
         assertEquals(sst.getSignalStrength().getLevel(), CellSignalStrength.SIGNAL_STRENGTH_GOOD);
 
         int[] wcdmaThresholds = {
@@ -712,7 +878,7 @@
         sendCarrierConfigUpdate();
         mSimulatedCommands.setSignalStrength(ss);
         mSimulatedCommands.notifySignalStrength();
-        waitForMs(300);
+        waitForLastHandlerAction(mSSTTestHandler.getThreadHandler());
         assertEquals(sst.getSignalStrength().getLevel(), CellSignalStrength.SIGNAL_STRENGTH_GOOD);
     }
 
@@ -721,21 +887,22 @@
     // TODO(nharold): we probably should remove support for this procedure (GET_LOC)
     public void testGsmCellLocation() {
         CellIdentityGsm cellIdentityGsm = new CellIdentityGsm(
-                2, 3, 900, 5, "001", "01", "test", "tst");
+                2, 3, 900, 5, "001", "01", "test", "tst", Collections.emptyList());
 
         NetworkRegistrationInfo result = new NetworkRegistrationInfo.Builder()
                 .setDomain(NetworkRegistrationInfo.DOMAIN_CS)
                 .setTransportType(AccessNetworkConstants.TRANSPORT_TYPE_WWAN)
                 .setRegistrationState(NetworkRegistrationInfo.REGISTRATION_STATE_HOME)
                 .setCellIdentity(cellIdentityGsm)
+                .setRegisteredPlmn("00101")
                 .build();
 
         sst.sendMessage(sst.obtainMessage(ServiceStateTracker.EVENT_GET_LOC_DONE,
                 new AsyncResult(null, result, null)));
 
-        waitForMs(200);
+        waitForLastHandlerAction(mSSTTestHandler.getThreadHandler());
         WorkSource workSource = new WorkSource(Process.myUid(), mContext.getPackageName());
-        GsmCellLocation cl = (GsmCellLocation) sst.getCellLocation();
+        GsmCellLocation cl = (GsmCellLocation) sst.getCellIdentity().asCellLocation();
         assertEquals(2, cl.getLac());
         assertEquals(3, cl.getCid());
     }
@@ -757,14 +924,34 @@
         sst.sendMessage(sst.obtainMessage(ServiceStateTracker.EVENT_GET_LOC_DONE,
                 new AsyncResult(null, result, null)));
 
-        waitForMs(200);
+        waitForLastHandlerAction(mSSTTestHandler.getThreadHandler());
         WorkSource workSource = new WorkSource(Process.myUid(), mContext.getPackageName());
-        CdmaCellLocation cl = (CdmaCellLocation) sst.getCellLocation();
+        CdmaCellLocation cl = (CdmaCellLocation) sst.getCellIdentity().asCellLocation();
         assertEquals(5, cl.getBaseStationLatitude());
         assertEquals(4, cl.getBaseStationLongitude());
     }
 
     @Test
+    public void testHasLocationChanged() {
+        CellIdentityCdma cellIdentity = null;
+        CellIdentityCdma newCellIdentity = null;
+
+        boolean hasLocationChanged = (cellIdentity == null ? newCellIdentity != null
+                : !cellIdentity.isSameCell(newCellIdentity));
+        assertFalse(hasLocationChanged);
+
+        cellIdentity = new CellIdentityCdma(1, 2, 3, 4, 5, "test", "tst");
+        hasLocationChanged = (cellIdentity == null ? newCellIdentity != null
+                : !cellIdentity.isSameCell(newCellIdentity));
+        assertTrue(hasLocationChanged);
+
+        newCellIdentity = new CellIdentityCdma(1, 2, 3, 4, 5, "test", "tst");
+        hasLocationChanged = (cellIdentity == null ? newCellIdentity != null
+                : !cellIdentity.isSameCell(newCellIdentity));
+        assertFalse(hasLocationChanged);
+    }
+
+    @Test
     @MediumTest
     public void testUpdatePhoneType() {
         doReturn(false).when(mPhone).isPhoneTypeGsm();
@@ -785,7 +972,7 @@
         msg.what = integerArgumentCaptor.getValue();
         msg.obj = new AsyncResult(null, null, null);
         sst.sendMessage(msg);
-        waitForMs(100);
+        waitForLastHandlerAction(mSSTTestHandler.getThreadHandler());
 
         // on RUIM_RECORDS_LOADED, sst is expected to call following apis
         verify(mRuimRecords, times(1)).isProvisioned();
@@ -803,7 +990,7 @@
         // There's no easy way to check if the msg was handled or discarded. Wait to make sure sst
         // did not crash, and then verify that the functions called records loaded are not called
         // again
-        waitForMs(200);
+        waitForLastHandlerAction(mSSTTestHandler.getThreadHandler());
 
         verify(mRuimRecords, times(1)).isProvisioned();
     }
@@ -819,7 +1006,7 @@
         mSimulatedCommands.setDataRegState(NetworkRegistrationInfo.REGISTRATION_STATE_ROAMING);
         mSimulatedCommands.notifyNetworkStateChanged();
 
-        waitForMs(200);
+        waitForLastHandlerAction(mSSTTestHandler.getThreadHandler());
 
         // verify if registered handler has message posted to it
         ArgumentCaptor<Message> messageArgumentCaptor = ArgumentCaptor.forClass(Message.class);
@@ -831,7 +1018,7 @@
         mSimulatedCommands.setDataRegState(NetworkRegistrationInfo.REGISTRATION_STATE_HOME);
         mSimulatedCommands.notifyNetworkStateChanged();
 
-        waitForMs(100);
+        waitForLastHandlerAction(mSSTTestHandler.getThreadHandler());
 
         // Unregister registrant
         sst.unregisterForVoiceRoamingOn(mTestHandler);
@@ -841,7 +1028,7 @@
         mSimulatedCommands.setDataRegState(NetworkRegistrationInfo.REGISTRATION_STATE_ROAMING);
         mSimulatedCommands.notifyNetworkStateChanged();
 
-        waitForMs(200);
+        waitForLastHandlerAction(mSSTTestHandler.getThreadHandler());
 
         // verify that no new message posted to handler
         verify(mTestHandler, times(1)).sendMessageAtTime(any(Message.class), anyLong());
@@ -856,7 +1043,7 @@
         mSimulatedCommands.setDataRegState(NetworkRegistrationInfo.REGISTRATION_STATE_ROAMING);
         mSimulatedCommands.notifyNetworkStateChanged();
 
-        waitForMs(100);
+        waitForLastHandlerAction(mSSTTestHandler.getThreadHandler());
 
         sst.registerForVoiceRoamingOff(mTestHandler, EVENT_DATA_ROAMING_OFF, null);
 
@@ -866,7 +1053,7 @@
         mSimulatedCommands.setDataRegState(NetworkRegistrationInfo.REGISTRATION_STATE_HOME);
         mSimulatedCommands.notifyNetworkStateChanged();
 
-        waitForMs(200);
+        waitForLastHandlerAction(mSSTTestHandler.getThreadHandler());
 
         // verify if registered handler has message posted to it
         ArgumentCaptor<Message> messageArgumentCaptor = ArgumentCaptor.forClass(Message.class);
@@ -878,7 +1065,7 @@
         mSimulatedCommands.setDataRegState(NetworkRegistrationInfo.REGISTRATION_STATE_ROAMING);
         mSimulatedCommands.notifyNetworkStateChanged();
 
-        waitForMs(100);
+        waitForLastHandlerAction(mSSTTestHandler.getThreadHandler());
 
         // Unregister registrant
         sst.unregisterForVoiceRoamingOff(mTestHandler);
@@ -888,7 +1075,7 @@
         mSimulatedCommands.setDataRegState(NetworkRegistrationInfo.REGISTRATION_STATE_HOME);
         mSimulatedCommands.notifyNetworkStateChanged();
 
-        waitForMs(100);
+        waitForLastHandlerAction(mSSTTestHandler.getThreadHandler());
 
         // verify that no new message posted to handler
         verify(mTestHandler, times(1)).sendMessageAtTime(any(Message.class), anyLong());
@@ -905,7 +1092,7 @@
         mSimulatedCommands.setDataRegState(NetworkRegistrationInfo.REGISTRATION_STATE_ROAMING);
         mSimulatedCommands.notifyNetworkStateChanged();
 
-        waitForMs(200);
+        waitForLastHandlerAction(mSSTTestHandler.getThreadHandler());
 
         // verify if registered handler has message posted to it
         ArgumentCaptor<Message> messageArgumentCaptor = ArgumentCaptor.forClass(Message.class);
@@ -917,7 +1104,7 @@
         mSimulatedCommands.setDataRegState(NetworkRegistrationInfo.REGISTRATION_STATE_HOME);
         mSimulatedCommands.notifyNetworkStateChanged();
 
-        waitForMs(100);
+        waitForLastHandlerAction(mSSTTestHandler.getThreadHandler());
 
         // Unregister registrant
         sst.unregisterForDataRoamingOn(mTestHandler);
@@ -927,7 +1114,7 @@
         mSimulatedCommands.setDataRegState(NetworkRegistrationInfo.REGISTRATION_STATE_ROAMING);
         mSimulatedCommands.notifyNetworkStateChanged();
 
-        waitForMs(200);
+        waitForLastHandlerAction(mSSTTestHandler.getThreadHandler());
 
         // verify that no new message posted to handler
         verify(mTestHandler, times(1)).sendMessageAtTime(any(Message.class), anyLong());
@@ -942,7 +1129,7 @@
         mSimulatedCommands.setDataRegState(NetworkRegistrationInfo.REGISTRATION_STATE_ROAMING);
         mSimulatedCommands.notifyNetworkStateChanged();
 
-        waitForMs(100);
+        waitForLastHandlerAction(mSSTTestHandler.getThreadHandler());
 
         sst.registerForDataRoamingOff(mTestHandler, EVENT_DATA_ROAMING_OFF, null, true);
 
@@ -952,7 +1139,7 @@
         mSimulatedCommands.setDataRegState(NetworkRegistrationInfo.REGISTRATION_STATE_HOME);
         mSimulatedCommands.notifyNetworkStateChanged();
 
-        waitForMs(100);
+        waitForLastHandlerAction(mSSTTestHandler.getThreadHandler());
 
         // verify if registered handler has message posted to it
         ArgumentCaptor<Message> messageArgumentCaptor = ArgumentCaptor.forClass(Message.class);
@@ -964,7 +1151,7 @@
         mSimulatedCommands.setDataRegState(NetworkRegistrationInfo.REGISTRATION_STATE_ROAMING);
         mSimulatedCommands.notifyNetworkStateChanged();
 
-        waitForMs(100);
+        waitForLastHandlerAction(mSSTTestHandler.getThreadHandler());
 
         // Unregister registrant
         sst.unregisterForDataRoamingOff(mTestHandler);
@@ -974,7 +1161,7 @@
         mSimulatedCommands.setDataRegState(NetworkRegistrationInfo.REGISTRATION_STATE_HOME);
         mSimulatedCommands.notifyNetworkStateChanged();
 
-        waitForMs(100);
+        waitForLastHandlerAction(mSSTTestHandler.getThreadHandler());
 
         // verify that no new message posted to handler
         verify(mTestHandler, times(1)).sendMessageAtTime(any(Message.class), anyLong());
@@ -989,7 +1176,7 @@
         mSimulatedCommands.setDataRegState(23);
         mSimulatedCommands.notifyNetworkStateChanged();
 
-        waitForMs(100);
+        waitForLastHandlerAction(mSSTTestHandler.getThreadHandler());
 
         sst.registerForDataConnectionAttached(AccessNetworkConstants.TRANSPORT_TYPE_WWAN,
                 mTestHandler, EVENT_DATA_CONNECTION_ATTACHED, null);
@@ -999,7 +1186,7 @@
         mSimulatedCommands.setDataRegState(NetworkRegistrationInfo.REGISTRATION_STATE_ROAMING);
         mSimulatedCommands.notifyNetworkStateChanged();
 
-        waitForMs(200);
+        waitForLastHandlerAction(mSSTTestHandler.getThreadHandler());
 
         // verify if registered handler has message posted to it
         ArgumentCaptor<Message> messageArgumentCaptor = ArgumentCaptor.forClass(Message.class);
@@ -1011,7 +1198,7 @@
         mSimulatedCommands.setDataRegState(-1);
         mSimulatedCommands.notifyNetworkStateChanged();
 
-        waitForMs(100);
+        waitForLastHandlerAction(mSSTTestHandler.getThreadHandler());
 
         // Unregister registrant
         sst.unregisterForDataConnectionAttached(AccessNetworkConstants.TRANSPORT_TYPE_WWAN,
@@ -1022,7 +1209,7 @@
         mSimulatedCommands.setDataRegState(NetworkRegistrationInfo.REGISTRATION_STATE_ROAMING);
         mSimulatedCommands.notifyNetworkStateChanged();
 
-        waitForMs(100);
+        waitForLastHandlerAction(mSSTTestHandler.getThreadHandler());
 
         // verify that no new message posted to handler
         verify(mTestHandler, times(1)).sendMessageAtTime(any(Message.class), anyLong());
@@ -1037,7 +1224,7 @@
         mSimulatedCommands.setDataRegState(NetworkRegistrationInfo.REGISTRATION_STATE_UNKNOWN);
         mSimulatedCommands.notifyNetworkStateChanged();
 
-        waitForMs(100);
+        waitForLastHandlerAction(mSSTTestHandler.getThreadHandler());
 
         sst.registerForDataConnectionAttached(AccessNetworkConstants.TRANSPORT_TYPE_WWAN,
                 mTestHandler, EVENT_DATA_CONNECTION_ATTACHED, null);
@@ -1047,7 +1234,7 @@
         mSimulatedCommands.setDataRegState(NetworkRegistrationInfo.REGISTRATION_STATE_ROAMING);
         mSimulatedCommands.notifyNetworkStateChanged();
 
-        waitForMs(200);
+        waitForLastHandlerAction(mSSTTestHandler.getThreadHandler());
 
         // verify if registered handler has message posted to it
         ArgumentCaptor<Message> messageArgumentCaptor = ArgumentCaptor.forClass(Message.class);
@@ -1059,7 +1246,7 @@
         mSimulatedCommands.setDataRegState(NetworkRegistrationInfo.REGISTRATION_STATE_UNKNOWN);
         mSimulatedCommands.notifyNetworkStateChanged();
 
-        waitForMs(100);
+        waitForLastHandlerAction(mSSTTestHandler.getThreadHandler());
 
         // Unregister registrant
         sst.unregisterForDataConnectionAttached(AccessNetworkConstants.TRANSPORT_TYPE_WWAN,
@@ -1070,7 +1257,7 @@
         mSimulatedCommands.setDataRegState(NetworkRegistrationInfo.REGISTRATION_STATE_ROAMING);
         mSimulatedCommands.notifyNetworkStateChanged();
 
-        waitForMs(100);
+        waitForLastHandlerAction(mSSTTestHandler.getThreadHandler());
 
         // verify that no new message posted to handler
         verify(mTestHandler, times(1)).sendMessageAtTime(any(Message.class), anyLong());
@@ -1093,7 +1280,7 @@
         mSimulatedCommands.setDataRegState(NetworkRegistrationInfo.REGISTRATION_STATE_UNKNOWN);
         mSimulatedCommands.notifyNetworkStateChanged();
 
-        waitForMs(200);
+        waitForLastHandlerAction(mSSTTestHandler.getThreadHandler());
 
         // verify if registered handler has message posted to it
         ArgumentCaptor<Message> messageArgumentCaptor = ArgumentCaptor.forClass(Message.class);
@@ -1105,7 +1292,7 @@
         mSimulatedCommands.setDataRegState(NetworkRegistrationInfo.REGISTRATION_STATE_ROAMING);
         mSimulatedCommands.notifyNetworkStateChanged();
 
-        waitForMs(100);
+        waitForLastHandlerAction(mSSTTestHandler.getThreadHandler());
 
         // Unregister registrant
         sst.unregisterForDataConnectionDetached(AccessNetworkConstants.TRANSPORT_TYPE_WWAN,
@@ -1116,7 +1303,7 @@
         mSimulatedCommands.setDataRegState(NetworkRegistrationInfo.REGISTRATION_STATE_UNKNOWN);
         mSimulatedCommands.notifyNetworkStateChanged();
 
-        waitForMs(100);
+        waitForLastHandlerAction(mSSTTestHandler.getThreadHandler());
 
         // verify that no new message posted to handler
         verify(mTestHandler, times(1)).sendMessageAtTime(any(Message.class), anyLong());
@@ -1134,7 +1321,7 @@
 
         sst.registerForVoiceRegStateOrRatChanged(mTestHandler, EVENT_VOICE_RAT_CHANGED, null);
 
-        waitForMs(100);
+        waitForLastHandlerAction(mSSTTestHandler.getThreadHandler());
 
         // Verify if message was posted to handler and value of result
         ArgumentCaptor<Message> messageArgumentCaptor = ArgumentCaptor.forClass(Message.class);
@@ -1158,7 +1345,7 @@
         sst.registerForDataRegStateOrRatChanged(AccessNetworkConstants.TRANSPORT_TYPE_WWAN,
                 mTestHandler, EVENT_DATA_RAT_CHANGED, null);
 
-        waitForMs(100);
+        waitForLastHandlerAction(mSSTTestHandler.getThreadHandler());
 
         // Verify if message was posted to handler and value of result
         ArgumentCaptor<Message> messageArgumentCaptor = ArgumentCaptor.forClass(Message.class);
@@ -1179,7 +1366,7 @@
         mSimulatedCommands.setDataRegState(NetworkRegistrationInfo.REGISTRATION_STATE_UNKNOWN);
         mSimulatedCommands.notifyNetworkStateChanged();
 
-        waitForMs(100);
+        waitForLastHandlerAction(mSSTTestHandler.getThreadHandler());
 
         sst.registerForNetworkAttached(mTestHandler, EVENT_REGISTERED_TO_NETWORK, null);
 
@@ -1188,7 +1375,7 @@
         mSimulatedCommands.setDataRegState(NetworkRegistrationInfo.REGISTRATION_STATE_ROAMING);
         mSimulatedCommands.notifyNetworkStateChanged();
 
-        waitForMs(100);
+        waitForLastHandlerAction(mSSTTestHandler.getThreadHandler());
 
         // verify if registered handler has message posted to it
         ArgumentCaptor<Message> messageArgumentCaptor = ArgumentCaptor.forClass(Message.class);
@@ -1200,7 +1387,7 @@
         mSimulatedCommands.setDataRegState(NetworkRegistrationInfo.REGISTRATION_STATE_UNKNOWN);
         mSimulatedCommands.notifyNetworkStateChanged();
 
-        waitForMs(100);
+        waitForLastHandlerAction(mSSTTestHandler.getThreadHandler());
 
         // Unregister registrant
         sst.unregisterForNetworkAttached(mTestHandler);
@@ -1210,7 +1397,7 @@
         mSimulatedCommands.setDataRegState(NetworkRegistrationInfo.REGISTRATION_STATE_ROAMING);
         mSimulatedCommands.notifyNetworkStateChanged();
 
-        waitForMs(100);
+        waitForLastHandlerAction(mSSTTestHandler.getThreadHandler());
 
         // verify that no new message posted to handler
         verify(mTestHandler, times(1)).sendMessageAtTime(any(Message.class), anyLong());
@@ -1225,7 +1412,7 @@
         mSimulatedCommands.setDataRegState(23);
         mSimulatedCommands.notifyNetworkStateChanged();
 
-        waitForMs(100);
+        waitForLastHandlerAction(mSSTTestHandler.getThreadHandler());
 
         sst.registerForNetworkAttached(mTestHandler, EVENT_REGISTERED_TO_NETWORK, null);
 
@@ -1234,7 +1421,7 @@
         mSimulatedCommands.setDataRegState(NetworkRegistrationInfo.REGISTRATION_STATE_ROAMING);
         mSimulatedCommands.notifyNetworkStateChanged();
 
-        waitForMs(100);
+        waitForLastHandlerAction(mSSTTestHandler.getThreadHandler());
 
         // verify if registered handler has message posted to it
         ArgumentCaptor<Message> messageArgumentCaptor = ArgumentCaptor.forClass(Message.class);
@@ -1246,13 +1433,13 @@
         mSimulatedCommands.setDataRegState(-1);
         mSimulatedCommands.notifyNetworkStateChanged();
 
-        waitForMs(100);
+        waitForLastHandlerAction(mSSTTestHandler.getThreadHandler());
 
         // Unregister registrant
         sst.unregisterForNetworkAttached(mTestHandler);
 
 
-        waitForMs(100);
+        waitForLastHandlerAction(mSSTTestHandler.getThreadHandler());
 
         sst.registerForNetworkAttached(mTestHandler, EVENT_REGISTERED_TO_NETWORK, null);
 
@@ -1261,7 +1448,7 @@
         mSimulatedCommands.setDataRegState(NetworkRegistrationInfo.REGISTRATION_STATE_ROAMING);
         mSimulatedCommands.notifyNetworkStateChanged();
 
-        waitForMs(100);
+        waitForLastHandlerAction(mSSTTestHandler.getThreadHandler());
 
         // verify if registered handler has message posted to it
         messageArgumentCaptor = ArgumentCaptor.forClass(Message.class);
@@ -1278,7 +1465,7 @@
         // also post message to handler
         sst.registerForPsRestrictedEnabled(mTestHandler, EVENT_PS_RESTRICT_ENABLED, null);
 
-        waitForMs(100);
+        waitForLastHandlerAction(mSSTTestHandler.getThreadHandler());
 
         // verify posted message
         ArgumentCaptor<Message> messageArgumentCaptor = ArgumentCaptor.forClass(Message.class);
@@ -1294,7 +1481,7 @@
         // also post message to handler
         sst.registerForPsRestrictedDisabled(mTestHandler, EVENT_PS_RESTRICT_DISABLED, null);
 
-        waitForMs(100);
+        waitForLastHandlerAction(mSSTTestHandler.getThreadHandler());
 
         // verify posted message
         ArgumentCaptor<Message> messageArgumentCaptor = ArgumentCaptor.forClass(Message.class);
@@ -1367,7 +1554,7 @@
     private void internalCheckForRestrictedStateChange(ServiceStateTracker serviceStateTracker,
                 int times, int[] restrictedState) {
         mSimulatedCommands.triggerRestrictedStateChanged(restrictedState[0]);
-        waitForMs(100);
+        waitForLastHandlerAction(mSSTTestHandler.getThreadHandler());
         ArgumentCaptor<Integer> intArgumentCaptor = ArgumentCaptor.forClass(Integer.class);
         verify(serviceStateTracker, times(times)).setNotification(intArgumentCaptor.capture());
         assertEquals(intArgumentCaptor.getValue().intValue(), restrictedState[1]);
@@ -1391,7 +1578,7 @@
         doReturn(subId).when(mSubInfo).getSubscriptionId();
 
         doReturn(mSubInfo).when(mSubscriptionController).getActiveSubscriptionInfo(
-                anyInt(), anyString());
+                anyInt(), anyString(), nullable(String.class));
 
         final NotificationManager nm = (NotificationManager)
                 mContext.getSystemService(Context.NOTIFICATION_SERVICE);
@@ -1423,7 +1610,7 @@
         sst.mSubId = subId;
         doReturn(subId).when(mSubInfo).getSubscriptionId();
         doReturn(mSubInfo).when(mSubscriptionController)
-                .getActiveSubscriptionInfo(anyInt(), anyString());
+                .getActiveSubscriptionInfo(anyInt(), anyString(), nullable(String.class));
 
         final NotificationManager nm = (NotificationManager)
                 mContext.getSystemService(Context.NOTIFICATION_SERVICE);
@@ -1456,7 +1643,7 @@
         sst.mSubId = subId;
         doReturn(subId).when(mSubInfo).getSubscriptionId();
         doReturn(mSubInfo).when(mSubscriptionController)
-                .getActiveSubscriptionInfo(anyInt(), anyString());
+                .getActiveSubscriptionInfo(anyInt(), anyString(), nullable(String.class));
 
         final NotificationManager nm = (NotificationManager)
                 mContext.getSystemService(Context.NOTIFICATION_SERVICE);
@@ -1488,7 +1675,7 @@
         sst.mSubId = subId;
         doReturn(subId).when(mSubInfo).getSubscriptionId();
         doReturn(mSubInfo).when(mSubscriptionController)
-                .getActiveSubscriptionInfo(anyInt(), anyString());
+                .getActiveSubscriptionInfo(anyInt(), anyString(), nullable(String.class));
 
         final NotificationManager nm = (NotificationManager)
                 mContext.getSystemService(Context.NOTIFICATION_SERVICE);
@@ -1555,7 +1742,7 @@
         sst.updatePhoneType();
         mSimulatedCommands.notifyOtaProvisionStatusChanged();
 
-        waitForMs(200);
+        waitForLastHandlerAction(mSSTTestHandler.getThreadHandler());
 
         // verify posted message
         ArgumentCaptor<Message> messageArgumentCaptor = ArgumentCaptor.forClass(Message.class);
@@ -1573,7 +1760,7 @@
         mSimulatedCommands.setDataRegState(NetworkRegistrationInfo.REGISTRATION_STATE_ROAMING);
         mSimulatedCommands.notifyNetworkStateChanged();
 
-        waitForMs(200);
+        waitForLastHandlerAction(mSSTTestHandler.getThreadHandler());
 
         sst.registerForDataRoamingOff(mTestHandler, EVENT_DATA_ROAMING_OFF, null, true);
         sst.registerForVoiceRoamingOff(mTestHandler, EVENT_VOICE_ROAMING_OFF, null);
@@ -1661,7 +1848,7 @@
     public void testIsImsRegistered() throws Exception {
         mSimulatedCommands.setImsRegistrationState(new int[]{1, PhoneConstants.PHONE_TYPE_GSM});
         mSimulatedCommands.notifyImsNetworkStateChanged();
-        waitForMs(200);
+        waitForLastHandlerAction(mSSTTestHandler.getThreadHandler());
         assertEquals(sst.isImsRegistered(), true);
     }
 
@@ -1676,10 +1863,10 @@
     @SmallTest
     public void testShuttingDownRequest() throws Exception {
         sst.setRadioPower(true);
-        waitForMs(100);
+        waitForLastHandlerAction(mSSTTestHandler.getThreadHandler());
 
         sst.requestShutdown();
-        waitForMs(100);
+        waitForLastHandlerAction(mSSTTestHandler.getThreadHandler());
         assertFalse(mSimulatedCommands.getRadioState()
                 != TelephonyManager.RADIO_POWER_UNAVAILABLE);
     }
@@ -1688,15 +1875,15 @@
     @SmallTest
     public void testShuttingDownRequestWithRadioPowerFailResponse() throws Exception {
         sst.setRadioPower(true);
-        waitForMs(100);
+        waitForLastHandlerAction(mSSTTestHandler.getThreadHandler());
 
         // Simulate RIL fails the radio power settings.
         mSimulatedCommands.setRadioPowerFailResponse(true);
         sst.setRadioPower(false);
-        waitForMs(100);
+        waitForLastHandlerAction(mSSTTestHandler.getThreadHandler());
         assertTrue(mSimulatedCommands.getRadioState() == TelephonyManager.RADIO_POWER_ON);
         sst.requestShutdown();
-        waitForMs(100);
+        waitForLastHandlerAction(mSSTTestHandler.getThreadHandler());
         assertFalse(mSimulatedCommands.getRadioState()
                 != TelephonyManager.RADIO_POWER_UNAVAILABLE);
     }
@@ -1707,7 +1894,7 @@
         {
             // Mock sending incorrect nitz str from RIL
             mSimulatedCommands.triggerNITZupdate("38/06/20,00:00:00+0");
-            waitForMs(200);
+            waitForLastHandlerAction(mSSTTestHandler.getThreadHandler());
             verify(mNitzStateMachine, times(0)).handleNitzReceived(any());
         }
         {
@@ -1715,7 +1902,7 @@
             String nitzStr = "15/06/20,00:00:00+0";
             NitzData expectedNitzData = NitzData.parse(nitzStr);
             mSimulatedCommands.triggerNITZupdate(nitzStr);
-            waitForMs(200);
+            waitForLastHandlerAction(mSSTTestHandler.getThreadHandler());
 
             ArgumentCaptor<TimestampedValue<NitzData>> argumentsCaptor =
                     ArgumentCaptor.forClass(TimestampedValue.class);
@@ -1739,22 +1926,22 @@
                     LteVopsSupportInfo.LTE_STATUS_NOT_AVAILABLE);
         NetworkRegistrationInfo dataResult = new NetworkRegistrationInfo(
                 NetworkRegistrationInfo.DOMAIN_PS, AccessNetworkConstants.TRANSPORT_TYPE_WWAN,
-                state, dataRat, 0, false,
-                null, cid, 1, false, false, false, lteVopsSupportInfo, false);
+                state, dataRat, 0, false, null, cid, getPlmnFromCellIdentity(cid),
+                1, false, false, false, lteVopsSupportInfo, false);
         sst.mPollingContext[0] = 2;
         // update data reg state to be in service
         sst.sendMessage(sst.obtainMessage(
                 ServiceStateTracker.EVENT_POLL_STATE_PS_CELLULAR_REGISTRATION,
                 new AsyncResult(sst.mPollingContext, dataResult, null)));
-        waitForMs(200);
+        waitForLastHandlerAction(mSSTTestHandler.getThreadHandler());
         NetworkRegistrationInfo voiceResult = new NetworkRegistrationInfo(
                 NetworkRegistrationInfo.DOMAIN_CS, AccessNetworkConstants.TRANSPORT_TYPE_WWAN,
                 state, voiceRat, 0, false,
-                null, cid, false, 0, 0, 0);
+                null, cid, getPlmnFromCellIdentity(cid), false, 0, 0, 0);
         sst.sendMessage(sst.obtainMessage(
                 ServiceStateTracker.EVENT_POLL_STATE_CS_CELLULAR_REGISTRATION,
                 new AsyncResult(sst.mPollingContext, voiceResult, null)));
-        waitForMs(200);
+        waitForLastHandlerAction(mSSTTestHandler.getThreadHandler());
     }
 
     private void changeRegStateWithIwlan(int state, CellIdentity cid, int voiceRat, int dataRat,
@@ -1767,32 +1954,51 @@
         // PS WWAN
         NetworkRegistrationInfo dataResult = new NetworkRegistrationInfo(
                 NetworkRegistrationInfo.DOMAIN_PS, AccessNetworkConstants.TRANSPORT_TYPE_WWAN,
-                state, dataRat, 0, false,
-                null, cid, 1, false, false, false, lteVopsSupportInfo, false);
+                state, dataRat, 0, false, null, cid, getPlmnFromCellIdentity(cid),
+                1, false, false, false, lteVopsSupportInfo, false);
         sst.sendMessage(sst.obtainMessage(
                 ServiceStateTracker.EVENT_POLL_STATE_PS_CELLULAR_REGISTRATION,
                 new AsyncResult(sst.mPollingContext, dataResult, null)));
-        waitForMs(200);
+        waitForLastHandlerAction(mSSTTestHandler.getThreadHandler());
 
         // CS WWAN
         NetworkRegistrationInfo voiceResult = new NetworkRegistrationInfo(
                 NetworkRegistrationInfo.DOMAIN_CS, AccessNetworkConstants.TRANSPORT_TYPE_WWAN,
-                state, voiceRat, 0, false,
-                null, cid, false, 0, 0, 0);
+                state, voiceRat, 0, false, null, cid, getPlmnFromCellIdentity(cid), false, 0, 0, 0);
         sst.sendMessage(sst.obtainMessage(
                 ServiceStateTracker.EVENT_POLL_STATE_CS_CELLULAR_REGISTRATION,
                 new AsyncResult(sst.mPollingContext, voiceResult, null)));
-        waitForMs(200);
+        waitForLastHandlerAction(mSSTTestHandler.getThreadHandler());
 
         // PS WLAN
         NetworkRegistrationInfo dataIwlanResult = new NetworkRegistrationInfo(
                 NetworkRegistrationInfo.DOMAIN_PS, AccessNetworkConstants.TRANSPORT_TYPE_WLAN,
                 iwlanState, iwlanDataRat, 0, false,
-                null, null, 1, false, false, false, lteVopsSupportInfo, false);
+                null, null, "", 1, false, false, false, lteVopsSupportInfo, false);
         sst.sendMessage(sst.obtainMessage(
                 ServiceStateTracker.EVENT_POLL_STATE_PS_IWLAN_REGISTRATION,
                 new AsyncResult(sst.mPollingContext, dataIwlanResult, null)));
-        waitForMs(200);
+        waitForLastHandlerAction(mSSTTestHandler.getThreadHandler());
+    }
+
+    @Test
+    public void testPollStateOperatorWhileNotRegistered() {
+        final String[] oldOpNamesResult = new String[] { "Old carrier long", "Old carrier", "" };
+        final String[] badOpNamesResult = null;
+        sst.mPollingContext[0] = 1;
+        sst.sendMessage(sst.obtainMessage(
+                ServiceStateTracker.EVENT_POLL_STATE_OPERATOR,
+                new AsyncResult(sst.mPollingContext, oldOpNamesResult, null)));
+        waitForLastHandlerAction(mSSTTestHandler.getThreadHandler());
+        assertEquals(oldOpNamesResult[0], sst.getServiceState().getOperatorAlpha());
+
+        // if the device is not registered, the modem returns an invalid operator
+        sst.mPollingContext[0] = 1;
+        sst.sendMessage(sst.obtainMessage(
+                ServiceStateTracker.EVENT_POLL_STATE_OPERATOR,
+                new AsyncResult(sst.mPollingContext, badOpNamesResult, null)));
+        waitForLastHandlerAction(mSSTTestHandler.getThreadHandler());
+        assertEquals(null, sst.getServiceState().getOperatorAlpha());
     }
 
     // Edge and GPRS are grouped under the same family and Edge has higher rate than GPRS.
@@ -1800,7 +2006,8 @@
     @Test
     public void testRatRatchet() throws Exception {
         CellIdentityGsm cellIdentity =
-                new CellIdentityGsm(0, 1, 900, 5, "001", "01", "test", "tst");
+                new CellIdentityGsm(0, 1, 900, 5, "001", "01", "test", "tst",
+                        Collections.emptyList());
         // start on GPRS
         changeRegState(1, cellIdentity, 16, 1);
         assertEquals(ServiceState.STATE_IN_SERVICE, sst.getCurrentDataConnectionState());
@@ -1818,14 +2025,16 @@
     @Test
     public void testRatRatchetWithCellChange() throws Exception {
         CellIdentityGsm cellIdentity =
-                new CellIdentityGsm(0, 1, 900, 5, "001", "01", "test", "tst");
+                new CellIdentityGsm(0, 1, 900, 5, "001", "01", "test", "tst",
+                        Collections.emptyList());
         // update data reg state to be in service
         changeRegState(1, cellIdentity, 16, 2);
         assertEquals(ServiceState.STATE_IN_SERVICE, sst.getCurrentDataConnectionState());
         assertEquals(ServiceState.RIL_RADIO_TECHNOLOGY_GSM, sst.mSS.getRilVoiceRadioTechnology());
         assertEquals(ServiceState.RIL_RADIO_TECHNOLOGY_EDGE, sst.mSS.getRilDataRadioTechnology());
         // RAT: EDGE -> GPRS cell ID: 1 -> 2
-        cellIdentity = new CellIdentityGsm(0, 2, 900, 5, "001", "01", "test", "tst");
+        cellIdentity = new CellIdentityGsm(0, 2, 900, 5, "001", "01", "test", "tst",
+                Collections.emptyList());
         changeRegState(1, cellIdentity, 16, 1);
         assertEquals(ServiceState.RIL_RADIO_TECHNOLOGY_GPRS, sst.mSS.getRilDataRadioTechnology());
 
@@ -1840,7 +2049,8 @@
     public void testRatRatchetWithCellChangeBeforeRatChange() throws Exception {
         // cell ID update
         CellIdentityGsm cellIdentity =
-                new CellIdentityGsm(0, 1, 900, 5, "001", "01", "test", "tst");
+                new CellIdentityGsm(0, 1, 900, 5, "001", "01", "test", "tst",
+                        Collections.emptyList());
         changeRegState(1, cellIdentity, 16, 2);
         assertEquals(ServiceState.STATE_IN_SERVICE, sst.getCurrentDataConnectionState());
         assertEquals(ServiceState.RIL_RADIO_TECHNOLOGY_EDGE, sst.mSS.getRilDataRadioTechnology());
@@ -1868,7 +2078,7 @@
         }
         sst.sendMessage(sst.obtainMessage(ServiceStateTracker.EVENT_PHYSICAL_CHANNEL_CONFIG,
                 new AsyncResult(null, pc, null)));
-        waitForMs(100);
+        waitForLastHandlerAction(mSSTTestHandler.getThreadHandler());
     }
 
     private void sendRegStateUpdateForLteCellId(CellIdentityLte cellId) {
@@ -1878,28 +2088,30 @@
         NetworkRegistrationInfo dataResult = new NetworkRegistrationInfo(
                 NetworkRegistrationInfo.DOMAIN_PS, AccessNetworkConstants.TRANSPORT_TYPE_WWAN,
                 NetworkRegistrationInfo.REGISTRATION_STATE_HOME, TelephonyManager.NETWORK_TYPE_LTE,
-                0, false, null, cellId, 1, false, false, false, lteVopsSupportInfo, false);
+                0, false, null, cellId, getPlmnFromCellIdentity(cellId), 1, false, false, false,
+                lteVopsSupportInfo, false);
         NetworkRegistrationInfo voiceResult = new NetworkRegistrationInfo(
                 NetworkRegistrationInfo.DOMAIN_CS, AccessNetworkConstants.TRANSPORT_TYPE_WWAN,
                 NetworkRegistrationInfo.REGISTRATION_STATE_HOME, TelephonyManager.NETWORK_TYPE_LTE,
-                0, false, null, cellId, false, 0, 0, 0);
+                0, false, null, cellId, getPlmnFromCellIdentity(cellId), false, 0, 0, 0);
         sst.mPollingContext[0] = 2;
         // update data reg state to be in service
         sst.sendMessage(sst.obtainMessage(
                 ServiceStateTracker.EVENT_POLL_STATE_PS_CELLULAR_REGISTRATION,
                 new AsyncResult(sst.mPollingContext, dataResult, null)));
-        waitForMs(200);
+        waitForLastHandlerAction(mSSTTestHandler.getThreadHandler());
         sst.sendMessage(sst.obtainMessage(
                 ServiceStateTracker.EVENT_POLL_STATE_CS_CELLULAR_REGISTRATION,
                 new AsyncResult(sst.mPollingContext, voiceResult, null)));
-        waitForMs(200);
+        waitForLastHandlerAction(mSSTTestHandler.getThreadHandler());
     }
 
     @Test
     public void testPhyChanBandwidthUpdatedOnDataRegState() throws Exception {
         // Cell ID change should trigger hasLocationChanged.
         CellIdentityLte cellIdentity5 =
-                new CellIdentityLte(1, 1, 5, 1, 5000, "001", "01", "test", "tst");
+                new CellIdentityLte(1, 1, 5, 1, new int[] {1, 2}, 5000, "001", "01", "test",
+                        "tst", Collections.emptyList(), null);
 
         sendPhyChanConfigChange(new int[] {10000});
         sendRegStateUpdateForLteCellId(cellIdentity5);
@@ -1910,7 +2122,8 @@
     public void testPhyChanBandwidthNotUpdatedWhenInvalidInCellIdentity() throws Exception {
         // Cell ID change should trigger hasLocationChanged.
         CellIdentityLte cellIdentityInv =
-                new CellIdentityLte(1, 1, 5, 1, 12345, "001", "01", "test", "tst");
+                new CellIdentityLte(1, 1, 5, 1, new int[] {1, 2}, 12345, "001", "01", "test",
+                        "tst", Collections.emptyList(), null);
 
         sendPhyChanConfigChange(new int[] {10000});
         sendRegStateUpdateForLteCellId(cellIdentityInv);
@@ -1921,7 +2134,8 @@
     public void testPhyChanBandwidthPrefersCarrierAggregationReport() throws Exception {
         // Cell ID change should trigger hasLocationChanged.
         CellIdentityLte cellIdentity10 =
-                new CellIdentityLte(1, 1, 5, 1, 10000, "001", "01", "test", "tst");
+                new CellIdentityLte(1, 1, 5, 1, new int[] {1, 2}, 10000, "001", "01", "test",
+                        "tst", Collections.emptyList(), null);
 
         sendPhyChanConfigChange(new int[] {10000, 5000});
         sendRegStateUpdateForLteCellId(cellIdentity10);
@@ -1932,7 +2146,8 @@
     public void testPhyChanBandwidthRatchetedOnPhyChanBandwidth() throws Exception {
         // LTE Cell with bandwidth = 10000
         CellIdentityLte cellIdentity10 =
-                new CellIdentityLte(1, 1, 1, 1, 10000, "1", "1", "test", "tst");
+                new CellIdentityLte(1, 1, 1, 1, new int[] {1, 2}, 10000, "1", "1", "test",
+                        "tst", Collections.emptyList(), null);
 
         sendRegStateUpdateForLteCellId(cellIdentity10);
         assertTrue(Arrays.equals(new int[] {10000}, sst.mSS.getCellBandwidths()));
@@ -1949,21 +2164,21 @@
         NetworkRegistrationInfo dataResult = new NetworkRegistrationInfo(
                 NetworkRegistrationInfo.DOMAIN_PS, AccessNetworkConstants.TRANSPORT_TYPE_WWAN,
                 NetworkRegistrationInfo.REGISTRATION_STATE_NOT_REGISTERED_OR_SEARCHING,
-                TelephonyManager.NETWORK_TYPE_UNKNOWN, 0, false, null, null, 1, false, false,
+                TelephonyManager.NETWORK_TYPE_UNKNOWN, 0, false, null, null, "", 1, false, false,
                 false, lteVopsSupportInfo, false);
         NetworkRegistrationInfo voiceResult = new NetworkRegistrationInfo(
                 NetworkRegistrationInfo.DOMAIN_CS, AccessNetworkConstants.TRANSPORT_TYPE_WWAN,
                 NetworkRegistrationInfo.REGISTRATION_STATE_NOT_REGISTERED_OR_SEARCHING,
-                TelephonyManager.NETWORK_TYPE_UNKNOWN, 0, false, null, null, false, 0, 0, 0);
+                TelephonyManager.NETWORK_TYPE_UNKNOWN, 0, false, null, null, "", false, 0, 0, 0);
         sst.mPollingContext[0] = 2;
         sst.sendMessage(sst.obtainMessage(
                 ServiceStateTracker.EVENT_POLL_STATE_PS_CELLULAR_REGISTRATION,
                 new AsyncResult(sst.mPollingContext, dataResult, null)));
-        waitForMs(200);
+        waitForLastHandlerAction(mSSTTestHandler.getThreadHandler());
         sst.sendMessage(sst.obtainMessage(
                 ServiceStateTracker.EVENT_POLL_STATE_CS_CELLULAR_REGISTRATION,
                 new AsyncResult(sst.mPollingContext, voiceResult, null)));
-        waitForMs(200);
+        waitForLastHandlerAction(mSSTTestHandler.getThreadHandler());
         assertTrue(Arrays.equals(new int[0], sst.mSS.getCellBandwidths()));
     }
 
@@ -1979,7 +2194,8 @@
 
         // Start state: Cell data only LTE + IWLAN
         CellIdentityLte cellIdentity =
-                new CellIdentityLte(1, 1, 5, 1, 5000, "001", "01", "test", "tst");
+                new CellIdentityLte(1, 1, 5, 1, new int[] {1, 2}, 5000, "001", "01", "test",
+                        "tst", Collections.emptyList(), null);
         changeRegStateWithIwlan(
                 // WWAN
                 NetworkRegistrationInfo.REGISTRATION_STATE_HOME, cellIdentity,
@@ -2134,7 +2350,8 @@
         sst.mSS = ss;
 
         CellIdentityLte cellId =
-                new CellIdentityLte(1, 1, 5, 1, 5000, "001", "01", "test", "tst");
+                new CellIdentityLte(1, 1, 5, 1, new int[] {1, 2}, 5000, "001", "01", "test",
+                        "tst", Collections.emptyList(), null);
         LteVopsSupportInfo lteVopsSupportInfo =
                 new LteVopsSupportInfo(LteVopsSupportInfo.LTE_STATUS_NOT_SUPPORTED,
                     LteVopsSupportInfo.LTE_STATUS_NOT_SUPPORTED);
@@ -2142,7 +2359,7 @@
         NetworkRegistrationInfo dataResult = new NetworkRegistrationInfo(
                 NetworkRegistrationInfo.DOMAIN_PS, AccessNetworkConstants.TRANSPORT_TYPE_WWAN,
                 NetworkRegistrationInfo.REGISTRATION_STATE_HOME, TelephonyManager.NETWORK_TYPE_LTE,
-                0, false, null, cellId, 1, false, false, false, lteVopsSupportInfo, false);
+                0, false, null, cellId, "00101", 1, false, false, false, lteVopsSupportInfo, false);
         sst.mPollingContext[0] = 2;
 
         sst.sendMessage(sst.obtainMessage(
@@ -2152,12 +2369,12 @@
                 NetworkRegistrationInfo.DOMAIN_CS, AccessNetworkConstants.TRANSPORT_TYPE_WWAN,
                 NetworkRegistrationInfo.REGISTRATION_STATE_HOME,
                 TelephonyManager.NETWORK_TYPE_LTE, 0,
-                false, null, cellId, false, 0, 0, 0);
+                false, null, cellId, "00101", false, 0, 0, 0);
         sst.sendMessage(sst.obtainMessage(
                 ServiceStateTracker.EVENT_POLL_STATE_CS_CELLULAR_REGISTRATION,
                 new AsyncResult(sst.mPollingContext, voiceResult, null)));
 
-        waitForMs(200);
+        waitForLastHandlerAction(mSSTTestHandler.getThreadHandler());
         assertEquals(ServiceState.STATE_IN_SERVICE, sst.getCurrentDataConnectionState());
         NetworkRegistrationInfo sSnetworkRegistrationInfo =
                 sst.mSS.getNetworkRegistrationInfo(NetworkRegistrationInfo.DOMAIN_PS,
@@ -2171,13 +2388,13 @@
         dataResult = new NetworkRegistrationInfo(NetworkRegistrationInfo.DOMAIN_PS,
                 AccessNetworkConstants.TRANSPORT_TYPE_WWAN,
                 NetworkRegistrationInfo.REGISTRATION_STATE_HOME,
-                TelephonyManager.NETWORK_TYPE_LTE, 0, false, null, cellId, 1, false, false, false,
-                lteVopsSupportInfo, false);
+                TelephonyManager.NETWORK_TYPE_LTE, 0, false, null, cellId, "00101",
+                1, false, false, false, lteVopsSupportInfo, false);
         sst.mPollingContext[0] = 1;
         sst.sendMessage(sst.obtainMessage(
                 ServiceStateTracker.EVENT_POLL_STATE_PS_CELLULAR_REGISTRATION,
                 new AsyncResult(sst.mPollingContext, dataResult, null)));
-        waitForMs(200);
+        waitForLastHandlerAction(mSSTTestHandler.getThreadHandler());
 
         sSnetworkRegistrationInfo =
                 sst.mSS.getNetworkRegistrationInfo(2, 1);
@@ -2189,20 +2406,12 @@
     @SmallTest
     public void testEriLoading() {
         sst.obtainMessage(GsmCdmaPhone.EVENT_CARRIER_CONFIG_CHANGED, null).sendToTarget();
-        waitForMs(100);
+        waitForLastHandlerAction(mSSTTestHandler.getThreadHandler());
         verify(mEriManager, times(1)).loadEriFile();
     }
 
-    private void enableCdnr() {
-        mBundle.putBoolean(
-                CarrierConfigManager.KEY_ENABLE_CARRIER_DISPLAY_NAME_RESOLVER_BOOL, true);
-        sendCarrierConfigUpdate();
-    }
-
     @Test
     public void testUpdateSpnDisplay_noService_displayEmergencyCallOnly() {
-        enableCdnr();
-
         // GSM phone
         doReturn(true).when(mPhone).isPhoneTypeGsm();
 
@@ -2218,15 +2427,13 @@
 
         // Plmn should be shown, and the string is "Emergency call only"
         Bundle b = getExtrasFromLastSpnUpdateIntent();
-        assertThat(b.getString(TelephonyIntents.EXTRA_PLMN))
+        assertThat(b.getString(TelephonyManager.EXTRA_PLMN))
                 .isEqualTo(CARRIER_NAME_DISPLAY_EMERGENCY_CALL);
-        assertThat(b.getBoolean(TelephonyIntents.EXTRA_SHOW_PLMN)).isTrue();
+        assertThat(b.getBoolean(TelephonyManager.EXTRA_SHOW_PLMN)).isTrue();
     }
 
     @Test
     public void testUpdateSpnDisplay_noServiceAndEmergencyCallNotAvailable_displayOOS() {
-        enableCdnr();
-
         // GSM phone
         doReturn(true).when(mPhone).isPhoneTypeGsm();
 
@@ -2242,15 +2449,13 @@
 
         // Plmn should be shown, and the string is "No service"
         Bundle b = getExtrasFromLastSpnUpdateIntent();
-        assertThat(b.getString(TelephonyIntents.EXTRA_PLMN))
+        assertThat(b.getString(TelephonyManager.EXTRA_PLMN))
                 .isEqualTo(CARRIER_NAME_DISPLAY_NO_SERVICE);
-        assertThat(b.getBoolean(TelephonyIntents.EXTRA_SHOW_PLMN)).isTrue();
+        assertThat(b.getBoolean(TelephonyManager.EXTRA_SHOW_PLMN)).isTrue();
     }
 
     @Test
     public void testUpdateSpnDisplay_flightMode_displayOOS() {
-        enableCdnr();
-
         // GSM phone
         doReturn(true).when(mPhone).isPhoneTypeGsm();
 
@@ -2265,15 +2470,13 @@
 
         // Plmn should be shown, and the string is "No service"
         Bundle b = getExtrasFromLastSpnUpdateIntent();
-        assertThat(b.getString(TelephonyIntents.EXTRA_PLMN))
+        assertThat(b.getString(TelephonyManager.EXTRA_PLMN))
                 .isEqualTo(CARRIER_NAME_DISPLAY_NO_SERVICE);
-        assertThat(b.getBoolean(TelephonyIntents.EXTRA_SHOW_PLMN)).isTrue();
+        assertThat(b.getBoolean(TelephonyManager.EXTRA_SHOW_PLMN)).isTrue();
     }
 
     @Test
     public void testUpdateSpnDisplay_spnNotEmptyAndWifiCallingEnabled_showSpnOnly() {
-        enableCdnr();
-
         // GSM phone
         doReturn(true).when(mPhone).isPhoneTypeGsm();
 
@@ -2292,20 +2495,19 @@
         // Only spn should be shown
         String spn = mBundle.getString(CarrierConfigManager.KEY_CARRIER_NAME_STRING);
         Bundle b = getExtrasFromLastSpnUpdateIntent();
-        assertThat(b.getString(TelephonyIntents.EXTRA_SPN))
+        assertThat(b.getString(TelephonyManager.EXTRA_SPN))
                 .isEqualTo(String.format(WIFI_CALLING_VOICE_FORMAT, spn));
-        assertThat(b.getBoolean(TelephonyIntents.EXTRA_SHOW_SPN)).isTrue();
-        assertThat(b.getString(TelephonyIntents.EXTRA_DATA_SPN))
+        assertThat(b.getBoolean(TelephonyManager.EXTRA_SHOW_SPN)).isTrue();
+        assertThat(b.getString(TelephonyManager.EXTRA_DATA_SPN))
                 .isEqualTo(String.format(WIFI_CALLING_DATA_FORMAT, spn));
-        assertThat(b.getBoolean(TelephonyIntents.EXTRA_SHOW_PLMN)).isFalse();
+        assertThat(b.getBoolean(TelephonyManager.EXTRA_SHOW_PLMN)).isFalse();
     }
 
     @Test
     public void testUpdateSpnDisplay_spnEmptyAndWifiCallingEnabled_showPlmnOnly() {
         // set empty service provider name
         mBundle.putString(CarrierConfigManager.KEY_CARRIER_NAME_STRING, "");
-
-        enableCdnr();
+        sendCarrierConfigUpdate();
 
         // GSM phone
         doReturn(true).when(mPhone).isPhoneTypeGsm();
@@ -2326,16 +2528,14 @@
         String plmn = mBundle.getStringArray(CarrierConfigManager.KEY_PNN_OVERRIDE_STRING_ARRAY)[0];
         plmn = plmn.split("\\s*,\\s*")[0];
         Bundle b = getExtrasFromLastSpnUpdateIntent();
-        assertThat(b.getString(TelephonyIntents.EXTRA_PLMN))
+        assertThat(b.getString(TelephonyManager.EXTRA_PLMN))
                 .isEqualTo(String.format(WIFI_CALLING_VOICE_FORMAT, plmn));
-        assertThat(b.getBoolean(TelephonyIntents.EXTRA_SHOW_PLMN)).isTrue();
-        assertThat(b.getBoolean(TelephonyIntents.EXTRA_SHOW_SPN)).isFalse();
+        assertThat(b.getBoolean(TelephonyManager.EXTRA_SHOW_PLMN)).isTrue();
+        assertThat(b.getBoolean(TelephonyManager.EXTRA_SHOW_SPN)).isFalse();
     }
 
     @Test
     public void testUpdateSpnDisplay_inServiceNoWifiCalling_showSpnAndPlmn() {
-        enableCdnr();
-
         // GSM phone
         doReturn(true).when(mPhone).isPhoneTypeGsm();
 
@@ -2355,10 +2555,10 @@
         String plmn = mBundle.getStringArray(CarrierConfigManager.KEY_PNN_OVERRIDE_STRING_ARRAY)[0];
         plmn = plmn.split("\\s*,\\s*")[0];
         Bundle b = getExtrasFromLastSpnUpdateIntent();
-        assertThat(b.getString(TelephonyIntents.EXTRA_SPN)).isEqualTo(spn);
-        assertThat(b.getBoolean(TelephonyIntents.EXTRA_SHOW_SPN)).isTrue();
-        assertThat(b.getString(TelephonyIntents.EXTRA_PLMN)).isEqualTo(plmn);
-        assertThat(b.getBoolean(TelephonyIntents.EXTRA_SHOW_PLMN)).isTrue();
+        assertThat(b.getString(TelephonyManager.EXTRA_SPN)).isEqualTo(spn);
+        assertThat(b.getBoolean(TelephonyManager.EXTRA_SHOW_SPN)).isTrue();
+        assertThat(b.getString(TelephonyManager.EXTRA_PLMN)).isEqualTo(plmn);
+        assertThat(b.getBoolean(TelephonyManager.EXTRA_SHOW_PLMN)).isTrue();
     }
 
     @Test
@@ -2382,6 +2582,34 @@
         assertTrue(sst.shouldForceDisplayNoService());
     }
 
+    @Test
+    public void testUpdateSpnDisplayLegacy_WlanServiceNoWifiCalling_displayOOS() {
+        mBundle.putBoolean(
+                CarrierConfigManager.KEY_ENABLE_CARRIER_DISPLAY_NAME_RESOLVER_BOOL, false);
+        sendCarrierConfigUpdate();
+
+        // GSM phone
+        doReturn(true).when(mPhone).isPhoneTypeGsm();
+
+        // voice out of service but data in service (connected to IWLAN)
+        doReturn(ServiceState.STATE_OUT_OF_SERVICE).when(mServiceState).getState();
+        doReturn(ServiceState.STATE_IN_SERVICE).when(mServiceState).getDataRegistrationState();
+        doReturn(TelephonyManager.NETWORK_TYPE_IWLAN).when(mServiceState).getDataNetworkType();
+        sst.mSS = mServiceState;
+
+        // wifi-calling is disable
+        doReturn(false).when(mPhone).isWifiCallingEnabled();
+
+        // update the spn
+        sst.updateSpnDisplay();
+
+        // Plmn should be shown, and the string is "No service"
+        Bundle b = getExtrasFromLastSpnUpdateIntent();
+        assertThat(b.getString(TelephonyManager.EXTRA_PLMN))
+                .isEqualTo(CARRIER_NAME_DISPLAY_NO_SERVICE);
+        assertThat(b.getBoolean(TelephonyManager.EXTRA_SHOW_PLMN)).isTrue();
+    }
+
     private Bundle getExtrasFromLastSpnUpdateIntent() {
         // Verify the spn update notification was sent
         ArgumentCaptor<Intent> intentArgumentCaptor = ArgumentCaptor.forClass(Intent.class);
@@ -2391,4 +2619,77 @@
         List<Intent> intents = intentArgumentCaptor.getAllValues();
         return intents.get(intents.size() - 1).getExtras();
     }
+
+    private static NetworkRegistrationInfo makeNetworkRegistrationInfo(
+            int domain, int transport, CellIdentity ci, boolean isRegistered) {
+        return new NetworkRegistrationInfo.Builder()
+                .setDomain(domain)
+                .setTransportType(transport)
+                .setCellIdentity(ci)
+                .setRegistrationState(isRegistered
+                        ? NetworkRegistrationInfo.REGISTRATION_STATE_HOME
+                        : NetworkRegistrationInfo.REGISTRATION_STATE_NOT_REGISTERED_OR_SEARCHING)
+                .build();
+    }
+
+    @Test
+    public void testCellIdentitySort() {
+        final CellIdentityLte cellIdentityLte =
+                new CellIdentityLte(1, 1, 5, 1, new int[] {1, 2}, 5000, "001", "01", "test",
+                        "tst", Collections.emptyList(), null);
+        final CellIdentityGsm cellIdentityGsm = new CellIdentityGsm(
+                2, 3, 900, 5, "001", "01", "test", "tst", Collections.emptyList());
+
+        ServiceState ss = new ServiceState();
+        List<CellIdentity> cids;
+
+        // Test that PS WWAN is reported if available
+        ss.addNetworkRegistrationInfo(makeNetworkRegistrationInfo(
+                NetworkRegistrationInfo.DOMAIN_PS,
+                AccessNetworkConstants.TRANSPORT_TYPE_WWAN,
+                cellIdentityLte, false));
+        cids = ServiceStateTracker.getPrioritizedCellIdentities(ss);
+        assertEquals(cids.size(), 1);
+        assertEquals(cids.get(0), cellIdentityLte);
+
+        // Test that CS is prioritized over PS
+        ss.addNetworkRegistrationInfo(makeNetworkRegistrationInfo(
+                NetworkRegistrationInfo.DOMAIN_CS,
+                AccessNetworkConstants.TRANSPORT_TYPE_WWAN,
+                cellIdentityGsm, false));
+        cids = ServiceStateTracker.getPrioritizedCellIdentities(ss);
+        assertEquals(cids.size(), 2);
+        assertEquals(cids.get(0), cellIdentityGsm);
+        assertEquals(cids.get(1), cellIdentityLte);
+
+        // Test that WLAN is ignored
+        ss.addNetworkRegistrationInfo(makeNetworkRegistrationInfo(
+                NetworkRegistrationInfo.DOMAIN_PS,
+                AccessNetworkConstants.TRANSPORT_TYPE_WLAN,
+                cellIdentityGsm, false));
+        cids = ServiceStateTracker.getPrioritizedCellIdentities(ss);
+        assertEquals(cids.size(), 2);
+        assertEquals(cids.get(0), cellIdentityGsm);
+        assertEquals(cids.get(1), cellIdentityLte);
+
+        // Test that null CellIdentities are ignored
+        ss.addNetworkRegistrationInfo(makeNetworkRegistrationInfo(
+                NetworkRegistrationInfo.DOMAIN_CS,
+                AccessNetworkConstants.TRANSPORT_TYPE_WLAN,
+                null, false));
+        cids = ServiceStateTracker.getPrioritizedCellIdentities(ss);
+        assertEquals(cids.size(), 2);
+        assertEquals(cids.get(0), cellIdentityGsm);
+        assertEquals(cids.get(1), cellIdentityLte);
+
+        // Test that registered networks are prioritized over unregistered
+        ss.addNetworkRegistrationInfo(makeNetworkRegistrationInfo(
+                NetworkRegistrationInfo.DOMAIN_PS,
+                AccessNetworkConstants.TRANSPORT_TYPE_WWAN,
+                cellIdentityLte, true));
+        cids = ServiceStateTracker.getPrioritizedCellIdentities(ss);
+        assertEquals(cids.size(), 2);
+        assertEquals(cids.get(0), cellIdentityLte);
+        assertEquals(cids.get(1), cellIdentityGsm);
+    }
 }
diff --git a/tests/telephonytests/src/com/android/internal/telephony/SignalStrengthTest.java b/tests/telephonytests/src/com/android/internal/telephony/SignalStrengthTest.java
index 04d8b0d..589d6b6 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/SignalStrengthTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/SignalStrengthTest.java
@@ -20,6 +20,8 @@
 import static org.junit.Assert.assertTrue;
 
 import android.os.Parcel;
+import android.os.PersistableBundle;
+import android.telephony.CarrierConfigManager;
 import android.telephony.CellInfo;
 import android.telephony.CellSignalStrength;
 import android.telephony.CellSignalStrengthCdma;
@@ -42,6 +44,29 @@
 @SmallTest
 @RunWith(JUnit4.class)
 public class SignalStrengthTest {
+    private static final int[] DEFAULT_LTE_RSRP_THRESHOLDS = {
+            -128,  // SIGNAL_STRENGTH_POOR
+            -118,  // SIGNAL_STRENGTH_MODERATE
+            -108,  // SIGNAL_STRENGTH_GOOD
+            -98 }; // SIGNAL_STRENGTH_GREAT
+
+    private static final int[] DEFAULT_LTE_RSRQ_THRESHOLDS = {
+            -19,   // SIGNAL_STRENGTH_POOR
+            -17,   // SIGNAL_STRENGTH_MODERATE
+            -14,   // SIGNAL_STRENGTH_GOOD
+            -12 }; // SIGNAL_STRENGTH_GREAT
+
+    private static final int[] DEFAULT_LTE_RSSNR_THRESHOLDS = {
+            -3,   // SIGNAL_STRENGTH_POOR
+            1,    // SIGNAL_STRENGTH_MODERATE
+            5,    // SIGNAL_STRENGTH_GOOD
+            13 }; // SIGNAL_STRENGTH_GREAT
+
+    private static final int[] DEFAULT_5G_NR_SSRSRP_THRESHOLDS = {
+            -125,  // SIGNAL_STRENGTH_POOR
+            -115,  // SIGNAL_STRENGTH_MODERATE
+            -105,  // SIGNAL_STRENGTH_GOOD
+            -95 }; // SIGNAL_STRENGTH_GREAT
 
     @Test
     public void testDefaults() throws Exception {
@@ -71,6 +96,23 @@
                 new CellSignalStrengthLte(-85, -91, -6, -10, 12, 1),
                 new CellSignalStrengthNr(-91, -6, 3, -80, -7, 4));
         assertParcelingIsLossless(s);
+
+        PersistableBundle bundle = new PersistableBundle();
+        bundle.putIntArray(
+                CarrierConfigManager.KEY_LTE_RSRQ_THRESHOLDS_INT_ARRAY,
+                DEFAULT_LTE_RSRQ_THRESHOLDS);
+        bundle.putIntArray(
+                CarrierConfigManager.KEY_LTE_RSRP_THRESHOLDS_INT_ARRAY,
+                DEFAULT_LTE_RSRP_THRESHOLDS);
+        bundle.putIntArray(
+                CarrierConfigManager.KEY_LTE_RSSNR_THRESHOLDS_INT_ARRAY,
+                DEFAULT_LTE_RSSNR_THRESHOLDS);
+        bundle.putIntArray(
+                CarrierConfigManager.KEY_5G_NR_SSRSRP_THRESHOLDS_INT_ARRAY,
+                DEFAULT_5G_NR_SSRSRP_THRESHOLDS);
+
+        s.updateLevel(bundle, null);
+        assertParcelingIsLossless(s);
     }
 
     private void assertParcelingIsLossless(SignalStrength ssi) throws Exception {
@@ -116,5 +158,247 @@
         assertTrue(css.contains(cdma));
         assertTrue(css.contains(lte));
     }
+
+    private static SignalStrength createSignalStrengthLteReportRsrq(int lteRsrp, int lteRsrq) {
+
+        CellSignalStrengthLte lte = new CellSignalStrengthLte(
+                -89,                   // rssi
+                lteRsrp,               // rsrp
+                lteRsrq,               // rsrq
+                -25,                   // rssnr
+                CellInfo.UNAVAILABLE,  // cqi
+                CellInfo.UNAVAILABLE); // timingAdvance
+
+        SignalStrength signalStrength = new SignalStrength(
+                new CellSignalStrengthCdma(),
+                new CellSignalStrengthGsm(),
+                new CellSignalStrengthWcdma(),
+                new CellSignalStrengthTdscdma(),
+                lte,
+                new CellSignalStrengthNr());
+
+        PersistableBundle bundle = new PersistableBundle();
+        bundle.putInt(
+                CarrierConfigManager.KEY_PARAMETERS_USED_FOR_LTE_SIGNAL_BAR_INT,
+                CellSignalStrengthLte.USE_RSRP | CellSignalStrengthLte.USE_RSRQ);
+        bundle.putIntArray(
+                CarrierConfigManager.KEY_LTE_RSRQ_THRESHOLDS_INT_ARRAY,
+                DEFAULT_LTE_RSRQ_THRESHOLDS);
+        bundle.putIntArray(
+                CarrierConfigManager.KEY_LTE_RSRP_THRESHOLDS_INT_ARRAY,
+                DEFAULT_LTE_RSRP_THRESHOLDS);
+        signalStrength.updateLevel(bundle, null);
+        return signalStrength;
+    }
+
+    private static SignalStrength createSignalStrengthLteReportRssnr(int lteRsrp, int lteRssnr) {
+
+        CellSignalStrengthLte lte = new CellSignalStrengthLte(
+                -89,                   // rssi
+                lteRsrp,               // rsrp
+                15,                    // rsrq
+                lteRssnr,              // rssnr
+                CellInfo.UNAVAILABLE,  // cqi
+                CellInfo.UNAVAILABLE); // timingAdvance
+
+        SignalStrength signalStrength = new SignalStrength(
+                new CellSignalStrengthCdma(),
+                new CellSignalStrengthGsm(),
+                new CellSignalStrengthWcdma(),
+                new CellSignalStrengthTdscdma(),
+                lte,
+                new CellSignalStrengthNr());
+
+        PersistableBundle bundle = new PersistableBundle();
+        bundle.putInt(
+                CarrierConfigManager.KEY_PARAMETERS_USED_FOR_LTE_SIGNAL_BAR_INT,
+                CellSignalStrengthLte.USE_RSRP | CellSignalStrengthLte.USE_RSSNR);
+        bundle.putIntArray(
+                CarrierConfigManager.KEY_LTE_RSRP_THRESHOLDS_INT_ARRAY,
+                DEFAULT_LTE_RSRP_THRESHOLDS);
+        bundle.putIntArray(
+                CarrierConfigManager.KEY_LTE_RSSNR_THRESHOLDS_INT_ARRAY,
+                DEFAULT_LTE_RSSNR_THRESHOLDS);
+        signalStrength.updateLevel(bundle, null);
+        return signalStrength;
+    }
+
+    @Test
+    public void testValidateInput() throws Exception {
+
+        SignalStrength ss;
+
+        // Input value of RSRQ: 4[dB]
+        ss = createSignalStrengthLteReportRsrq(-60, 4);
+        assertEquals(SignalStrength.INVALID, ss.getLteRsrq());
+
+        // Input value of RSRQ: 3[dB]
+        ss = createSignalStrengthLteReportRsrq(-60, 3);
+        assertEquals(3, ss.getLteRsrq());
+
+        // Input value of RSRQ: -34[dB]
+        ss = createSignalStrengthLteReportRsrq(-60, -34);
+        assertEquals(-34, ss.getLteRsrq());
+
+        // Input value of RSRQ: -35[dB]
+        ss = createSignalStrengthLteReportRsrq(-60, -35);
+        assertEquals(SignalStrength.INVALID, ss.getLteRsrq());
+
+        // Input value of RSSNR: 31[dB]
+        ss = createSignalStrengthLteReportRssnr(-60, 31);
+        assertEquals(SignalStrength.INVALID, ss.getLteRssnr());
+
+        // Input value of RSSNR: 30[dB]
+        ss = createSignalStrengthLteReportRssnr(-60, 30);
+        assertEquals(30, ss.getLteRssnr());
+
+        // Input value of RSSNR: -20[dB]
+        ss = createSignalStrengthLteReportRssnr(60, -20);
+        assertEquals(-20, ss.getLteRssnr());
+
+        // Input value of RSSNR: -21[dB]
+        ss = createSignalStrengthLteReportRssnr(60, -21);
+        assertEquals(SignalStrength.INVALID, ss.getLteRssnr());
+    }
+
+    @Test
+    public void testRsrqThresholds_rsrp_great() throws Exception {
+        assertEquals(SignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN,
+                createSignalStrengthLteReportRsrq(-98, -34).getLteLevel());
+        assertEquals(SignalStrength.SIGNAL_STRENGTH_POOR,
+                createSignalStrengthLteReportRsrq(-98, -19).getLteLevel());
+        assertEquals(SignalStrength.SIGNAL_STRENGTH_MODERATE,
+                createSignalStrengthLteReportRsrq(-98, -17).getLteLevel());
+        assertEquals(SignalStrength.SIGNAL_STRENGTH_GOOD,
+                createSignalStrengthLteReportRsrq(-98, -14).getLteLevel());
+        assertEquals(SignalStrength.SIGNAL_STRENGTH_GREAT,
+                createSignalStrengthLteReportRsrq(-98, -12).getLteLevel());
+    }
+
+    @Test
+    public void testRsrqThresholds_rsrp_good() throws Exception {
+        assertEquals(SignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN,
+                createSignalStrengthLteReportRsrq(-108, -34).getLteLevel());
+        assertEquals(SignalStrength.SIGNAL_STRENGTH_POOR,
+                createSignalStrengthLteReportRsrq(-108, -19).getLteLevel());
+        assertEquals(SignalStrength.SIGNAL_STRENGTH_MODERATE,
+                createSignalStrengthLteReportRsrq(-108, -17).getLteLevel());
+        assertEquals(SignalStrength.SIGNAL_STRENGTH_GOOD,
+                createSignalStrengthLteReportRsrq(-108, -14).getLteLevel());
+        assertEquals(SignalStrength.SIGNAL_STRENGTH_GOOD,
+                createSignalStrengthLteReportRsrq(-108, -12).getLteLevel());
+    }
+
+    @Test
+    public void testRsrqThresholds_rsrp_moderate() throws Exception {
+        assertEquals(SignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN,
+                createSignalStrengthLteReportRsrq(-118, -34).getLteLevel());
+        assertEquals(SignalStrength.SIGNAL_STRENGTH_POOR,
+                createSignalStrengthLteReportRsrq(-118, -19).getLteLevel());
+        assertEquals(SignalStrength.SIGNAL_STRENGTH_MODERATE,
+                createSignalStrengthLteReportRsrq(-118, -17).getLteLevel());
+        assertEquals(SignalStrength.SIGNAL_STRENGTH_MODERATE,
+                createSignalStrengthLteReportRsrq(-118, -14).getLteLevel());
+        assertEquals(SignalStrength.SIGNAL_STRENGTH_MODERATE,
+                createSignalStrengthLteReportRsrq(-118, -12).getLteLevel());
+    }
+
+    @Test
+    public void testRsrqThresholds_rsrp_poor() throws Exception {
+        assertEquals(SignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN,
+                createSignalStrengthLteReportRsrq(-128, -34).getLteLevel());
+        assertEquals(SignalStrength.SIGNAL_STRENGTH_POOR,
+                createSignalStrengthLteReportRsrq(-128, -19).getLteLevel());
+        assertEquals(SignalStrength.SIGNAL_STRENGTH_POOR,
+                createSignalStrengthLteReportRsrq(-128, -17).getLteLevel());
+        assertEquals(SignalStrength.SIGNAL_STRENGTH_POOR,
+                createSignalStrengthLteReportRsrq(-128, -14).getLteLevel());
+        assertEquals(SignalStrength.SIGNAL_STRENGTH_POOR,
+                createSignalStrengthLteReportRsrq(-128, -12).getLteLevel());
+    }
+
+    @Test
+    public void testRsrqThresholds_rsrp_unknown() throws Exception {
+        assertEquals(SignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN,
+                createSignalStrengthLteReportRsrq(-138, -34).getLteLevel());
+        assertEquals(SignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN,
+                createSignalStrengthLteReportRsrq(-138, -19).getLteLevel());
+        assertEquals(SignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN,
+                createSignalStrengthLteReportRsrq(-138, -17).getLteLevel());
+        assertEquals(SignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN,
+                createSignalStrengthLteReportRsrq(-138, -14).getLteLevel());
+        assertEquals(SignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN,
+                createSignalStrengthLteReportRsrq(-138, -12).getLteLevel());
+    }
+
+    @Test
+    public void testRssnrThresholds_rsrp_great() throws Exception {
+        assertEquals(SignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN,
+                createSignalStrengthLteReportRssnr(-98, -20).getLteLevel());
+        assertEquals(SignalStrength.SIGNAL_STRENGTH_POOR,
+                createSignalStrengthLteReportRssnr(-98, -3).getLteLevel());
+        assertEquals(SignalStrength.SIGNAL_STRENGTH_MODERATE,
+                createSignalStrengthLteReportRssnr(-98, 1).getLteLevel());
+        assertEquals(SignalStrength.SIGNAL_STRENGTH_GOOD,
+                createSignalStrengthLteReportRssnr(-98, 5).getLteLevel());
+        assertEquals(SignalStrength.SIGNAL_STRENGTH_GREAT,
+                createSignalStrengthLteReportRssnr(-98, 13).getLteLevel());
+    }
+
+    @Test
+    public void testRssnrThresholds_rsrp_good() throws Exception {
+        assertEquals(SignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN,
+                createSignalStrengthLteReportRssnr(-108, -20).getLteLevel());
+        assertEquals(SignalStrength.SIGNAL_STRENGTH_POOR,
+                createSignalStrengthLteReportRssnr(-108, -3).getLteLevel());
+        assertEquals(SignalStrength.SIGNAL_STRENGTH_MODERATE,
+                createSignalStrengthLteReportRssnr(-108, 1).getLteLevel());
+        assertEquals(SignalStrength.SIGNAL_STRENGTH_GOOD,
+                createSignalStrengthLteReportRssnr(-108, 5).getLteLevel());
+        assertEquals(SignalStrength.SIGNAL_STRENGTH_GOOD,
+                createSignalStrengthLteReportRssnr(-108, 13).getLteLevel());
+    }
+
+    @Test
+    public void testRssnrThresholds_rsrp_moderate() throws Exception {
+        assertEquals(SignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN,
+                createSignalStrengthLteReportRssnr(-118, -20).getLteLevel());
+        assertEquals(SignalStrength.SIGNAL_STRENGTH_POOR,
+                createSignalStrengthLteReportRssnr(-118, -3).getLteLevel());
+        assertEquals(SignalStrength.SIGNAL_STRENGTH_MODERATE,
+                createSignalStrengthLteReportRssnr(-118, 1).getLteLevel());
+        assertEquals(SignalStrength.SIGNAL_STRENGTH_MODERATE,
+                createSignalStrengthLteReportRssnr(-118, 5).getLteLevel());
+        assertEquals(SignalStrength.SIGNAL_STRENGTH_MODERATE,
+                createSignalStrengthLteReportRssnr(-118, 13).getLteLevel());
+    }
+
+    @Test
+    public void testRssnrThresholds_rsrp_poor() throws Exception {
+        assertEquals(SignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN,
+                createSignalStrengthLteReportRssnr(-128, -20).getLteLevel());
+        assertEquals(SignalStrength.SIGNAL_STRENGTH_POOR,
+                createSignalStrengthLteReportRssnr(-128, -3).getLteLevel());
+        assertEquals(SignalStrength.SIGNAL_STRENGTH_POOR,
+                createSignalStrengthLteReportRssnr(-128, 1).getLteLevel());
+        assertEquals(SignalStrength.SIGNAL_STRENGTH_POOR,
+                createSignalStrengthLteReportRssnr(-128, 5).getLteLevel());
+        assertEquals(SignalStrength.SIGNAL_STRENGTH_POOR,
+                createSignalStrengthLteReportRssnr(-128, 13).getLteLevel());
+    }
+
+    @Test
+    public void testRssnrThresholds_rsrp_unknown() throws Exception {
+        assertEquals(SignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN,
+                createSignalStrengthLteReportRssnr(-138, -20).getLteLevel());
+        assertEquals(SignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN,
+                createSignalStrengthLteReportRssnr(-138, -3).getLteLevel());
+        assertEquals(SignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN,
+                createSignalStrengthLteReportRssnr(-138, 1).getLteLevel());
+        assertEquals(SignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN,
+                createSignalStrengthLteReportRssnr(-138, 5).getLteLevel());
+        assertEquals(SignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN,
+                createSignalStrengthLteReportRssnr(-138, 13).getLteLevel());
+    }
 }
 
diff --git a/tests/telephonytests/src/com/android/internal/telephony/SignalThresholdInfoTest.java b/tests/telephonytests/src/com/android/internal/telephony/SignalThresholdInfoTest.java
new file mode 100644
index 0000000..ddf5bcd
--- /dev/null
+++ b/tests/telephonytests/src/com/android/internal/telephony/SignalThresholdInfoTest.java
@@ -0,0 +1,155 @@
+/*
+ * Copyright (C) 2019 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;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import android.os.Parcel;
+import android.telephony.SignalThresholdInfo;
+
+import androidx.test.filters.SmallTest;
+
+import junit.framework.TestCase;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+
+@RunWith(JUnit4.class)
+public class SignalThresholdInfoTest extends TestCase {
+    private static final int HYSTERESIS_DB = 2;
+    private static final int HYSTERESIS_MS = 30;
+    private static final int[] SSRSRP_THRESHOLDS = new int[] {-30, 10, 45, 130};
+
+    private final int[] mRssiThresholds = new int[] {-109, -103, -97, -89};
+    private final int[] mRscpThresholds = new int[] {-115, -105, -95, -85};
+    private final int[] mRsrpThresholds = new int[] {-128, -118, -108, -98};
+    private final int[] mRsrqThresholds = new int[] {-19, -17, -14, -12};
+    private final int[] mRssnrThresholds = new int[] {-30, 10, 45, 130};
+    private final int[][] mThresholds = new int[5][];
+
+    @Test
+    @SmallTest
+    public void testSignalThresholdInfo() throws Exception {
+        SignalThresholdInfo signalThresholdInfo = new SignalThresholdInfo(
+                SignalThresholdInfo.SIGNAL_SSRSRP,
+                HYSTERESIS_MS,
+                HYSTERESIS_DB,
+                SSRSRP_THRESHOLDS,
+                false);
+
+        assertEquals(SignalThresholdInfo.SIGNAL_SSRSRP,
+                signalThresholdInfo.getSignalMeasurement());
+        assertEquals(HYSTERESIS_MS, signalThresholdInfo.getHysteresisMs());
+        assertEquals(HYSTERESIS_DB, signalThresholdInfo.getHysteresisDb());
+        assertEquals(Arrays.toString(SSRSRP_THRESHOLDS), Arrays.toString(
+                signalThresholdInfo.getThresholds()));
+        assertFalse(signalThresholdInfo.isEnabled());
+    }
+
+    @Test
+    @SmallTest
+    public void testDefaultThresholdsConstruction() {
+        setThresholds();
+        ArrayList<SignalThresholdInfo> stList = setSignalThresholdInfoConstructor();
+
+        int count = 0;
+        for (SignalThresholdInfo st : stList) {
+            assertThat(st.getThresholds()).isEqualTo(mThresholds[count]);
+            count++;
+        }
+    }
+
+    @Test
+    @SmallTest
+    public void testDefaultThresholdsParcel() {
+        ArrayList<SignalThresholdInfo> stList = setSignalThresholdInfoConstructor();
+
+        for (SignalThresholdInfo st : stList) {
+            Parcel p = Parcel.obtain();
+            st.writeToParcel(p, 0);
+            p.setDataPosition(0);
+
+            SignalThresholdInfo newSt = SignalThresholdInfo.CREATOR.createFromParcel(p);
+            assertThat(newSt).isEqualTo(st);
+        }
+    }
+
+    @Test
+    @SmallTest
+    public void testGetSignalThresholdInfo() {
+        ArrayList<SignalThresholdInfo> stList = new ArrayList<>();
+        stList.add(new SignalThresholdInfo(0, 0, 0, null, false));
+        stList.add(new SignalThresholdInfo(SignalThresholdInfo.SIGNAL_RSSI, HYSTERESIS_MS,
+                HYSTERESIS_DB, mRssiThresholds, false));
+
+        assertThat(stList.get(0).getThresholds()).isEqualTo(null);
+        assertThat(stList.get(1).getSignalMeasurement()).isEqualTo(SignalThresholdInfo.SIGNAL_RSSI);
+        assertThat(stList.get(1).getThresholds()).isEqualTo(mRssiThresholds);
+    }
+
+    @Test
+    @SmallTest
+    public void testEqualsSignalThresholdInfo() {
+        final int[] dummyThresholds = new int[] {-100, -1, 1, 100};
+        SignalThresholdInfo st1 = new SignalThresholdInfo(1, HYSTERESIS_MS, HYSTERESIS_DB,
+                mRssiThresholds, false);
+        SignalThresholdInfo st2 = new SignalThresholdInfo(2, HYSTERESIS_MS, HYSTERESIS_DB,
+                mRssiThresholds, false);
+        SignalThresholdInfo st3 = new SignalThresholdInfo(1, HYSTERESIS_MS, HYSTERESIS_DB,
+                dummyThresholds, false);
+        SignalThresholdInfo st4 = new SignalThresholdInfo(1, HYSTERESIS_MS, HYSTERESIS_DB,
+                mRssiThresholds, false);
+
+        //Return true if all SignalThresholdInfo values match.
+        assertTrue(st1.equals(st1));
+        assertFalse(st1.equals(st2));
+        assertFalse(st1.equals(st3));
+        assertTrue(st1.equals(st4));
+        //Return false if the object of argument is other than SignalThresholdInfo.
+        assertFalse(st1.equals(new String("test")));
+    }
+
+    private void setThresholds() {
+        mThresholds[0] = mRssiThresholds;
+        mThresholds[1] = mRscpThresholds;
+        mThresholds[2] = mRsrpThresholds;
+        mThresholds[3] = mRsrqThresholds;
+        mThresholds[4] = mRssnrThresholds;
+    }
+
+    private ArrayList<SignalThresholdInfo> setSignalThresholdInfoConstructor() {
+        ArrayList<SignalThresholdInfo> stList = new ArrayList<>();
+        stList.add(new SignalThresholdInfo(SignalThresholdInfo.SIGNAL_RSSI, HYSTERESIS_MS,
+                HYSTERESIS_DB, mRssiThresholds, false));
+        stList.add(new SignalThresholdInfo(SignalThresholdInfo.SIGNAL_RSCP, HYSTERESIS_MS,
+                HYSTERESIS_DB, mRscpThresholds, false));
+        stList.add(new SignalThresholdInfo(SignalThresholdInfo.SIGNAL_RSRP, HYSTERESIS_MS,
+                HYSTERESIS_DB, mRsrpThresholds, false));
+        stList.add(new SignalThresholdInfo(SignalThresholdInfo.SIGNAL_RSRQ, HYSTERESIS_MS,
+                HYSTERESIS_DB, mRsrqThresholds, false));
+        stList.add(new SignalThresholdInfo(SignalThresholdInfo.SIGNAL_RSSNR, HYSTERESIS_MS,
+                HYSTERESIS_DB, mRssnrThresholds, false));
+
+        return stList;
+    }
+}
diff --git a/tests/telephonytests/src/com/android/internal/telephony/SimPhoneBookTest.java b/tests/telephonytests/src/com/android/internal/telephony/SimPhoneBookTest.java
index 9d5c680..15e16fb 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/SimPhoneBookTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/SimPhoneBookTest.java
@@ -16,22 +16,26 @@
 
 package com.android.internal.telephony;
 
-import android.os.ServiceManager;
+import android.telephony.TelephonyFrameworkInitializer;
 import android.test.suitebuilder.annotation.Suppress;
 
 import com.android.internal.telephony.uicc.AdnRecord;
 import com.android.internal.telephony.uicc.IccConstants;
 
-import java.util.List;
-
 import junit.framework.TestCase;
 
+import java.util.List;
+
 @Suppress
 public class SimPhoneBookTest extends TestCase {
 
     public void testBasic() throws Exception {
         IIccPhoneBook simPhoneBook =
-                IIccPhoneBook.Stub.asInterface(ServiceManager.getService("simphonebook"));
+                IIccPhoneBook.Stub.asInterface(
+                        TelephonyFrameworkInitializer
+                                .getTelephonyServiceManager()
+                                .getIccPhoneBookServiceRegisterer()
+                                .get());
         assertNotNull(simPhoneBook);
 
         int size[] = simPhoneBook.getAdnRecordsSize(IccConstants.EF_ADN);
@@ -50,7 +54,7 @@
         AdnRecord originalAdn = null;
         // We need to maintain the state of the SIM before and after the test.
         // Since this test doesn't mock the SIM we try to get a valid ADN record,
-        // for 3 tries and if this fails, we bail out. 
+        // for 3 tries and if this fails, we bail out.
         for (adnIndex = 3 ; adnIndex >= 1; adnIndex--) {
             listIndex = adnIndex - 1; // listIndex is zero based.
             originalAdn = adnRecordList.get(listIndex);
diff --git a/tests/telephonytests/src/com/android/internal/telephony/SimSmsTest.java b/tests/telephonytests/src/com/android/internal/telephony/SimSmsTest.java
index 7dc5777..0bacc1d 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/SimSmsTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/SimSmsTest.java
@@ -17,21 +17,25 @@
 package com.android.internal.telephony;
 
 import android.app.ActivityThread;
-import android.os.ServiceManager;
+import android.telephony.TelephonyFrameworkInitializer;
 import android.test.suitebuilder.annotation.MediumTest;
 import android.test.suitebuilder.annotation.Suppress;
 
-import java.util.List;
-
 import junit.framework.TestCase;
 
+import java.util.List;
+
 public class SimSmsTest extends TestCase {
 
     @MediumTest
     @Suppress // TODO: suppress this test for now since it doesn't work on the emulator
     public void testBasic() throws Exception {
 
-        ISms sms = ISms.Stub.asInterface(ServiceManager.getService("isms"));
+        ISms sms = ISms.Stub.asInterface(
+                TelephonyFrameworkInitializer
+                        .getTelephonyServiceManager()
+                        .getSmsServiceRegisterer()
+                        .get());
         assertNotNull(sms);
 
         int preferredSmsSubscription = sms.getPreferredSmsSubscription();
diff --git a/src/java/com/android/internal/telephony/test/SimulatedCommands.java b/tests/telephonytests/src/com/android/internal/telephony/SimulatedCommands.java
similarity index 93%
rename from src/java/com/android/internal/telephony/test/SimulatedCommands.java
rename to tests/telephonytests/src/com/android/internal/telephony/SimulatedCommands.java
index 0cdcb49..967ed49 100644
--- a/src/java/com/android/internal/telephony/test/SimulatedCommands.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/SimulatedCommands.java
@@ -16,6 +16,7 @@
 
 package com.android.internal.telephony.test;
 
+import android.compat.annotation.UnsupportedAppUsage;
 import android.hardware.radio.V1_0.DataRegStateResult;
 import android.hardware.radio.V1_0.SetupDataCallResult;
 import android.hardware.radio.V1_0.VoiceRegStateResult;
@@ -42,10 +43,11 @@
 import android.telephony.ImsiEncryptionInfo;
 import android.telephony.NetworkRegistrationInfo;
 import android.telephony.NetworkScanRequest;
-import android.telephony.Rlog;
 import android.telephony.ServiceState;
 import android.telephony.SignalStrength;
+import android.telephony.SignalThresholdInfo;
 import android.telephony.TelephonyManager;
+import android.telephony.data.ApnSetting;
 import android.telephony.data.DataCallResponse;
 import android.telephony.data.DataProfile;
 import android.telephony.emergency.EmergencyNumber;
@@ -65,9 +67,11 @@
 import com.android.internal.telephony.cdma.CdmaSmsBroadcastConfigInfo;
 import com.android.internal.telephony.gsm.SmsBroadcastConfigInfo;
 import com.android.internal.telephony.gsm.SuppServiceNotification;
+import com.android.internal.telephony.uicc.IccCardApplicationStatus.PersoSubState;
 import com.android.internal.telephony.uicc.IccCardStatus;
 import com.android.internal.telephony.uicc.IccIoResult;
 import com.android.internal.telephony.uicc.IccSlotStatus;
+import com.android.telephony.Rlog;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -76,7 +80,7 @@
 
 public class SimulatedCommands extends BaseCommands
         implements CommandsInterface, SimulatedRadioControl {
-    private final static String LOG_TAG = "SimulatedCommands";
+    private static final String LOG_TAG = "SimulatedCommands";
 
     private enum SimLockState {
         NONE,
@@ -92,27 +96,34 @@
         SIM_PERM_LOCKED
     }
 
-    private final static SimLockState INITIAL_LOCK_STATE = SimLockState.NONE;
-    public final static String DEFAULT_SIM_PIN_CODE = "1234";
-    private final static String SIM_PUK_CODE = "12345678";
-    private final static SimFdnState INITIAL_FDN_STATE = SimFdnState.NONE;
-    public final static String DEFAULT_SIM_PIN2_CODE = "5678";
-    private final static String SIM_PUK2_CODE = "87654321";
-    public final static String FAKE_LONG_NAME = "Fake long name";
-    public final static String FAKE_SHORT_NAME = "Fake short name";
-    public final static String FAKE_MCC_MNC = "310260";
-    public final static String FAKE_IMEI = "012345678901234";
-    public final static String FAKE_IMEISV = "99";
-    public final static String FAKE_ESN = "1234";
-    public final static String FAKE_MEID = "1234";
-    public final static int DEFAULT_PIN1_ATTEMPT = 5;
-    public final static int DEFAULT_PIN2_ATTEMPT = 5;
+    private static final SimLockState INITIAL_LOCK_STATE = SimLockState.NONE;
+    public static final String DEFAULT_SIM_PIN_CODE = "1234";
+    private static final String SIM_PUK_CODE = "12345678";
+    private static final SimFdnState INITIAL_FDN_STATE = SimFdnState.NONE;
+    public static final String DEFAULT_SIM_PIN2_CODE = "5678";
+    private static final String SIM_PUK2_CODE = "87654321";
+    public static final String FAKE_LONG_NAME = "Fake long name";
+    public static final String FAKE_SHORT_NAME = "Fake short name";
+    public static final String FAKE_MCC_MNC = "310260";
+    public static final String FAKE_IMEI = "012345678901234";
+    public static final String FAKE_IMEISV = "99";
+    public static final String FAKE_ESN = "1234";
+    public static final String FAKE_MEID = "1234";
+    public static final int DEFAULT_PIN1_ATTEMPT = 5;
+    public static final int DEFAULT_PIN2_ATTEMPT = 5;
+    public static final int ICC_AUTHENTICATION_MODE_DEFAULT = 0;
+    public static final int ICC_AUTHENTICATION_MODE_NULL = 1;
+    public static final int ICC_AUTHENTICATION_MODE_TIMEOUT = 2;
+    // Maximum time in millisecond to wait for a IccSim Challenge before assuming it will not
+    // arrive and returning null to the callers.
+    public static final  long ICC_SIM_CHALLENGE_TIMEOUT_MILLIS = 2500;
 
     private String mImei;
     private String mImeiSv;
 
     //***** Instance Variables
 
+    @UnsupportedAppUsage
     SimulatedGsmCallState simulatedCallState;
     HandlerThread mHandlerThread;
     SimLockState mSimLockedState;
@@ -153,10 +164,16 @@
 
     int mNextCallFailCause = CallFailCause.NORMAL_CLEARING;
 
+    @UnsupportedAppUsage
     private boolean mDcSuccess = true;
     private SetupDataCallResult mSetupDataCallResult;
     private boolean mIsRadioPowerFailResponse = false;
 
+    public boolean mSetRadioPowerForEmergencyCall;
+    public boolean mSetRadioPowerAsSelectedPhoneForEmergencyCall;
+
+    // mode for Icc Sim Authentication
+    private int mAuthenticationMode;
     //***** Constructor
     public
     SimulatedCommands() {
@@ -174,11 +191,13 @@
         mSimFdnEnabledState = INITIAL_FDN_STATE;
         mSimFdnEnabled = (mSimFdnEnabledState != SimFdnState.NONE);
         mPin2Code = DEFAULT_SIM_PIN2_CODE;
+        mAuthenticationMode = ICC_AUTHENTICATION_MODE_DEFAULT;
     }
 
-    public void dispose() {
+    public void dispose() throws Exception {
         if (mHandlerThread != null) {
             mHandlerThread.quit();
+            mHandlerThread.join();
         }
     }
 
@@ -503,6 +522,12 @@
         unimplemented(result);
     }
 
+    @Override
+    public void supplySimDepersonalization(PersoSubState persoType,
+            String conrolKey, Message result) {
+        unimplemented(result);
+    }
+
     /**
      *  returned message
      *  retMsg.obj = AsyncResult ar
@@ -801,6 +826,7 @@
      *  ar.userObject contains the original value of result.obj
      *  ar.result is null on success and failure
      */
+    @UnsupportedAppUsage
     @Override
     public void acceptCall (Message result) {
         boolean success;
@@ -1094,7 +1120,7 @@
     @Override
     public void sendSMS (String smscPDU, String pdu, Message result) {
         SimulatedCommandsVerifier.getInstance().sendSMS(smscPDU, pdu, result);
-        resultSuccess(result, new SmsResponse(0 /*messageRef*/, null, 0));
+        resultSuccess(result, new SmsResponse(0 /*messageRef*/, null, SmsResponse.NO_ERROR_CODE));
     }
 
     /**
@@ -1129,7 +1155,7 @@
     }
 
     @Override
-    public void writeSmsToRuim(int status, String pdu, Message response) {
+    public void writeSmsToRuim(int status, byte[] pdu, Message response) {
         Rlog.d(LOG_TAG, "Write SMS to RUIM with status " + status);
         unimplemented(response);
     }
@@ -1174,6 +1200,14 @@
             }
         }
 
+        // Store different cids to simulate concurrent IMS and default data calls
+        if ((dataProfile.getSupportedApnTypesBitmask() & ApnSetting.TYPE_IMS)
+            == ApnSetting.TYPE_IMS) {
+            mSetupDataCallResult.cid = 0;
+        } else {
+            mSetupDataCallResult.cid = 1;
+        }
+
         DataCallResponse response = RIL.convertDataCallResult(mSetupDataCallResult);
         if (mDcSuccess) {
             resultSuccess(result, response);
@@ -1244,12 +1278,16 @@
     }
 
     @Override
-    public void setRadioPower(boolean on, Message result) {
+    public void setRadioPower(boolean on, boolean forEmergencyCall,
+            boolean preferredForEmergencyCall, Message result) {
         if (mIsRadioPowerFailResponse) {
             resultFail(result, null, new RuntimeException("setRadioPower failed!"));
             return;
         }
 
+        mSetRadioPowerForEmergencyCall = forEmergencyCall;
+        mSetRadioPowerAsSelectedPhoneForEmergencyCall = preferredForEmergencyCall;
+
         if(on) {
             setRadioState(TelephonyManager.RADIO_POWER_ON, false /* forceNotifyRegistrants */);
         } else {
@@ -1391,8 +1429,9 @@
     @Override
     public void exitEmergencyCallbackMode(Message result) {unimplemented(result);}
     @Override
-    public void setNetworkSelectionModeManual(
-            String operatorNumeric, Message result) {unimplemented(result);}
+    public void setNetworkSelectionModeManual(String operatorNumeric, int ran, Message result) {
+        unimplemented(result);
+    }
 
     /**
      * Queries whether the current network selection mode is automatic
@@ -1654,6 +1693,7 @@
 
     //***** Private Methods
 
+    @UnsupportedAppUsage
     private void unimplemented(Message result) {
         if (result != null) {
             AsyncResult.forMessage(result).exception
@@ -1667,6 +1707,7 @@
         }
     }
 
+    @UnsupportedAppUsage
     private void resultSuccess(Message result, Object ret) {
         if (result != null) {
             AsyncResult.forMessage(result).result = ret;
@@ -1678,6 +1719,7 @@
         }
     }
 
+    @UnsupportedAppUsage
     private void resultFail(Message result, Object ret, Throwable tr) {
         if (result != null) {
             AsyncResult.forMessage(result, ret, tr);
@@ -1788,6 +1830,13 @@
         resultSuccess(response, null);
     }
 
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void sendCdmaSMSExpectMore(byte[] pdu, Message response){
+    }
+
     @Override
     public void setCdmaBroadcastActivation(boolean activate, Message response) {
         unimplemented(response);
@@ -1871,7 +1920,38 @@
 
     @Override
     public void requestIccSimAuthentication(int authContext, String data, String aid, Message response) {
-        unimplemented(response);
+        switch (mAuthenticationMode) {
+            case ICC_AUTHENTICATION_MODE_TIMEOUT:
+                break;
+
+            case ICC_AUTHENTICATION_MODE_NULL:
+                sendMessageResponse(response, null);
+                break;
+
+            default:
+                if (data == null || data.length() == 0) {
+                    sendMessageResponse(response,  null);
+                } else {
+                    sendMessageResponse(response, new IccIoResult(0, 0, (byte[]) data.getBytes()));
+                }
+                break;
+        }
+    }
+
+    /**
+     * Helper function to send response msg
+     * @param msg Response message to be sent
+     * @param ret Return object to be included in the response message
+     */
+    private void sendMessageResponse(Message msg, Object ret) {
+        if (msg != null) {
+            AsyncResult.forMessage(msg, ret, null);
+            msg.sendToTarget();
+        }
+    }
+
+    public void setAuthenticationMode(int authenticationMode) {
+        mAuthenticationMode = authenticationMode;
     }
 
     @Override
@@ -1965,7 +2045,7 @@
     public void sendImsCdmaSms(byte[] pdu, int retry, int messageRef,
             Message response){
         SimulatedCommandsVerifier.getInstance().sendImsCdmaSms(pdu, retry, messageRef, response);
-        resultSuccess(response, new SmsResponse(0 /*messageRef*/, null, 0));
+        resultSuccess(response, new SmsResponse(0 /*messageRef*/, null, SmsResponse.NO_ERROR_CODE));
     }
 
     @Override
@@ -1973,7 +2053,7 @@
             int retry, int messageRef, Message response){
         SimulatedCommandsVerifier.getInstance().sendImsGsmSms(smscPDU, pdu, retry, messageRef,
                 response);
-        resultSuccess(response, new SmsResponse(0 /*messageRef*/, null, 0));
+        resultSuccess(response, new SmsResponse(0 /*messageRef*/, null, SmsResponse.NO_ERROR_CODE));
     }
 
     @Override
@@ -2139,6 +2219,12 @@
         super.registerForExitEmergencyCallbackMode(h, what, obj);
     }
 
+    @Override
+    public void registerForSrvccStateChanged(Handler h, int what, Object obj) {
+        SimulatedCommandsVerifier.getInstance().registerForSrvccStateChanged(h, what, obj);
+        super.registerForSrvccStateChanged(h, what, obj);
+    }
+
     public void notifyRadioOn() {
         mOnRegistrants.notifyRegistrants();
     }
@@ -2231,8 +2317,8 @@
     }
 
     @Override
-    public void setSignalStrengthReportingCriteria(int hysteresisMs, int hysteresisDb,
-            int[] thresholdsDbm, int ran, Message result) {
+    public void setSignalStrengthReportingCriteria(SignalThresholdInfo signalThresholdInfo,
+            int ran, Message result) {
     }
 
     @Override
@@ -2300,4 +2386,10 @@
     public Handler getHandler() {
         return mHandlerThread.getThreadHandler();
     }
+
+    @Override
+    public void getBarringInfo(Message result) {
+        SimulatedCommandsVerifier.getInstance().getBarringInfo(result);
+        resultSuccess(result, null);
+    }
 }
diff --git a/src/java/com/android/internal/telephony/test/SimulatedCommandsVerifier.java b/tests/telephonytests/src/com/android/internal/telephony/SimulatedCommandsVerifier.java
similarity index 95%
rename from src/java/com/android/internal/telephony/test/SimulatedCommandsVerifier.java
rename to tests/telephonytests/src/com/android/internal/telephony/SimulatedCommandsVerifier.java
index 722d1e8..6646c35 100644
--- a/src/java/com/android/internal/telephony/test/SimulatedCommandsVerifier.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/SimulatedCommandsVerifier.java
@@ -16,12 +16,14 @@
 
 package com.android.internal.telephony.test;
 
+import android.compat.annotation.UnsupportedAppUsage;
 import android.net.KeepalivePacketData;
 import android.net.LinkProperties;
 import android.os.Handler;
 import android.os.Message;
 import android.telephony.ImsiEncryptionInfo;
 import android.telephony.NetworkScanRequest;
+import android.telephony.SignalThresholdInfo;
 import android.telephony.TelephonyManager;
 import android.telephony.data.DataProfile;
 import android.telephony.emergency.EmergencyNumber;
@@ -29,6 +31,7 @@
 import com.android.internal.telephony.CommandsInterface;
 import com.android.internal.telephony.RadioCapability;
 import com.android.internal.telephony.UUSInfo;
+import com.android.internal.telephony.uicc.IccCardApplicationStatus.PersoSubState;
 import com.android.internal.telephony.cdma.CdmaSmsBroadcastConfigInfo;
 import com.android.internal.telephony.gsm.SmsBroadcastConfigInfo;
 
@@ -39,6 +42,7 @@
 
     }
 
+    @UnsupportedAppUsage
     public static SimulatedCommandsVerifier getInstance() {
         if (sInstance == null) {
             sInstance = new SimulatedCommandsVerifier();
@@ -658,6 +662,12 @@
     }
 
     @Override
+    public void supplySimDepersonalization(PersoSubState persoType,
+            String controlKey, Message result) {
+
+    }
+
+    @Override
     public void getCurrentCalls(Message result) {
 
     }
@@ -839,6 +849,11 @@
     }
 
     @Override
+    public void sendCdmaSMSExpectMore(byte[] pdu, Message response) {
+
+    }
+
+    @Override
     public void sendImsGsmSms(String smscPDU, String pdu, int retry, int messageRef,
                               Message response) {
 
@@ -865,7 +880,7 @@
     }
 
     @Override
-    public void writeSmsToRuim(int status, String pdu, Message response) {
+    public void writeSmsToRuim(int status, byte[] pdu, Message response) {
 
     }
 
@@ -926,6 +941,7 @@
 
     }
 
+    @UnsupportedAppUsage
     @Override
     public void setCallForward(int action, int cfReason, int serviceClass, String number,
                                int timeSeconds, Message response) {
@@ -944,7 +960,7 @@
     }
 
     @Override
-    public void setNetworkSelectionModeManual(String operatorNumeric, Message response) {
+    public void setNetworkSelectionModeManual(String operatorNumeric, int ran, Message response) {
 
     }
 
@@ -1123,7 +1139,7 @@
     }
 
     @Override
-    public void sendCDMAFeatureCode(String FeatureCode, Message response) {
+    public void sendCDMAFeatureCode(String featureCode, Message response) {
 
     }
 
@@ -1256,7 +1272,7 @@
     }
 
     @Override
-    public void iccOpenLogicalChannel(String AID, int p2, Message response) {
+    public void iccOpenLogicalChannel(String aid, int p2, Message response) {
 
     }
 
@@ -1389,8 +1405,8 @@
     }
 
     @Override
-    public void setSignalStrengthReportingCriteria(int hysteresisMs, int hysteresisDb,
-            int[] thresholdsDbm, int ran, Message result) {
+    public void setSignalStrengthReportingCriteria(SignalThresholdInfo signalThresholdInfo,
+            int ran, Message result) {
     }
 
     @Override
@@ -1439,4 +1455,14 @@
     @Override
     public void stopNattKeepalive(int sessionHandle, Message result)  {
     }
+
+    @Override
+    public void registerUiccApplicationEnablementChanged(Handler h, int what, Object obj) {}
+
+    @Override
+    public void unregisterUiccApplicationEnablementChanged(Handler h) {}
+
+    @Override
+    public void getBarringInfo(Message result) {
+    }
 }
diff --git a/src/java/com/android/internal/telephony/test/SimulatedGsmCallState.java b/tests/telephonytests/src/com/android/internal/telephony/SimulatedGsmCallState.java
similarity index 98%
rename from src/java/com/android/internal/telephony/test/SimulatedGsmCallState.java
rename to tests/telephonytests/src/com/android/internal/telephony/SimulatedGsmCallState.java
index 75e84c4..53fd987 100644
--- a/src/java/com/android/internal/telephony/test/SimulatedGsmCallState.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/SimulatedGsmCallState.java
@@ -16,16 +16,18 @@
 
 package com.android.internal.telephony.test;
 
+import android.compat.annotation.UnsupportedAppUsage;
+import android.os.Handler;
 import android.os.Looper;
 import android.os.Message;
-import android.os.Handler;
 import android.telephony.PhoneNumberUtils;
+
 import com.android.internal.telephony.ATParseEx;
 import com.android.internal.telephony.DriverCall;
-import java.util.List;
-import java.util.ArrayList;
+import com.android.telephony.Rlog;
 
-import android.telephony.Rlog;
+import java.util.ArrayList;
+import java.util.List;
 
 class CallInfo {
     enum State {
@@ -388,6 +390,7 @@
         return found;
     }
 
+    @UnsupportedAppUsage
     public boolean
     onChld(char c0, char c1) {
         boolean ret;
@@ -444,6 +447,7 @@
         return ret;
     }
 
+    @UnsupportedAppUsage
     public boolean
     releaseHeldOrUDUB() {
         boolean found = false;
@@ -474,6 +478,7 @@
     }
 
 
+    @UnsupportedAppUsage
     public boolean
     releaseActiveAcceptHeldOrWaiting() {
         boolean foundHeld = false;
@@ -529,6 +534,7 @@
         return true;
     }
 
+    @UnsupportedAppUsage
     public boolean
     switchActiveAndHeldOrWaiting() {
         boolean hasHeld = false;
@@ -562,6 +568,7 @@
     }
 
 
+    @UnsupportedAppUsage
     public boolean
     separateCall(int index) {
         try {
@@ -603,6 +610,7 @@
 
 
 
+    @UnsupportedAppUsage
     public boolean
     conference() {
         int countCalls = 0;
diff --git a/tests/telephonytests/src/com/android/internal/telephony/SmsDispatchersControllerTest.java b/tests/telephonytests/src/com/android/internal/telephony/SmsDispatchersControllerTest.java
index 094908e..90ec3d5 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/SmsDispatchersControllerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/SmsDispatchersControllerTest.java
@@ -16,8 +16,6 @@
 
 package com.android.internal.telephony;
 
-import static com.android.internal.telephony.TelephonyTestUtils.waitForMs;
-
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
@@ -31,59 +29,43 @@
 import static org.mockito.Mockito.verify;
 
 import android.app.ActivityManager;
-import android.os.HandlerThread;
 import android.os.Message;
 import android.provider.Telephony.Sms.Intents;
 import android.test.FlakyTest;
 import android.test.suitebuilder.annotation.SmallTest;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
 import android.util.Singleton;
 
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
+import org.junit.runner.RunWith;
 import org.mockito.ArgumentCaptor;
 import org.mockito.Mock;
 
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper
 public class SmsDispatchersControllerTest extends TelephonyTest {
     @Mock
     private SMSDispatcher.SmsTracker mTracker;
 
     private SmsDispatchersController mSmsDispatchersController;
-    private ImsSmsDispatcherTestHandler mImsSmsDispatcherTestHandler;
     private boolean mInjectionCallbackTriggered = false;
-    private static final String TEST_INTENT = "com.android.internal.telephony.TEST_INTENT";
-    private static final int TEST_TIMEOUT = 5000;
-
-    private class ImsSmsDispatcherTestHandler extends HandlerThread {
-
-        private ImsSmsDispatcherTestHandler(String name) {
-            super(name);
-        }
-
-        @Override
-        public void onLooperPrepared() {
-            mSmsDispatchersController = new SmsDispatchersController(mPhone, mSmsStorageMonitor,
-                    mSmsUsageMonitor);
-            //Initial state of RIL is power on, need to wait util RADIO_ON msg get handled
-            waitForMs(200);
-            setReady(true);
-        }
-    }
-
     @Before
     public void setUp() throws Exception {
         super.setUp(getClass().getSimpleName());
         setupMockPackagePermissionChecks();
 
-        mImsSmsDispatcherTestHandler = new ImsSmsDispatcherTestHandler(getClass().getSimpleName());
-        mImsSmsDispatcherTestHandler.start();
-        waitUntilReady();
+        mSmsDispatchersController = new SmsDispatchersController(mPhone, mSmsStorageMonitor,
+            mSmsUsageMonitor);
+        processAllMessages();
     }
 
     @After
     public void tearDown() throws Exception {
+        mSmsDispatchersController.dispose();
         mSmsDispatchersController = null;
-        mImsSmsDispatcherTestHandler.quit();
         super.tearDown();
     }
 
@@ -105,7 +87,7 @@
     public void testSendImsGmsTest() throws Exception {
         switchImsSmsFormat(PhoneConstants.PHONE_TYPE_GSM);
         mSmsDispatchersController.sendText("111"/* desAddr*/, "222" /*scAddr*/, TAG,
-                null, null, null, null, false, -1, false, -1, false);
+                null, null, null, null, false, -1, false, -1, false, 0L);
         verify(mSimulatedCommandsVerifier).sendImsGsmSms(eq("038122F2"),
                 eq("0100038111F100001CD3F69C989EC3C3F431BA2C9F0FDF6EBAFCCD6697E5D4F29C0E"), eq(0), eq(0),
                 any(Message.class));
@@ -115,7 +97,7 @@
     public void testSendImsGmsTestWithOutDesAddr() throws Exception {
         switchImsSmsFormat(PhoneConstants.PHONE_TYPE_GSM);
         mSmsDispatchersController.sendText(null, "222" /*scAddr*/, TAG,
-                null, null, null, null, false, -1, false, -1, false);
+                null, null, null, null, false, -1, false, -1, false, 0L);
         verify(mSimulatedCommandsVerifier, times(0)).sendImsGsmSms(anyString(), anyString(),
                 anyInt(), anyInt(), any(Message.class));
     }
@@ -124,7 +106,7 @@
     public void testSendImsCdmaTest() throws Exception {
         switchImsSmsFormat(PhoneConstants.PHONE_TYPE_CDMA);
         mSmsDispatchersController.sendText("111"/* desAddr*/, "222" /*scAddr*/, TAG,
-                null, null, null, null, false, -1, false, -1, false);
+                null, null, null, null, false, -1, false, -1, false, 0L);
         verify(mSimulatedCommandsVerifier).sendImsCdmaSms((byte[])any(), eq(0), eq(0),
                 any(Message.class));
     }
@@ -169,17 +151,15 @@
                    assertEquals(Intents.RESULT_SMS_GENERIC_ERROR, result);
                 }
         );
-        waitForMs(100);
+        processAllMessages();
         assertEquals(true, mInjectionCallbackTriggered);
     }
 
     private void switchImsSmsFormat(int phoneType) {
         mSimulatedCommands.setImsRegistrationState(new int[]{1, phoneType});
         mSimulatedCommands.notifyImsNetworkStateChanged();
-        /* wait for async msg get handled */
-        waitForHandlerAction(mSmsDispatchersController, TEST_TIMEOUT);
         /* handle EVENT_IMS_STATE_DONE */
-        waitForHandlerAction(mSmsDispatchersController, TEST_TIMEOUT);
+        processAllMessages();
         assertTrue(mSmsDispatchersController.isIms());
     }
 }
diff --git a/tests/telephonytests/src/com/android/internal/telephony/SmsMessageBodyTest.java b/tests/telephonytests/src/com/android/internal/telephony/SmsMessageBodyTest.java
index 32dfef8..bd963ef 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/SmsMessageBodyTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/SmsMessageBodyTest.java
@@ -16,18 +16,11 @@
 
 package com.android.internal.telephony;
 
-import android.telephony.SmsMessage;
 import android.telephony.TelephonyManager;
-import android.telephony.Rlog;
 import android.test.AndroidTestCase;
-import android.test.suitebuilder.annotation.LargeTest;
-import android.test.suitebuilder.annotation.MediumTest;
 import android.test.suitebuilder.annotation.SmallTest;
-import android.util.Log;
 
-import com.android.internal.telephony.SmsConstants;
-
-import java.util.Random;
+import com.android.telephony.Rlog;
 
 /**
  * Test cases to verify selection of the optimal 7 bit encoding tables
diff --git a/tests/telephonytests/src/com/android/internal/telephony/SmsNumberUtilsTest.java b/tests/telephonytests/src/com/android/internal/telephony/SmsNumberUtilsTest.java
index 05c2cb8..ff3c36c 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/SmsNumberUtilsTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/SmsNumberUtilsTest.java
@@ -16,6 +16,9 @@
 
 package com.android.internal.telephony;
 
+import static org.junit.Assert.assertEquals;
+import static org.mockito.Mockito.doReturn;
+
 import android.database.Cursor;
 import android.database.MatrixCursor;
 import android.net.Uri;
@@ -31,10 +34,6 @@
 
 import java.util.Arrays;
 
-import static org.junit.Assert.assertEquals;
-import static org.mockito.Matchers.anyInt;
-import static org.mockito.Mockito.doReturn;
-
 public class SmsNumberUtilsTest extends TelephonyTest {
 
     private static final String TMO_MCC_MNC = "310260";
@@ -119,7 +118,7 @@
 
         mHbpcdContentProvider = new HbpcdContentProvider();
 
-        doReturn(TMO_MCC_MNC).when(mTelephonyManager).getNetworkOperator(anyInt());
+        doReturn(TMO_MCC_MNC).when(mTelephonyManager).getNetworkOperator();
 
         ((MockContentResolver) mContextFixture.getTestDouble().getContentResolver())
                 .addProvider(HbpcdLookup.MccIdd.CONTENT_URI.getAuthority(), mHbpcdContentProvider);
@@ -138,103 +137,116 @@
     @Test
     @SmallTest
     public void testInvalidNumberConversion() {
-        assertEquals("123", SmsNumberUtils.filterDestAddr(mPhone, "123"));
+        assertEquals("123", SmsNumberUtils.filterDestAddr(mContext, mPhone.getSubId(), "123"));
     }
 
     @Test
     @SmallTest
     public void testNaPcCountryCodeAreaLocalNumberConversion() {
         // NP_NANP_NBPCD_CC_AREA_LOCAL tests
-        doReturn(PhoneConstants.PHONE_TYPE_CDMA).when(mPhone).getPhoneType();
-        assertEquals("18583420022", SmsNumberUtils.filterDestAddr(mPhone, "+1-858-342-0022"));
+        doReturn(TelephonyManager.PHONE_TYPE_CDMA).when(mTelephonyManager).getPhoneType();
+        assertEquals("18583420022", SmsNumberUtils.filterDestAddr(mContext, mPhone.getSubId(),
+                "+1-858-342-0022"));
     }
 
     @Test
     @SmallTest
     public void testPcCountryCodeAreaLocalNumberConversion() {
         // NP_NBPCD_CC_AREA_LOCAL tests
-        assertEquals("01188671234567", SmsNumberUtils.filterDestAddr(mPhone, "+886-7-1234567"));
+        assertEquals("01188671234567", SmsNumberUtils.filterDestAddr(mContext, mPhone.getSubId(),
+                "+886-7-1234567"));
     }
 
     @Test
     @SmallTest
     public void testIndiaPcCountryCodeAreaLocalNumberConversion() {
         // NP_NBPCD_CC_AREA_LOCAL tests
-        doReturn(INDIA_AIRTEL_MCC_MNC).when(mTelephonyManager).getNetworkOperator(anyInt());
-        assertEquals("0119172345678", SmsNumberUtils.filterDestAddr(mPhone, "+91-7-234-5678"));
+        doReturn(INDIA_AIRTEL_MCC_MNC).when(mTelephonyManager).getNetworkOperator();
+        assertEquals("0119172345678", SmsNumberUtils.filterDestAddr(mContext, mPhone.getSubId(),
+                "+91-7-234-5678"));
     }
 
     @Test
     @SmallTest
     public void testPcHomeIddCountryCodeAreaLocalNumberConversion() {
         // NP_NBPCD_HOMEIDD_CC_AREA_LOCAL tests
-        assertEquals("01188671234567", SmsNumberUtils.filterDestAddr(mPhone, "+011886-7-1234567"));
+        assertEquals("01188671234567", SmsNumberUtils.filterDestAddr(mContext, mPhone.getSubId(),
+                "+011886-7-1234567"));
     }
 
     @Test
     @SmallTest
     public void testHomeIddCountryCodeAreaLocalNumberConversion() {
         // NP_HOMEIDD_CC_AREA_LOCAL tests
-        assertEquals("01188671234567", SmsNumberUtils.filterDestAddr(mPhone, "011886-7-1234567"));
+        assertEquals("01188671234567", SmsNumberUtils.filterDestAddr(mContext, mPhone.getSubId(),
+                "011886-7-1234567"));
     }
 
     @Test
     @SmallTest
     public void testLocalIddCountryCodeAreaLocalNumberConversion() {
         // NP_LOCALIDD_CC_AREA_LOCAL tests
-        doReturn(TAIWAN_FET_MCC_MNC).when(mTelephonyManager).getNetworkOperator(anyInt());
-        assertEquals("01118581234567", SmsNumberUtils.filterDestAddr(mPhone, "002-1-858-1234567"));
+        doReturn(TAIWAN_FET_MCC_MNC).when(mTelephonyManager).getNetworkOperator();
+        assertEquals("01118581234567", SmsNumberUtils.filterDestAddr(mContext, mPhone.getSubId(),
+                "002-1-858-1234567"));
     }
 
     @Test
     @SmallTest
     public void testIndiaLocalIddCountryCodeAreaLocalNumberConversion() {
         // NP_LOCALIDD_CC_AREA_LOCAL tests
-        doReturn(INDIA_AIRTEL_MCC_MNC).when(mTelephonyManager).getNetworkOperator(anyInt());
-        assertEquals("01118581234567", SmsNumberUtils.filterDestAddr(mPhone, "010-1-858-1234567"));
+        doReturn(INDIA_AIRTEL_MCC_MNC).when(mTelephonyManager).getNetworkOperator();
+        assertEquals("01118581234567", SmsNumberUtils.filterDestAddr(mContext, mPhone.getSubId(),
+                "010-1-858-1234567"));
     }
 
     @Test
     @SmallTest
     public void testJapanLocalIddCountryCodeAreaLocalNumberConversion() {
         // NP_LOCALIDD_CC_AREA_LOCAL tests
-        doReturn(JAPAN_NTTDOCOMO_MCC_MNC).when(mTelephonyManager).getNetworkOperator(anyInt());
-        assertEquals("01118581234567", SmsNumberUtils.filterDestAddr(mPhone, "010-1-858-1234567"));
+        doReturn(JAPAN_NTTDOCOMO_MCC_MNC).when(mTelephonyManager).getNetworkOperator();
+        assertEquals("01118581234567", SmsNumberUtils.filterDestAddr(mContext, mPhone.getSubId(),
+                "010-1-858-1234567"));
     }
 
     @Test
     @SmallTest
     public void testCountryCodeAreaLocalNumberConversion() {
         // NP_CC_AREA_LOCAL tests
-        assertEquals("011886286281234", SmsNumberUtils.filterDestAddr(mPhone, "886-2-86281234"));
+        assertEquals("011886286281234", SmsNumberUtils.filterDestAddr(mContext, mPhone.getSubId(),
+                "886-2-86281234"));
     }
 
     @Test
     @SmallTest
     public void testNaLocalNumberConversion() {
         // NP_NANP_LOCAL
-        assertEquals("2345678", SmsNumberUtils.filterDestAddr(mPhone, "234-5678"));
+        assertEquals("2345678", SmsNumberUtils.filterDestAddr(mContext, mPhone.getSubId(),
+                "234-5678"));
     }
 
     @Test
     @SmallTest
     public void testNaAreaLocalNumberConversion() {
         // NP_NANP_AREA_LOCAL
-        assertEquals("8582345678", SmsNumberUtils.filterDestAddr(mPhone, "858-234-5678"));
+        assertEquals("8582345678", SmsNumberUtils.filterDestAddr(mContext, mPhone.getSubId(),
+                "858-234-5678"));
     }
 
     @Test
     @SmallTest
     public void testNaNddAreaLocalNumberConversion() {
         // NP_NANP_NDD_AREA_LOCAL
-        assertEquals("18582345678", SmsNumberUtils.filterDestAddr(mPhone, "1-858-234-5678"));
+        assertEquals("18582345678", SmsNumberUtils.filterDestAddr(mContext, mPhone.getSubId(),
+                "1-858-234-5678"));
     }
 
     @Test
     @SmallTest
     public void testNaLocalIddCcAreaLocalNumberConversion() {
         // NP_NANP_LOCALIDD_CC_AREA_LOCAL
-        assertEquals("+18582345678", SmsNumberUtils.filterDestAddr(mPhone, "011-1-858-234-5678"));
+        assertEquals("+18582345678", SmsNumberUtils.filterDestAddr(mContext, mPhone.getSubId(),
+                "011-1-858-234-5678"));
     }
 
     @Test
@@ -242,6 +254,6 @@
     public void testNaPcHomeIddCcAreaLocalNumberConversion() {
         // NP_NANP_NBPCD_HOMEIDD_CC_AREA_LOCAL
         assertEquals("01118582345678",
-                SmsNumberUtils.filterDestAddr(mPhone, "+011-1-858-234-5678"));
+                SmsNumberUtils.filterDestAddr(mContext, mPhone.getSubId(), "+011-1-858-234-5678"));
     }
 }
diff --git a/tests/telephonytests/src/com/android/internal/telephony/SmsPermissionsTest.java b/tests/telephonytests/src/com/android/internal/telephony/SmsPermissionsTest.java
index 9738f3b..c69c733 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/SmsPermissionsTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/SmsPermissionsTest.java
@@ -15,9 +15,16 @@
  */
 package com.android.internal.telephony;
 
+import static android.content.pm.PackageManager.PERMISSION_DENIED;
+import static android.content.pm.PackageManager.PERMISSION_GRANTED;
+
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.anyInt;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.mock;
 
 import android.Manifest;
 import android.app.AppOpsManager;
@@ -36,8 +43,9 @@
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
 
-public class SmsPermissionsTest {
+public class SmsPermissionsTest extends TelephonyTest {
     private static final String PACKAGE = "com.example.package";
+    private static final String ATTRIBUTION_TAG = null;
     private static final String MESSAGE = "msg";
 
     private HandlerThread mHandlerThread;
@@ -52,9 +60,12 @@
     private SmsPermissions mSmsPermissionsTest;
 
     private boolean mCallerHasCarrierPrivileges;
+    private boolean mCallerIsDefaultSmsPackage;
 
     @Before
     public void setUp() throws Exception {
+        super.setUp("SmsPermissionsTest");
+
         MockitoAnnotations.initMocks(this);
         mHandlerThread = new HandlerThread("IccSmsInterfaceManagerTest");
         mHandlerThread.start();
@@ -68,6 +79,11 @@
                         throw new SecurityException(message);
                     }
                 }
+
+                @Override
+                public boolean isCallerDefaultSmsPackage(String packageName) {
+                    return mCallerIsDefaultSmsPackage;
+                }
             };
             initialized.countDown();
         });
@@ -80,12 +96,14 @@
     @After
     public void tearDown() throws Exception {
         mHandlerThread.quit();
+        mHandlerThread.join();
+        super.tearDown();
     }
 
     @Test
     public void testCheckCallingSendTextPermissions_persist_grant() {
         assertTrue(mSmsPermissionsTest.checkCallingCanSendText(
-                true /* persistMessageForNonDefaultSmsApp */, PACKAGE, MESSAGE));
+                true /* persistMessageForNonDefaultSmsApp */, PACKAGE, ATTRIBUTION_TAG, MESSAGE));
     }
 
     @Test
@@ -94,7 +112,8 @@
                 .enforceCallingPermission(Manifest.permission.SEND_SMS, MESSAGE);
         try {
             mSmsPermissionsTest.checkCallingCanSendText(
-                    true /* persistMessageForNonDefaultSmsApp */, PACKAGE, MESSAGE);
+                    true /* persistMessageForNonDefaultSmsApp */, PACKAGE, ATTRIBUTION_TAG,
+                    MESSAGE);
             fail();
         } catch (SecurityException e) {
             // expected
@@ -103,11 +122,12 @@
 
     @Test
     public void testCheckCallingSendTextPermissions_persist_noAppOps() {
-        Mockito.when(mMockAppOps.noteOp(
-                AppOpsManager.OP_SEND_SMS, Binder.getCallingUid(), PACKAGE))
+        Mockito.when(
+                mMockAppOps.noteOp(AppOpsManager.OPSTR_SEND_SMS, Binder.getCallingUid(), PACKAGE,
+                        ATTRIBUTION_TAG, null))
                 .thenReturn(AppOpsManager.MODE_ERRORED);
         assertFalse(mSmsPermissionsTest.checkCallingCanSendText(
-                true /* persistMessageForNonDefaultSmsApp */, PACKAGE, MESSAGE));
+                true /* persistMessageForNonDefaultSmsApp */, PACKAGE, ATTRIBUTION_TAG, MESSAGE));
     }
 
     @Test
@@ -118,18 +138,19 @@
                 .enforceCallingPermission(Manifest.permission.MODIFY_PHONE_STATE, MESSAGE);
         Mockito.doThrow(new SecurityException(MESSAGE)).when(mMockContext)
                 .enforceCallingPermission(Manifest.permission.SEND_SMS, MESSAGE);
-        Mockito.when(mMockAppOps.noteOp(
-                AppOpsManager.OP_SEND_SMS, Binder.getCallingUid(), PACKAGE))
+        Mockito.when(
+                mMockAppOps.noteOp(AppOpsManager.OPSTR_SEND_SMS, Binder.getCallingUid(), PACKAGE,
+                        ATTRIBUTION_TAG, null))
                 .thenReturn(AppOpsManager.MODE_ERRORED);
 
         assertTrue(mSmsPermissionsTest.checkCallingCanSendText(
-                false /* persistMessageForNonDefaultSmsApp */, PACKAGE, MESSAGE));
+                false /* persistMessageForNonDefaultSmsApp */, PACKAGE, ATTRIBUTION_TAG, MESSAGE));
     }
 
     @Test
     public void testCheckCallingSendTextPermissions_noPersist_grantViaModifyAndSend() {
         assertTrue(mSmsPermissionsTest.checkCallingCanSendText(
-                false /* persistMessageForNonDefaultSmsApp */, PACKAGE, MESSAGE));
+                false /* persistMessageForNonDefaultSmsApp */, PACKAGE, ATTRIBUTION_TAG, MESSAGE));
     }
 
     @Test
@@ -138,7 +159,8 @@
                 .enforceCallingPermission(Manifest.permission.MODIFY_PHONE_STATE, MESSAGE);
         try {
             mSmsPermissionsTest.checkCallingCanSendText(
-                    false /* persistMessageForNonDefaultSmsApp */, PACKAGE, MESSAGE);
+                    false /* persistMessageForNonDefaultSmsApp */, PACKAGE, ATTRIBUTION_TAG,
+                    MESSAGE);
             fail();
         } catch (SecurityException e) {
             // expected
@@ -151,7 +173,8 @@
                 .enforceCallingPermission(Manifest.permission.SEND_SMS, MESSAGE);
         try {
             mSmsPermissionsTest.checkCallingCanSendText(
-                    false /* persistMessageForNonDefaultSmsApp */, PACKAGE, MESSAGE);
+                    false /* persistMessageForNonDefaultSmsApp */, PACKAGE, ATTRIBUTION_TAG,
+                    MESSAGE);
             fail();
         } catch (SecurityException e) {
             // expected
@@ -160,10 +183,92 @@
 
     @Test
     public void testCheckCallingSendTextPermissions_noPersist_noAppOps() {
-        Mockito.when(mMockAppOps.noteOp(
-                AppOpsManager.OP_SEND_SMS, Binder.getCallingUid(), PACKAGE))
+        Mockito.when(
+                mMockAppOps.noteOp(AppOpsManager.OPSTR_SEND_SMS, Binder.getCallingUid(), PACKAGE,
+                        ATTRIBUTION_TAG, null))
                 .thenReturn(AppOpsManager.MODE_ERRORED);
         assertFalse(mSmsPermissionsTest.checkCallingCanSendText(
-                false /* persistMessageForNonDefaultSmsApp */, PACKAGE, MESSAGE));
+                false /* persistMessageForNonDefaultSmsApp */, PACKAGE, ATTRIBUTION_TAG, MESSAGE));
+    }
+
+    @Test
+    public void testCheckCallingOrSelfCanGetSmscAddressPermissions_defaultSmsApp() {
+        mCallerIsDefaultSmsPackage = true;
+        // Other permissions shouldn't matter.
+        Mockito.when(mMockContext.checkCallingOrSelfPermission(
+                    Manifest.permission.READ_PRIVILEGED_PHONE_STATE))
+                .thenReturn(PERMISSION_DENIED);
+        assertTrue(mSmsPermissionsTest.checkCallingOrSelfCanGetSmscAddress(PACKAGE, MESSAGE));
+    }
+
+    @Test
+    public void testCheckCallingOrSelfCanGetSmscAddressPermissions_hasReadPrivilegedPhoneState() {
+        Mockito.when(mMockContext.checkCallingOrSelfPermission(
+                    Manifest.permission.READ_PRIVILEGED_PHONE_STATE))
+                .thenReturn(PERMISSION_GRANTED);
+        assertTrue(mSmsPermissionsTest.checkCallingOrSelfCanGetSmscAddress(PACKAGE, MESSAGE));
+    }
+
+    @Test
+    public void testCheckCallingOrSelfCanGetSmscAddressPermissions_noPermissions() {
+        Mockito.when(mMockContext.getSystemService(Context.TELEPHONY_SERVICE)).thenReturn(
+                mTelephonyManager);
+        Mockito.when(mMockContext.checkCallingOrSelfPermission(
+                    Manifest.permission.READ_PRIVILEGED_PHONE_STATE))
+                .thenReturn(PERMISSION_DENIED);
+        try {
+            mSmsPermissionsTest.checkCallingOrSelfCanGetSmscAddress(PACKAGE, MESSAGE);
+            fail();
+        } catch (SecurityException e) {
+            // expected
+        }
+    }
+    @Test
+    public void testCheckCallingOrSelfCanSetSmscAddressPermissions_defaultSmsApp() {
+        mCallerIsDefaultSmsPackage = true;
+        // Other permissions shouldn't matter.
+        Mockito.when(mMockContext.checkCallingOrSelfPermission(
+                    Manifest.permission.MODIFY_PHONE_STATE))
+                .thenReturn(PERMISSION_DENIED);
+        assertTrue(mSmsPermissionsTest.checkCallingOrSelfCanSetSmscAddress(PACKAGE, MESSAGE));
+    }
+
+    @Test
+    public void testCheckCallingOrSelfCanSetSmscAddressPermissions_hasModifyPhoneState() {
+        Mockito.when(mMockContext.checkCallingOrSelfPermission(
+                    Manifest.permission.MODIFY_PHONE_STATE))
+                .thenReturn(PERMISSION_GRANTED);
+        assertTrue(mSmsPermissionsTest.checkCallingOrSelfCanSetSmscAddress(PACKAGE, MESSAGE));
+    }
+
+    @Test
+    public void testCheckCallingOrSelfCanSetSmscAddressPermissions_noPermissions() {
+        Mockito.when(mMockContext.getSystemService(Context.TELEPHONY_SERVICE)).thenReturn(
+                mTelephonyManager);
+        Mockito.when(mMockContext.checkCallingOrSelfPermission(
+                Manifest.permission.MODIFY_PHONE_STATE)).thenReturn(PERMISSION_DENIED);
+        try {
+            assertFalse(mSmsPermissionsTest.checkCallingOrSelfCanSetSmscAddress(PACKAGE, MESSAGE));
+            fail();
+        } catch (SecurityException e) {
+            // expected
+        }
+    }
+
+    @Test
+    public void testPackageNameMatchesCallingUid() {
+        AppOpsManager mockAppOpsManager = mock(AppOpsManager.class);
+        Mockito.when(mMockContext.getSystemService(Context.APP_OPS_SERVICE)).thenReturn(
+                mockAppOpsManager);
+
+        // test matching case
+        assertTrue(new SmsPermissions(mMockPhone, mMockContext, mMockAppOps)
+                .packageNameMatchesCallingUid(PACKAGE));
+
+        // test mis-match case
+        SecurityException e = new SecurityException("Test exception");
+        doThrow(e).when(mockAppOpsManager).checkPackage(anyInt(), anyString());
+        assertFalse(new SmsPermissions(mMockPhone, mMockContext, mMockAppOps)
+                .packageNameMatchesCallingUid(PACKAGE));
     }
 }
diff --git a/tests/telephonytests/src/com/android/internal/telephony/SmsStorageMonitorTest.java b/tests/telephonytests/src/com/android/internal/telephony/SmsStorageMonitorTest.java
index cbc56ae..4b22f5f 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/SmsStorageMonitorTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/SmsStorageMonitorTest.java
@@ -16,58 +16,38 @@
 
 package com.android.internal.telephony;
 
+import static org.junit.Assert.*;
+import static org.mockito.Mockito.*;
+
 import android.content.Intent;
-import android.os.HandlerThread;
 import android.os.Message;
 import android.provider.Telephony;
 import android.test.suitebuilder.annotation.SmallTest;
-import android.util.Log;
-
-import com.android.internal.telephony.test.SimulatedCommands;
-import com.android.internal.telephony.test.SimulatedCommandsVerifier;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
 
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
+import org.junit.runner.RunWith;
 import org.mockito.ArgumentCaptor;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
 
-import java.lang.reflect.Field;
-
-import static org.junit.Assert.*;
-import static org.mockito.Mockito.*;
-
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper
 public class SmsStorageMonitorTest extends TelephonyTest {
 
     private SmsStorageMonitor mSmsStorageMonitor;
-    private SmsStorageMonitorTestHandler mSmsStorageMonitorTestHandler;
-
-    private class SmsStorageMonitorTestHandler extends HandlerThread {
-
-        private SmsStorageMonitorTestHandler(String name) {
-            super(name);
-        }
-
-        @Override
-        public void onLooperPrepared() {
-            mSmsStorageMonitor = new SmsStorageMonitor(mPhone);
-            setReady(true);
-        }
-    }
 
     @Before
     public void setUp() throws Exception {
         super.setUp(getClass().getSimpleName());
-        mSmsStorageMonitorTestHandler = new SmsStorageMonitorTestHandler(TAG);
-        mSmsStorageMonitorTestHandler.start();
-        waitUntilReady();
+        mSmsStorageMonitor = new SmsStorageMonitor(mPhone);
+        processAllMessages();
     }
 
     @After
     public void tearDown() throws Exception {
         mSmsStorageMonitor = null;
-        mSmsStorageMonitorTestHandler.quit();
         super.tearDown();
     }
 
@@ -75,7 +55,7 @@
     public void testEventIccFull() {
         // Notify icc sms full
         mSimulatedCommands.notifyIccSmsFull();
-        TelephonyTestUtils.waitForMs(50);
+        processAllMessages();
 
         // SIM_FULL_ACTION intent should be broadcast
         ArgumentCaptor<Intent> intentArgumentCaptor = ArgumentCaptor.forClass(Intent.class);
@@ -88,7 +68,7 @@
     public void testSmsMemoryStatus() {
         // Notify radio on
         mSimulatedCommands.notifyRadioOn();
-        TelephonyTestUtils.waitForMs(50);
+        processAllMessages();
 
         verify(mSimulatedCommandsVerifier, never()).reportSmsMemoryStatus(anyBoolean(),
                 any(Message.class));
@@ -96,26 +76,26 @@
         // Send DEVICE_STORAGE_FULL
         mContextFixture.getTestDouble().sendBroadcast(
                 new Intent(Intent.ACTION_DEVICE_STORAGE_FULL));
-        TelephonyTestUtils.waitForMs(50);
+        processAllMessages();
 
         verify(mSimulatedCommandsVerifier).reportSmsMemoryStatus(eq(false), any(Message.class));
         assertFalse(mSmsStorageMonitor.isStorageAvailable());
 
         mSimulatedCommands.notifyRadioOn();
-        TelephonyTestUtils.waitForMs(50);
+        processAllMessages();
 
         verify(mSimulatedCommandsVerifier).reportSmsMemoryStatus(eq(false), any(Message.class));
 
         // Send DEVICE_STORAGE_NOT_FULL
         mContextFixture.getTestDouble().sendBroadcast(
                 new Intent(Intent.ACTION_DEVICE_STORAGE_NOT_FULL));
-        TelephonyTestUtils.waitForMs(50);
+        processAllMessages();
 
         verify(mSimulatedCommandsVerifier).reportSmsMemoryStatus(eq(true), any(Message.class));
         assertTrue(mSmsStorageMonitor.isStorageAvailable());
 
         mSimulatedCommands.notifyRadioOn();
-        TelephonyTestUtils.waitForMs(50);
+        processAllMessages();
 
         verify(mSimulatedCommandsVerifier).reportSmsMemoryStatus(eq(true), any(Message.class));
     }
diff --git a/tests/telephonytests/src/com/android/internal/telephony/SubscriptionControllerTest.java b/tests/telephonytests/src/com/android/internal/telephony/SubscriptionControllerTest.java
index 26adb81..57c9eb2 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/SubscriptionControllerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/SubscriptionControllerTest.java
@@ -15,6 +15,10 @@
  */
 package com.android.internal.telephony;
 
+import static android.telephony.TelephonyManager.SET_OPPORTUNISTIC_SUB_REMOTE_SERVICE_EXCEPTION;
+
+import static com.android.internal.telephony.uicc.IccCardStatus.CardState.CARDSTATE_PRESENT;
+
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotEquals;
@@ -22,19 +26,25 @@
 import static org.junit.Assert.assertNotSame;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyLong;
 import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.nullable;
 import static org.mockito.Mockito.atLeast;
-import static org.mockito.Mockito.clearInvocations;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.eq;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 
 import android.Manifest;
+import android.app.AppOpsManager;
 import android.content.ContentResolver;
 import android.content.ContentValues;
 import android.content.Intent;
+import android.os.Build;
 import android.os.Bundle;
+import android.os.Handler;
 import android.os.ParcelUuid;
 import android.os.UserHandle;
 import android.provider.Settings;
@@ -46,6 +56,7 @@
 
 import androidx.test.filters.FlakyTest;
 
+import com.android.internal.telephony.uicc.IccCardStatus;
 import com.android.internal.telephony.uicc.UiccController;
 import com.android.internal.telephony.uicc.UiccSlot;
 
@@ -60,11 +71,13 @@
 import java.util.Comparator;
 import java.util.List;
 import java.util.Map;
+import java.util.UUID;
 
 public class SubscriptionControllerTest extends TelephonyTest {
     private static final int SINGLE_SIM = 1;
     private static final int DUAL_SIM = 2;
     private String mCallingPackage;
+    private String mCallingFeature;
     private SubscriptionController mSubscriptionControllerUT;
     private MockContentResolver mMockContentResolver;
     private FakeTelephonyProvider mFakeTelephonyProvider;
@@ -74,11 +87,17 @@
     private ITelephonyRegistry.Stub mTelephonyRegisteryMock;
     @Mock
     private MultiSimSettingController mMultiSimSettingControllerMock;
+    @Mock
+    private ISetOpportunisticDataCallback mSetOpptDataCallback;
+    @Mock
+    private Handler mHandler;
 
     private static final String MAC_ADDRESS_PREFIX = "mac_";
     private static final String DISPLAY_NAME_PREFIX = "my_phone_";
 
     private static final String UNAVAILABLE_ICCID = "";
+    private static final String UNAVAILABLE_NUMBER = "";
+    private static final String DISPLAY_NUMBER = "123456";
 
     @Before
     public void setUp() throws Exception {
@@ -94,14 +113,14 @@
         replaceInstance(MultiSimSettingController.class, "sInstance", null,
                 mMultiSimSettingControllerMock);
 
-        SubscriptionController.init(mContext, null);
-        mSubscriptionControllerUT = SubscriptionController.getInstance();
+        mSubscriptionControllerUT = SubscriptionController.init(mContext);
         mCallingPackage = mContext.getOpPackageName();
+        mCallingFeature = mContext.getAttributionTag();
 
         doReturn(1).when(mProxyController).getMaxRafSupported();
         mContextFixture.putIntArrayResource(com.android.internal.R.array.sim_colors, new int[]{5});
 
-        mSubscriptionControllerUT.getInstance().updatePhonesAvailability(new Phone[]{mPhone});
+        setupMocksForTelephonyPermissions(Build.VERSION_CODES.R);
     }
 
     @After
@@ -133,18 +152,20 @@
     @Test @SmallTest
     public void testInsertSim() {
         //verify there is no sim inserted in the SubscriptionManager
-        assertEquals(0, mSubscriptionControllerUT.getAllSubInfoCount(mCallingPackage));
+        assertEquals(0, mSubscriptionControllerUT.getAllSubInfoCount(mCallingPackage,
+                mCallingFeature));
 
         int slotID = 0;
         //insert one Subscription Info
         mSubscriptionControllerUT.addSubInfoRecord("test", slotID);
 
         //verify there is one sim
-        assertEquals(1, mSubscriptionControllerUT.getAllSubInfoCount(mCallingPackage));
+        assertEquals(1, mSubscriptionControllerUT.getAllSubInfoCount(mCallingPackage,
+                mCallingFeature));
 
         //sanity for slot id and sub id
         List<SubscriptionInfo> mSubList = mSubscriptionControllerUT
-                .getActiveSubscriptionInfoList(mCallingPackage);
+                .getActiveSubscriptionInfoList(mCallingPackage, mCallingFeature);
         assertTrue(mSubList != null && mSubList.size() > 0);
         for (int i = 0; i < mSubList.size(); i++) {
             assertTrue(SubscriptionManager.isValidSubscriptionId(
@@ -169,7 +190,7 @@
 
         /* Getting, there is no direct getter function for each fields of property */
         SubscriptionInfo subInfo = mSubscriptionControllerUT
-                .getActiveSubscriptionInfo(subID, mCallingPackage);
+                .getActiveSubscriptionInfo(subID, mCallingPackage, mCallingFeature);
 
         /* Setting */
         mSubscriptionControllerUT.setDisplayNameUsingSrc(disName, subID,
@@ -180,7 +201,7 @@
         mSubscriptionControllerUT.setOpportunistic(isOpportunistic, subID, mCallingPackage);
 
         subInfo = mSubscriptionControllerUT
-            .getActiveSubscriptionInfo(subID, mCallingPackage);
+            .getActiveSubscriptionInfo(subID, mCallingPackage, mCallingFeature);
 
         assertNotNull(subInfo);
         assertEquals(dataRoaming, subInfo.getDataRoaming());
@@ -210,7 +231,7 @@
         int nameSource = SubscriptionManager.NAME_SOURCE_SIM_SPN;
         mSubscriptionControllerUT.setDisplayNameUsingSrc(disName, subID, nameSource);
         SubscriptionInfo subInfo = mSubscriptionControllerUT
-                .getActiveSubscriptionInfo(subID, mCallingPackage);
+                .getActiveSubscriptionInfo(subID, mCallingPackage, mCallingFeature);
         assertNotNull(subInfo);
         assertEquals(disName, subInfo.getDisplayName());
         assertEquals(nameSource, subInfo.getNameSource());
@@ -257,7 +278,7 @@
         mSubscriptionControllerUT.setMccMnc(mCcMncVERIZON, 1);
 
         SubscriptionInfo subInfo = mSubscriptionControllerUT
-                .getActiveSubscriptionInfo(1, mCallingPackage);
+                .getActiveSubscriptionInfo(1, mCallingPackage, mCallingFeature);
         assertNotNull(subInfo);
         assertEquals(Integer.parseInt(mCcMncVERIZON.substring(0, 3)), subInfo.getMcc());
         assertEquals(Integer.parseInt(mCcMncVERIZON.substring(3)), subInfo.getMnc());
@@ -276,7 +297,7 @@
         mSubscriptionControllerUT.setCarrierId(carrierId, 1);
 
         SubscriptionInfo subInfo = mSubscriptionControllerUT
-                .getActiveSubscriptionInfo(1, mCallingPackage);
+                .getActiveSubscriptionInfo(1, mCallingPackage, mCallingFeature);
         assertNotNull(subInfo);
         assertEquals(carrierId, subInfo.getCarrierId());
 
@@ -294,7 +315,6 @@
 
         mSubscriptionControllerUT.setDefaultDataSubId(1);
 
-        verify(mPhone, times(1)).updateDataConnectionTracker();
         ArgumentCaptor<Intent> captorIntent = ArgumentCaptor.forClass(Intent.class);
         verify(mContext, times(1)).sendStickyBroadcastAsUser(
                 captorIntent.capture(), eq(UserHandle.ALL));
@@ -363,27 +383,32 @@
         assertEquals("1", mSubscriptionControllerUT.getSubscriptionProperty(
                 subID,
                 SubscriptionManager.ENHANCED_4G_MODE_ENABLED,
-                mCallingPackage));
+                mCallingPackage,
+                mCallingFeature));
 
         assertEquals("0", mSubscriptionControllerUT.getSubscriptionProperty(
                 subID,
                 SubscriptionManager.VT_IMS_ENABLED,
-                mCallingPackage));
+                mCallingPackage,
+                mCallingFeature));
 
         assertEquals("1", mSubscriptionControllerUT.getSubscriptionProperty(
                 subID,
                 SubscriptionManager.WFC_IMS_ENABLED,
-                mCallingPackage));
+                mCallingPackage,
+                mCallingFeature));
 
         assertEquals("2", mSubscriptionControllerUT.getSubscriptionProperty(
                 subID,
                 SubscriptionManager.WFC_IMS_MODE,
-                mCallingPackage));
+                mCallingPackage,
+                mCallingFeature));
 
         assertEquals("3", mSubscriptionControllerUT.getSubscriptionProperty(
                 subID,
                 SubscriptionManager.WFC_IMS_ROAMING_MODE,
-                mCallingPackage));
+                mCallingPackage,
+                mCallingFeature));
     }
 
     @Test
@@ -443,7 +468,7 @@
     @SmallTest
     public void testOpptSubInfoListChanged() throws Exception {
         registerMockTelephonyRegistry();
-        verify(mTelephonyRegisteryMock, times(0))
+        verify(mTelephonyRegistryManager, times(0))
                 .notifyOpportunisticSubscriptionInfoChanged();
 
         testInsertSim();
@@ -452,31 +477,31 @@
         // Neither sub1 or sub2 are opportunistic. So getOpportunisticSubscriptions
         // should return empty list and no callback triggered.
         List<SubscriptionInfo> opptSubList = mSubscriptionControllerUT
-                .getOpportunisticSubscriptions(mCallingPackage);
+                .getOpportunisticSubscriptions(mCallingPackage, mCallingFeature);
 
         assertTrue(opptSubList.isEmpty());
-        verify(mTelephonyRegisteryMock, times(0))
+        verify(mTelephonyRegistryManager, times(0))
                 .notifyOpportunisticSubscriptionInfoChanged();
 
         // Setting sub2 as opportunistic should trigger callback.
         mSubscriptionControllerUT.setOpportunistic(true, 2, mCallingPackage);
 
-        verify(mTelephonyRegisteryMock, times(1))
+        verify(mTelephonyRegistryManager, times(1))
                 .notifyOpportunisticSubscriptionInfoChanged();
         opptSubList = mSubscriptionControllerUT
-                .getOpportunisticSubscriptions(mCallingPackage);
+                .getOpportunisticSubscriptions(mCallingPackage, mCallingFeature);
         assertEquals(1, opptSubList.size());
         assertEquals("test2", opptSubList.get(0).getIccId());
 
         // Changing non-opportunistic sub1 shouldn't trigger callback.
         mSubscriptionControllerUT.setDisplayNameUsingSrc("DisplayName", 1,
                 SubscriptionManager.NAME_SOURCE_SIM_SPN);
-        verify(mTelephonyRegisteryMock, times(1))
+        verify(mTelephonyRegistryManager, times(1))
                 .notifyOpportunisticSubscriptionInfoChanged();
 
         mSubscriptionControllerUT.setDisplayNameUsingSrc("DisplayName", 2,
                 SubscriptionManager.NAME_SOURCE_SIM_SPN);
-        verify(mTelephonyRegisteryMock, times(2))
+        verify(mTelephonyRegistryManager, times(2))
                 .notifyOpportunisticSubscriptionInfoChanged();
     }
 
@@ -485,7 +510,8 @@
         makeThisDeviceMultiSimCapable();
 
         // verify there are no sim's in the system.
-        assertEquals(0, mSubscriptionControllerUT.getAllSubInfoCount(mCallingPackage));
+        assertEquals(0, mSubscriptionControllerUT.getAllSubInfoCount(mCallingPackage,
+                mCallingFeature));
 
         addAndVerifyRemoteSimAddition(1, 0);
     }
@@ -493,14 +519,15 @@
     private void addAndVerifyRemoteSimAddition(int num, int numOfCurrentSubs) {
         // Verify the number of current subs in the system
         assertEquals(numOfCurrentSubs,
-                mSubscriptionControllerUT.getAllSubInfoCount(mCallingPackage));
+                mSubscriptionControllerUT.getAllSubInfoCount(mCallingPackage, mCallingFeature));
 
         // if there are current subs in the system, get that info
         List<SubscriptionInfo> mSubList;
         ArrayList<String> macAddresses = new ArrayList<>();
         ArrayList<String> displayNames = new ArrayList<>();
         if (numOfCurrentSubs > 0) {
-            mSubList = mSubscriptionControllerUT.getActiveSubscriptionInfoList(mCallingPackage);
+            mSubList = mSubscriptionControllerUT.getActiveSubscriptionInfoList(mCallingPackage,
+                    mCallingFeature);
             assertNotNull(mSubList);
             assertEquals(numOfCurrentSubs, mSubList.size());
             for (SubscriptionInfo info : mSubList) {
@@ -540,7 +567,8 @@
             assertEquals(expectedNumOfSubs, subIdsList.size());
 
             // validate slot index, sub id etc
-            mSubList = mSubscriptionControllerUT.getActiveSubscriptionInfoList(mCallingPackage);
+            mSubList = mSubscriptionControllerUT.getActiveSubscriptionInfoList(mCallingPackage,
+                    mCallingFeature);
             assertNotNull(mSubList);
             assertEquals(expectedNumOfSubs, mSubList.size());
 
@@ -570,7 +598,8 @@
         makeThisDeviceMultiSimCapable();
 
         // verify that there are no subscription info records
-        assertEquals(0, mSubscriptionControllerUT.getAllSubInfoCount(mCallingPackage));
+        assertEquals(0, mSubscriptionControllerUT.getAllSubInfoCount(mCallingPackage,
+                mCallingFeature));
         Map<Integer, ArrayList<Integer>> slotIndexToSubsMap =
                 mSubscriptionControllerUT.getSlotIndexToSubIdsMap();
         assertNotNull(slotIndexToSubsMap);
@@ -712,8 +741,9 @@
                 subIdList, mContext.getOpPackageName());
         assertNotEquals(null, groupId);
 
-        List<SubscriptionInfo> subInfoList = mSubscriptionControllerUT
-                .getActiveSubscriptionInfoList(mContext.getOpPackageName());
+        List<SubscriptionInfo> subInfoList =
+                mSubscriptionControllerUT.getActiveSubscriptionInfoList(mContext.getOpPackageName(),
+                        mContext.getAttributionTag());
 
         // Put sub3 into slot 1 to make sub2 inactive.
         mContextFixture.addCallingOrSelfPermission(
@@ -734,6 +764,57 @@
 
     @Test
     @SmallTest
+    public void testAddSubscriptionIntoGroupWithCarrierPrivilegePermission() throws Exception {
+        testInsertSim();
+        // Adding a second profile and mark as embedded.
+        // TODO b/123300875 slot index 1 is not expected to be valid
+        mSubscriptionControllerUT.addSubInfoRecord("test2", 1);
+        ContentValues values = new ContentValues();
+        values.put(SubscriptionManager.IS_EMBEDDED, 1);
+        mFakeTelephonyProvider.update(SubscriptionManager.CONTENT_URI, values,
+                SubscriptionManager.UNIQUE_KEY_SUBSCRIPTION_ID + "=" + 2, null);
+        mSubscriptionControllerUT.refreshCachedActiveSubscriptionInfoList();
+
+        mContextFixture.removeCallingOrSelfPermission(ContextFixture.PERMISSION_ENABLE_ALL);
+        mContextFixture.addCallingOrSelfPermission(Manifest.permission.READ_PHONE_STATE);
+
+        // Create group for sub 1.
+        int[] subIdList = new int[] {1};
+        doReturn(true).when(mTelephonyManager).hasCarrierPrivileges(1);
+        ParcelUuid groupId = mSubscriptionControllerUT.createSubscriptionGroup(
+                subIdList, "packageName1");
+
+        // Try to add sub 2 into group of sub 1.
+        // Should fail as it doesn't have carrier privilege on sub 2.
+        try {
+            mSubscriptionControllerUT.addSubscriptionsIntoGroup(
+                    new int[] {2}, groupId, "packageName1");
+            fail("addSubscriptionsIntoGroup should fail with no permission on sub 2.");
+        } catch (SecurityException e) {
+            // Expected result.
+        }
+
+        doReturn(false).when(mTelephonyManager).hasCarrierPrivileges(1);
+        doReturn(true).when(mTelephonyManager).hasCarrierPrivileges(2);
+        // Try to add sub 2 into group of sub 1.
+        // Should fail as it doesn't have carrier privilege on sub 1.
+        try {
+            mSubscriptionControllerUT.addSubscriptionsIntoGroup(
+                    new int[] {2}, groupId, "packageName2");
+            fail("addSubscriptionsIntoGroup should fail with no permission on the group (sub 1).");
+        } catch (SecurityException e) {
+            // Expected result.
+        }
+
+        doReturn(true).when(mTelephonyManager).hasCarrierPrivileges(1);
+        mSubscriptionControllerUT.addSubscriptionsIntoGroup(new int[] {2}, groupId, "packageName2");
+        List<SubscriptionInfo> infoList = mSubscriptionControllerUT
+                .getSubscriptionsInGroup(groupId, "packageName2", "feature2");
+        assertEquals(2, infoList.size());
+    }
+
+    @Test
+    @SmallTest
     public void testUpdateSubscriptionGroupWithCarrierPrivilegePermission() throws Exception {
         testInsertSim();
         // Adding a second profile and mark as embedded.
@@ -760,7 +841,7 @@
         mSubscriptionControllerUT.addSubscriptionsIntoGroup(
                 new int[] {2}, groupId, "packageName1");
         List<SubscriptionInfo> infoList = mSubscriptionControllerUT.getSubscriptionsInGroup(
-                groupId, "packageName1");
+                groupId, "packageName1", "feature1");
         assertEquals(2, infoList.size());
         assertEquals(1, infoList.get(0).getSubscriptionId());
         assertEquals(2, infoList.get(1).getSubscriptionId());
@@ -768,7 +849,7 @@
         mSubscriptionControllerUT.removeSubscriptionsFromGroup(
                 new int[] {2}, groupId, "packageName1");
         infoList = mSubscriptionControllerUT.getSubscriptionsInGroup(
-                groupId, "packageName1");
+                groupId, "packageName1", "feature1");
         assertEquals(1, infoList.size());
         assertEquals(1, infoList.get(0).getSubscriptionId());
 
@@ -788,7 +869,7 @@
         mSubscriptionControllerUT.addSubscriptionsIntoGroup(
                 new int[] {2}, groupId, "packageName1");
         infoList = mSubscriptionControllerUT.getSubscriptionsInGroup(
-                groupId, "packageName1");
+                groupId, "packageName1", "feature1");
         assertEquals(2, infoList.size());
         assertEquals(1, infoList.get(0).getSubscriptionId());
         assertEquals(2, infoList.get(1).getSubscriptionId());
@@ -796,7 +877,7 @@
         mSubscriptionControllerUT.removeSubscriptionsFromGroup(
                 new int[] {2}, groupId, "packageName1");
         infoList = mSubscriptionControllerUT.getSubscriptionsInGroup(
-                groupId, "packageName1");
+                groupId, "packageName1", "feature1");
         assertEquals(1, infoList.size());
         assertEquals(1, infoList.get(0).getSubscriptionId());
     }
@@ -818,7 +899,7 @@
         mSubscriptionControllerUT.refreshCachedActiveSubscriptionInfoList();
         mSubscriptionControllerUT.notifySubscriptionInfoChanged();
 
-        verify(mTelephonyRegisteryMock, times(1))
+        verify(mTelephonyRegistryManager, times(1))
                 .notifyOpportunisticSubscriptionInfoChanged();
 
         // Set sub 1 and 2 into same group.
@@ -828,10 +909,10 @@
         assertNotEquals(null, groupId);
 
         mSubscriptionControllerUT.notifySubscriptionInfoChanged();
-        verify(mTelephonyRegisteryMock, times(2))
+        verify(mTelephonyRegistryManager, times(2))
                 .notifyOpportunisticSubscriptionInfoChanged();
         List<SubscriptionInfo> opptSubList = mSubscriptionControllerUT
-                .getOpportunisticSubscriptions(mCallingPackage);
+                .getOpportunisticSubscriptions(mCallingPackage, mCallingFeature);
         assertEquals(1, opptSubList.size());
         assertEquals(2, opptSubList.get(0).getSubscriptionId());
         assertEquals(false, opptSubList.get(0).isGroupDisabled());
@@ -844,9 +925,10 @@
         mSubscriptionControllerUT.refreshCachedActiveSubscriptionInfoList();
         mSubscriptionControllerUT.notifySubscriptionInfoChanged();
 
-        verify(mTelephonyRegisteryMock, times(3))
+        verify(mTelephonyRegistryManager, times(3))
                 .notifyOpportunisticSubscriptionInfoChanged();
-        opptSubList = mSubscriptionControllerUT.getOpportunisticSubscriptions(mCallingPackage);
+        opptSubList = mSubscriptionControllerUT.getOpportunisticSubscriptions(mCallingPackage,
+                mCallingFeature);
         assertEquals(1, opptSubList.size());
         assertEquals(2, opptSubList.get(0).getSubscriptionId());
         assertEquals(true, opptSubList.get(0).isGroupDisabled());
@@ -867,7 +949,8 @@
         assertTrue(mSubscriptionControllerUT.isActiveSubId(1));
         assertTrue(mSubscriptionControllerUT.isActiveSubId(2));
         assertTrue(TelephonyPermissions.checkCallingOrSelfReadPhoneState(mContext, 1,
-                mContext.getOpPackageName(), "getSubscriptionsInGroup"));
+                mContext.getOpPackageName(), mContext.getAttributionTag(),
+                "getSubscriptionsInGroup"));
 
         int[] subIdList = new int[] {1};
         ParcelUuid groupUuid = mSubscriptionControllerUT.createSubscriptionGroup(
@@ -875,8 +958,8 @@
         assertNotEquals(null, groupUuid);
 
         // Sub 1 and sub 2 should be in same group.
-        List<SubscriptionInfo> infoList = mSubscriptionControllerUT
-                .getSubscriptionsInGroup(groupUuid, mContext.getOpPackageName());
+        List<SubscriptionInfo> infoList = mSubscriptionControllerUT.getSubscriptionsInGroup(
+                groupUuid, mContext.getOpPackageName(), mContext.getAttributionTag());
         assertNotEquals(null, infoList);
         assertEquals(1, infoList.size());
         assertEquals(1, infoList.get(0).getSubscriptionId());
@@ -885,8 +968,8 @@
 
         mSubscriptionControllerUT.addSubscriptionsIntoGroup(
                 subIdList, groupUuid, mContext.getOpPackageName());
-        infoList = mSubscriptionControllerUT
-                .getSubscriptionsInGroup(groupUuid, mContext.getOpPackageName());
+        infoList = mSubscriptionControllerUT.getSubscriptionsInGroup(groupUuid,
+                mContext.getOpPackageName(), mContext.getAttributionTag());
         assertEquals(2, infoList.size());
         assertEquals(2, infoList.get(1).getSubscriptionId());
 
@@ -894,10 +977,19 @@
         subIdList = new int[] {1};
         mSubscriptionControllerUT.removeSubscriptionsFromGroup(
                 subIdList, groupUuid, mContext.getOpPackageName());
-        infoList = mSubscriptionControllerUT
-                .getSubscriptionsInGroup(groupUuid, mContext.getOpPackageName());
+        infoList = mSubscriptionControllerUT.getSubscriptionsInGroup(groupUuid,
+                mContext.getOpPackageName(), mContext.getAttributionTag());
         assertEquals(1, infoList.size());
         assertEquals(2, infoList.get(0).getSubscriptionId());
+
+        // Adding sub 1 into a non-existing UUID, which should be granted.
+        groupUuid = new ParcelUuid(UUID.randomUUID());
+        mSubscriptionControllerUT.addSubscriptionsIntoGroup(
+                subIdList, groupUuid, mContext.getOpPackageName());
+        infoList = mSubscriptionControllerUT.getSubscriptionsInGroup(groupUuid,
+                mContext.getOpPackageName(), mContext.getAttributionTag());
+        assertEquals(1, infoList.size());
+        assertEquals(1, infoList.get(0).getSubscriptionId());
     }
 
     private void registerMockTelephonyRegistry() {
@@ -951,7 +1043,8 @@
         int subId = getFirstSubId();
 
         try {
-            mSubscriptionControllerUT.getActiveSubscriptionInfo(subId, mCallingPackage);
+            mSubscriptionControllerUT.getActiveSubscriptionInfo(subId, mCallingPackage,
+                    mCallingFeature);
             fail("getActiveSubscriptionInfo should fail when invoked with no permissions");
         } catch (SecurityException expected) {
         }
@@ -963,16 +1056,49 @@
         // getActiveSubscriptionInfo should still return a result but the ICC ID should not be
         // available via getIccId or getCardString.
         testInsertSim();
-        mContextFixture.removeCallingOrSelfPermission(ContextFixture.PERMISSION_ENABLE_ALL);
-        mContextFixture.addCallingOrSelfPermission(Manifest.permission.READ_PHONE_STATE);
-        setupMocksForTelephonyPermissions();
+        setupReadPhoneNumbersTest();
+        setIdentifierAccess(false);
         int subId = getFirstSubId();
 
         SubscriptionInfo subscriptionInfo = mSubscriptionControllerUT.getActiveSubscriptionInfo(
-                subId, mCallingPackage);
+                subId, mCallingPackage, mCallingFeature);
+
         assertNotNull(subscriptionInfo);
         assertEquals(UNAVAILABLE_ICCID, subscriptionInfo.getIccId());
         assertEquals(UNAVAILABLE_ICCID, subscriptionInfo.getCardString());
+        assertEquals(UNAVAILABLE_NUMBER, subscriptionInfo.getNumber());
+    }
+
+    @Test
+    public void testGetActiveSubscriptionWithReadPhoneNumbers() throws Exception {
+        // If the calling package has the READ_PHONE_NUMBERS permission the number should be
+        // available in the SubscriptionInfo.
+        testInsertSim();
+        setupReadPhoneNumbersTest();
+        mContextFixture.addCallingOrSelfPermission(Manifest.permission.READ_PHONE_NUMBERS);
+        int subId = getFirstSubId();
+
+        SubscriptionInfo subscriptionInfo = mSubscriptionControllerUT.getActiveSubscriptionInfo(
+                subId, mCallingPackage, mCallingFeature);
+
+        assertNotNull(subscriptionInfo);
+        assertEquals(DISPLAY_NUMBER, subscriptionInfo.getNumber());
+    }
+
+    @Test
+    public void testGetActiveSubscriptionInfoWithCarrierPrivileges() throws Exception {
+        // If the calling package has the READ_PRIVILEGED_PHONE_STATE permission or carrier
+        // privileges the ICC ID should be available in the SubscriptionInfo.
+        testInsertSim();
+        setupIdentifierCarrierPrivilegesTest();
+        int subId = getFirstSubId();
+
+        SubscriptionInfo subscriptionInfo = mSubscriptionControllerUT.getActiveSubscriptionInfo(
+                subId, mCallingPackage, mCallingFeature);
+
+        assertNotNull(subscriptionInfo);
+        assertTrue(subscriptionInfo.getIccId().length() > 0);
+        assertTrue(subscriptionInfo.getCardString().length() > 0);
     }
 
     @Test
@@ -983,7 +1109,8 @@
         int subId = getFirstSubId();
 
         SubscriptionInfo subscriptionInfo = mSubscriptionControllerUT.getActiveSubscriptionInfo(
-                subId, mCallingPackage);
+                subId, mCallingPackage, mCallingFeature);
+
         assertNotNull(subscriptionInfo);
         assertTrue(subscriptionInfo.getIccId().length() > 0);
         assertTrue(subscriptionInfo.getCardString().length() > 0);
@@ -998,7 +1125,8 @@
         mContextFixture.removeCallingOrSelfPermission(ContextFixture.PERMISSION_ENABLE_ALL);
 
         try {
-            mSubscriptionControllerUT.getActiveSubscriptionInfoForSimSlotIndex(0, mCallingPackage);
+            mSubscriptionControllerUT.getActiveSubscriptionInfoForSimSlotIndex(0, mCallingPackage,
+                    mCallingFeature);
             fail("getActiveSubscriptionInfoForSimSlotIndex should fail when invoked with no "
                     + "permissions");
         } catch (SecurityException expected) {
@@ -1011,16 +1139,51 @@
         // getActiveSubscriptionInfoForSimlSlotIndex should still return the SubscriptionInfo but
         // the ICC ID should not be available via getIccId or getCardString.
         testInsertSim();
-        mContextFixture.removeCallingOrSelfPermission(ContextFixture.PERMISSION_ENABLE_ALL);
-        mContextFixture.addCallingOrSelfPermission(Manifest.permission.READ_PHONE_STATE);
-        setupMocksForTelephonyPermissions();
+        setupReadPhoneNumbersTest();
+        setIdentifierAccess(false);
 
         SubscriptionInfo subscriptionInfo =
                 mSubscriptionControllerUT.getActiveSubscriptionInfoForSimSlotIndex(0,
-                        mCallingPackage);
+                        mCallingPackage, mCallingFeature);
+
         assertNotNull(subscriptionInfo);
         assertEquals(UNAVAILABLE_ICCID, subscriptionInfo.getIccId());
         assertEquals(UNAVAILABLE_ICCID, subscriptionInfo.getCardString());
+        assertEquals(UNAVAILABLE_NUMBER, subscriptionInfo.getNumber());
+    }
+
+    @Test
+    public void testGetActiveSubscriptionInfoForSimSlotIndexWithReadPhoneNumbers()
+            throws Exception {
+        // If the calling package has the READ_PHONE_NUMBERS permission the number should be
+        // available in the SubscriptionInfo.
+        testInsertSim();
+        setupReadPhoneNumbersTest();
+        mContextFixture.addCallingOrSelfPermission(Manifest.permission.READ_PHONE_NUMBERS);
+
+        SubscriptionInfo subscriptionInfo =
+                mSubscriptionControllerUT.getActiveSubscriptionInfoForSimSlotIndex(0,
+                        mCallingPackage, mCallingFeature);
+
+        assertNotNull(subscriptionInfo);
+        assertEquals(DISPLAY_NUMBER, subscriptionInfo.getNumber());
+    }
+
+    @Test
+    public void testGetActiveSubscriptionInfoForSimSlotIndexWithCarrierPrivileges()
+            throws Exception {
+        // If the calling package has the READ_PRIVILEGED_PHONE_STATE permission or carrier
+        // privileges the ICC ID should be available in the SubscriptionInfo.
+        testInsertSim();
+        setupIdentifierCarrierPrivilegesTest();
+
+        SubscriptionInfo subscriptionInfo =
+                mSubscriptionControllerUT.getActiveSubscriptionInfoForSimSlotIndex(0,
+                        mCallingPackage, mCallingFeature);
+
+        assertNotNull(subscriptionInfo);
+        assertTrue(subscriptionInfo.getIccId().length() > 0);
+        assertTrue(subscriptionInfo.getCardString().length() > 0);
     }
 
     @Test
@@ -1032,7 +1195,8 @@
 
         SubscriptionInfo subscriptionInfo =
                 mSubscriptionControllerUT.getActiveSubscriptionInfoForSimSlotIndex(0,
-                        mCallingPackage);
+                        mCallingPackage, mCallingFeature);
+
         assertNotNull(subscriptionInfo);
         assertTrue(subscriptionInfo.getIccId().length() > 0);
         assertTrue(subscriptionInfo.getCardString().length() > 0);
@@ -1046,7 +1210,9 @@
         mContextFixture.removeCallingOrSelfPermission(ContextFixture.PERMISSION_ENABLE_ALL);
 
         List<SubscriptionInfo> subInfoList =
-                mSubscriptionControllerUT.getActiveSubscriptionInfoList(mCallingPackage);
+                mSubscriptionControllerUT.getActiveSubscriptionInfoList(mCallingPackage,
+                        mCallingFeature);
+
         assertNotNull(subInfoList);
         assertTrue(subInfoList.size() == 0);
     }
@@ -1057,20 +1223,84 @@
         // getActiveSubscriptionInfoList should still return the list of SubscriptionInfo objects
         // but the ICC ID should not be available via getIccId or getCardString.
         testInsertSim();
-        mContextFixture.removeCallingOrSelfPermission(ContextFixture.PERMISSION_ENABLE_ALL);
-        mContextFixture.addCallingOrSelfPermission(Manifest.permission.READ_PHONE_STATE);
-        setupMocksForTelephonyPermissions();
+        setupReadPhoneNumbersTest();
+        setIdentifierAccess(false);
 
         List<SubscriptionInfo> subInfoList =
-                mSubscriptionControllerUT.getActiveSubscriptionInfoList(mCallingPackage);
+                mSubscriptionControllerUT.getActiveSubscriptionInfoList(mCallingPackage,
+                        mCallingFeature);
+
         assertTrue(subInfoList.size() > 0);
         for (SubscriptionInfo info : subInfoList) {
             assertEquals(UNAVAILABLE_ICCID, info.getIccId());
             assertEquals(UNAVAILABLE_ICCID, info.getCardString());
+            assertEquals(UNAVAILABLE_NUMBER, info.getNumber());
         }
     }
 
     @Test
+    public void testGetActiveSubscriptionInfoListWithReadPhoneNumbers() throws Exception {
+        // If the calling package has the READ_PHONE_NUMBERS permission the number should be
+        // available in the SubscriptionInfo.
+        testInsertSim();
+        setupReadPhoneNumbersTest();
+        mContextFixture.addCallingOrSelfPermission(Manifest.permission.READ_PHONE_NUMBERS);
+
+        List<SubscriptionInfo> subInfoList =
+                mSubscriptionControllerUT.getActiveSubscriptionInfoList(mCallingPackage,
+                        mCallingFeature);
+
+        assertTrue(subInfoList.size() > 0);
+        SubscriptionInfo subInfo = subInfoList.get(0);
+        assertEquals(DISPLAY_NUMBER, subInfo.getNumber());
+    }
+
+    @Test
+    public void testGetActiveSubscriptionInfoListWithCarrierPrivileges() throws Exception {
+        // If the calling package has the READ_PRIVILEGED_PHONE_STATE permission or carrier
+        // privileges the ICC ID should be available in the SubscriptionInfo objects in the List.
+        testInsertSim();
+        setupIdentifierCarrierPrivilegesTest();
+
+        List<SubscriptionInfo> subInfoList =
+                mSubscriptionControllerUT.getActiveSubscriptionInfoList(mCallingPackage,
+                        mCallingFeature);
+
+        assertTrue(subInfoList.size() > 0);
+        for (SubscriptionInfo info : subInfoList) {
+            assertTrue(info.getIccId().length() > 0);
+            assertTrue(info.getCardString().length() > 0);
+        }
+    }
+
+    @Test
+    public void testGetActiveSubscriptionInfoListWithCarrierPrivilegesOnOneSubId()
+            throws Exception {
+        // If an app does not have the READ_PHONE_STATE permission but has carrier privileges on one
+        // out of multiple sub IDs then the SubscriptionInfo for that subId should be returned with
+        // the ICC ID and phone number.
+        testInsertSim();
+        doReturn(2).when(mTelephonyManager).getPhoneCount();
+        mSubscriptionControllerUT.addSubInfoRecord("test2", 1);
+        int firstSubId = getFirstSubId();
+        int secondSubId = getSubIdAtIndex(1);
+        mSubscriptionControllerUT.setDisplayNumber(DISPLAY_NUMBER, secondSubId);
+        setupIdentifierCarrierPrivilegesTest();
+        mContextFixture.removeCallingOrSelfPermission(Manifest.permission.READ_PHONE_STATE);
+        setCarrierPrivilegesForSubId(false, firstSubId);
+        setCarrierPrivilegesForSubId(true, secondSubId);
+
+        List<SubscriptionInfo> subInfoList =
+                mSubscriptionControllerUT.getActiveSubscriptionInfoList(mCallingPackage,
+                        mCallingFeature);
+
+        assertEquals(1, subInfoList.size());
+        SubscriptionInfo subInfo = subInfoList.get(0);
+        assertEquals("test2", subInfo.getIccId());
+        assertEquals(DISPLAY_NUMBER, subInfo.getNumber());
+    }
+
+    @Test
     public void testGetActiveSubscriptionInfoListWithIdentifierAccessWithoutNumberAccess()
             throws Exception {
         // An app with access to device identifiers may not have access to the device phone number
@@ -1078,13 +1308,17 @@
         // the device identifiers appop); this test verifies that an app with identifier access
         // can read the ICC ID but does not receive the phone number.
         testInsertSim();
+        setupReadPhoneNumbersTest();
+        setIdentifierAccess(true);
 
         List<SubscriptionInfo> subInfoList =
-                mSubscriptionControllerUT.getActiveSubscriptionInfoList(mCallingPackage);
+                mSubscriptionControllerUT.getActiveSubscriptionInfoList(mCallingPackage,
+                        mCallingFeature);
 
         assertEquals(1, subInfoList.size());
         SubscriptionInfo subInfo = subInfoList.get(0);
         assertEquals("test", subInfo.getIccId());
+        assertEquals(UNAVAILABLE_NUMBER, subInfo.getNumber());
     }
 
     @Test
@@ -1094,7 +1328,9 @@
         testInsertSim();
 
         List<SubscriptionInfo> subInfoList =
-                mSubscriptionControllerUT.getActiveSubscriptionInfoList(mCallingPackage);
+                mSubscriptionControllerUT.getActiveSubscriptionInfoList(mCallingPackage,
+                        mCallingFeature);
+
         assertTrue(subInfoList.size() > 0);
         for (SubscriptionInfo info : subInfoList) {
             assertTrue(info.getIccId().length() > 0);
@@ -1111,7 +1347,8 @@
         mContextFixture.removeCallingOrSelfPermission(ContextFixture.PERMISSION_ENABLE_ALL);
 
         try {
-            mSubscriptionControllerUT.getSubscriptionsInGroup(groupUuid, mCallingPackage);
+            mSubscriptionControllerUT.getSubscriptionsInGroup(groupUuid, mCallingPackage,
+                    mCallingFeature);
             fail("getSubscriptionsInGroup should fail when invoked with no permissions");
         } catch (SecurityException expected) {
         }
@@ -1123,16 +1360,50 @@
         // getSubscriptionsInGroup should still return the list of SubscriptionInfo objects
         // but the ICC ID should not be available via getIccId or getCardString.
         ParcelUuid groupUuid = setupGetSubscriptionsInGroupTest();
-        mContextFixture.removeCallingOrSelfPermission(ContextFixture.PERMISSION_ENABLE_ALL);
-        mContextFixture.addCallingOrSelfPermission(Manifest.permission.READ_PHONE_STATE);
-        setupMocksForTelephonyPermissions();
+        setupReadPhoneNumbersTest();
+        setIdentifierAccess(false);
 
         List<SubscriptionInfo> subInfoList = mSubscriptionControllerUT.getSubscriptionsInGroup(
-                groupUuid, mCallingPackage);
+                groupUuid, mCallingPackage, mCallingFeature);
+
         assertTrue(subInfoList.size() > 0);
         for (SubscriptionInfo info : subInfoList) {
             assertEquals(UNAVAILABLE_ICCID, info.getIccId());
             assertEquals(UNAVAILABLE_ICCID, info.getCardString());
+            assertEquals(UNAVAILABLE_NUMBER, info.getNumber());
+        }
+    }
+
+    @Test
+    public void testGetSubscriptionInGroupWithReadPhoneNumbers() throws Exception {
+        // If the calling package has the READ_PHONE_NUMBERS permission the number should be
+        // available in the SubscriptionInfo.
+        ParcelUuid groupUuid = setupGetSubscriptionsInGroupTest();
+        setupReadPhoneNumbersTest();
+        mContextFixture.addCallingOrSelfPermission(Manifest.permission.READ_PHONE_NUMBERS);
+
+        List<SubscriptionInfo> subInfoList = mSubscriptionControllerUT.getSubscriptionsInGroup(
+                groupUuid, mCallingPackage, mCallingFeature);
+
+        assertTrue(subInfoList.size() > 0);
+        SubscriptionInfo subInfo = subInfoList.get(0);
+        assertEquals(DISPLAY_NUMBER, subInfo.getNumber());
+    }
+
+    @Test
+    public void testGetSubscriptionsInGroupWithCarrierPrivileges() throws Exception {
+        // If the calling package has the READ_PRIVILEGED_PHONE_STATE permission or carrier
+        // privileges the ICC ID should be available in the SubscriptionInfo objects in the List.
+        ParcelUuid groupUuid = setupGetSubscriptionsInGroupTest();
+        setupIdentifierCarrierPrivilegesTest();
+
+        List<SubscriptionInfo> subInfoList = mSubscriptionControllerUT.getSubscriptionsInGroup(
+                groupUuid, mCallingPackage, mCallingFeature);
+
+        assertTrue(subInfoList.size() > 0);
+        for (SubscriptionInfo info : subInfoList) {
+            assertTrue(info.getIccId().length() > 0);
+            assertTrue(info.getCardString().length() > 0);
         }
     }
 
@@ -1143,7 +1414,8 @@
         ParcelUuid groupUuid = setupGetSubscriptionsInGroupTest();
 
         List<SubscriptionInfo> subInfoList = mSubscriptionControllerUT.getSubscriptionsInGroup(
-                groupUuid, mCallingPackage);
+                groupUuid, mCallingPackage, mCallingFeature);
+
         assertTrue(subInfoList.size() > 0);
         for (SubscriptionInfo info : subInfoList) {
             assertTrue(info.getIccId().length() > 0);
@@ -1160,12 +1432,30 @@
         return groupUuid;
     }
 
+    private void setupReadPhoneNumbersTest() throws Exception {
+        mSubscriptionControllerUT.setDisplayNumber(DISPLAY_NUMBER, getFirstSubId());
+        mContextFixture.removeCallingOrSelfPermission(ContextFixture.PERMISSION_ENABLE_ALL);
+        mContextFixture.addCallingOrSelfPermission(Manifest.permission.READ_PHONE_STATE);
+        setupMocksForTelephonyPermissions(Build.VERSION_CODES.R);
+        doReturn(AppOpsManager.MODE_DEFAULT).when(mAppOpsManager).noteOp(
+                eq(AppOpsManager.OPSTR_WRITE_SMS), anyInt(), anyString(),
+                nullable(String.class), nullable(String.class));
+    }
+
+    private void setupIdentifierCarrierPrivilegesTest() throws Exception {
+        mContextFixture.removeCallingOrSelfPermission(ContextFixture.PERMISSION_ENABLE_ALL);
+        mContextFixture.addCallingOrSelfPermission(Manifest.permission.READ_PHONE_STATE);
+        setupMocksForTelephonyPermissions();
+        setIdentifierAccess(false);
+        setCarrierPrivileges(true);
+    }
+
     private int getFirstSubId() throws Exception {
         return getSubIdAtIndex(0);
     }
 
     private int getSubIdAtIndex(int index) throws Exception {
-        int[] subIds = mSubscriptionControllerUT.getActiveSubIdList(/*visibleOnly*/false);
+        int[] subIds = mSubscriptionControllerUT.getActiveSubIdList(/*visibileOnly*/false);
         assertTrue(subIds != null && subIds.length > index);
         return subIds[index];
     }
@@ -1195,6 +1485,7 @@
     public void testGetEnabledSubscriptionIdDualSIM() {
         doReturn(SINGLE_SIM).when(mTelephonyManager).getSimCount();
         doReturn(SINGLE_SIM).when(mTelephonyManager).getPhoneCount();
+        doReturn(SINGLE_SIM).when(mTelephonyManager).getActiveModemCount();
         // A dual SIM device may have logical slot 0 mapped to physical slot 0
         // (i.e. logical slot 1 mapped to physical slot 1)
         UiccSlotInfo slot0 = getFakeUiccSlotInfo(true, 0);
@@ -1203,6 +1494,7 @@
         UiccSlot [] uiccSlots = {mUiccSlot, mUiccSlot};
 
         doReturn(2).when(mTelephonyManager).getPhoneCount();
+        doReturn(2).when(mTelephonyManager).getActiveModemCount();
         doReturn(uiccSlotInfos).when(mTelephonyManager).getUiccSlotsInfo();
         doReturn(uiccSlots).when(mUiccController).getUiccSlots();
         assertEquals(2, UiccController.getInstance().getUiccSlots().length);
@@ -1220,20 +1512,12 @@
 
 
     private UiccSlotInfo getFakeUiccSlotInfo(boolean active, int logicalSlotIndex) {
-        return new UiccSlotInfo(active, false, "fake card Id",
-                UiccSlotInfo.CARD_STATE_INFO_PRESENT, logicalSlotIndex, true, true);
+        return getFakeUiccSlotInfo(active, logicalSlotIndex, "fake card Id");
     }
 
-    // TODO: Move this test once SubscriptionManager.setAlwaysAllowMmsData is moved to telephony
-    // manager.
-    @Test
-    @SmallTest
-    public void testSetAlwaysAllowMmsData() throws Exception {
-        mSubscriptionControllerUT.setAlwaysAllowMmsData(0, true);
-        verify(mDataEnabledSettings).setAlwaysAllowMmsData(eq(true));
-        clearInvocations(mDataEnabledSettings);
-        mSubscriptionControllerUT.setAlwaysAllowMmsData(0, false);
-        verify(mDataEnabledSettings).setAlwaysAllowMmsData(eq(false));
+    private UiccSlotInfo getFakeUiccSlotInfo(boolean active, int logicalSlotIndex, String cardId) {
+        return new UiccSlotInfo(active, false, cardId,
+                UiccSlotInfo.CARD_STATE_INFO_PRESENT, logicalSlotIndex, true, true);
     }
 
     @Test
@@ -1257,6 +1541,104 @@
         assertTrue(mSubscriptionControllerUT.getNameSourcePriority(
                 SubscriptionManager.NAME_SOURCE_SIM_PNN)
                 > mSubscriptionControllerUT.getNameSourcePriority(
-                SubscriptionManager.NAME_SOURCE_DEFAULT_SOURCE));
+                SubscriptionManager.NAME_SOURCE_CARRIER_ID));
+    }
+
+    @Test
+    @SmallTest
+    public void testGetAvailableSubscriptionList() throws Exception {
+        // TODO b/123300875 slot index 1 is not expected to be valid
+        mSubscriptionControllerUT.addSubInfoRecord("123", 1);   // sub 1
+        mSubscriptionControllerUT.addSubInfoRecord("456", 0);   // sub 2
+
+        List<SubscriptionInfo> infoList = mSubscriptionControllerUT
+                .getAvailableSubscriptionInfoList(mCallingPackage, mCallingFeature);
+        assertEquals(2, infoList.size());
+        assertEquals("456", infoList.get(0).getIccId());
+        assertEquals("123", infoList.get(1).getIccId());
+
+        // Remove "123" from active sim list but have it inserted.
+        UiccSlot[] uiccSlots = {mUiccSlot};
+        IccCardStatus.CardState cardState = CARDSTATE_PRESENT;
+        doReturn(uiccSlots).when(mUiccController).getUiccSlots();
+        doReturn(cardState).when(mUiccSlot).getCardState();
+        doReturn("123").when(mUiccSlot).getIccId();
+        mSubscriptionControllerUT.clearSubInfoRecord(1);
+
+        // Active sub list should return 1 now.
+        infoList = mSubscriptionControllerUT
+                .getActiveSubscriptionInfoList(mCallingPackage, mCallingFeature);
+        assertEquals(1, infoList.size());
+        assertEquals("456", infoList.get(0).getIccId());
+
+        // Available sub list should still return two.
+        infoList = mSubscriptionControllerUT
+                .getAvailableSubscriptionInfoList(mCallingPackage, mCallingFeature);
+        assertEquals(2, infoList.size());
+        assertEquals("123", infoList.get(0).getIccId());
+        assertEquals("456", infoList.get(1).getIccId());
+    }
+
+    @Test
+    @SmallTest
+    public void testGetAvailableSubscriptionList_withTrailingF() throws Exception {
+        // TODO b/123300875 slot index 1 is not expected to be valid
+        mSubscriptionControllerUT.addSubInfoRecord("123", 1);   // sub 1
+        mSubscriptionControllerUT.addSubInfoRecord("456", 0);   // sub 2
+
+        // Remove "123" from active sim list but have it inserted.
+        UiccSlot[] uiccSlots = {mUiccSlot};
+        IccCardStatus.CardState cardState = CARDSTATE_PRESENT;
+        doReturn(uiccSlots).when(mUiccController).getUiccSlots();
+        doReturn(cardState).when(mUiccSlot).getCardState();
+        // IccId ends with a 'F' which should be ignored and taking into account.
+        doReturn("123F").when(mUiccSlot).getIccId();
+        mSubscriptionControllerUT.clearSubInfoRecord(1);
+
+        // Active sub list should return 1 now.
+        List<SubscriptionInfo> infoList = mSubscriptionControllerUT
+                .getActiveSubscriptionInfoList(mCallingPackage, mCallingFeature);
+        assertEquals(1, infoList.size());
+        assertEquals("456", infoList.get(0).getIccId());
+
+        // Available sub list should still return two.
+        infoList = mSubscriptionControllerUT
+                .getAvailableSubscriptionInfoList(mCallingPackage, mCallingFeature);
+        assertEquals(2, infoList.size());
+        assertEquals("123", infoList.get(0).getIccId());
+        assertEquals("456", infoList.get(1).getIccId());
+    }
+
+    @Test
+    @SmallTest
+    public void testSetPreferredDataSubscriptionId_phoneSwitcherNotInitialized() throws Exception {
+        replaceInstance(PhoneSwitcher.class, "sPhoneSwitcher", null, null);
+
+        mSubscriptionControllerUT.setPreferredDataSubscriptionId(1, true, mSetOpptDataCallback);
+        verify(mSetOpptDataCallback).onComplete(SET_OPPORTUNISTIC_SUB_REMOTE_SERVICE_EXCEPTION);
+    }
+
+    @Test
+    @SmallTest
+    public void testGetPreferredDataSubscriptionId_phoneSwitcherNotInitialized() throws Exception {
+        replaceInstance(PhoneSwitcher.class, "sPhoneSwitcher", null, null);
+
+        assertEquals(SubscriptionManager.DEFAULT_SUBSCRIPTION_ID,
+                mSubscriptionControllerUT.getPreferredDataSubscriptionId());
+    }
+
+    @Test
+    public void testSetSubscriptionEnabled_disableActivePsim_cardIdWithTrailingF() {
+        String iccId = "123F";
+        mSubscriptionControllerUT.addSubInfoRecord(iccId, 0);
+        mSubscriptionControllerUT.registerForUiccAppsEnabled(mHandler, 0, null, false);
+        UiccSlotInfo slot = getFakeUiccSlotInfo(true, 0, iccId + "FF");
+        UiccSlotInfo[] uiccSlotInfos = {slot};
+        doReturn(uiccSlotInfos).when(mTelephonyManager).getUiccSlotsInfo();
+
+        mSubscriptionControllerUT.setSubscriptionEnabled(false, 1);
+        verify(mHandler).sendMessageAtTime(any(), anyLong());
+        assertFalse(mSubscriptionControllerUT.getActiveSubscriptionInfo(
+                1, mContext.getOpPackageName(), null).areUiccApplicationsEnabled());
     }
 }
diff --git a/tests/telephonytests/src/com/android/internal/telephony/SubscriptionInfoUpdaterTest.java b/tests/telephonytests/src/com/android/internal/telephony/SubscriptionInfoUpdaterTest.java
index 551ca40..ce5be8e 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/SubscriptionInfoUpdaterTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/SubscriptionInfoUpdaterTest.java
@@ -15,7 +15,7 @@
  */
 package com.android.internal.telephony;
 
-import static com.android.internal.telephony.TelephonyTestUtils.waitForMs;
+import static android.telephony.SubscriptionManager.UICC_APPLICATIONS_ENABLED;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
@@ -39,9 +39,10 @@
 import android.content.pm.IPackageManager;
 import android.content.pm.UserInfo;
 import android.net.Uri;
-import android.os.HandlerThread;
+import android.os.Looper;
 import android.os.ParcelUuid;
 import android.os.PersistableBundle;
+import android.permission.IPermissionManager;
 import android.service.euicc.EuiccProfileInfo;
 import android.service.euicc.EuiccService;
 import android.service.euicc.GetEuiccProfileInfoListResult;
@@ -52,6 +53,8 @@
 import android.test.mock.MockContentProvider;
 import android.test.mock.MockContentResolver;
 import android.test.suitebuilder.annotation.SmallTest;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
 
 import com.android.internal.telephony.euicc.EuiccController;
 import com.android.internal.telephony.uicc.IccFileHandler;
@@ -62,6 +65,7 @@
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
+import org.junit.runner.RunWith;
 import org.mockito.ArgumentCaptor;
 import org.mockito.Mock;
 import org.mockito.invocation.InvocationOnMock;
@@ -73,6 +77,8 @@
 import java.util.HashMap;
 import java.util.List;
 
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper
 public class SubscriptionInfoUpdaterTest extends TelephonyTest {
 
     private static final int FAKE_SUB_ID_1 = 0;
@@ -82,7 +88,6 @@
     private static final String FAKE_MCC_MNC_1 = "123456";
     private static final String FAKE_MCC_MNC_2 = "456789";
 
-    private SubscriptionInfoUpdaterHandlerThread mSubscriptionInfoUpdaterHandlerThread;
     private SubscriptionInfoUpdater mUpdater;
     private IccRecords mIccRecord;
     @Mock
@@ -102,6 +107,8 @@
     @Mock
     private IPackageManager mPackageManager;
     @Mock
+    private IPermissionManager mPermissionManager;
+    @Mock
     private UiccSlot mUiccSlot;
 
     /*Custom ContentProvider */
@@ -112,27 +119,13 @@
         }
     }
 
-    private class SubscriptionInfoUpdaterHandlerThread extends HandlerThread {
-
-        private SubscriptionInfoUpdaterHandlerThread(String name) {
-            super(name);
-        }
-
-        @Override
-        public void onLooperPrepared() {
-            mUpdater = new SubscriptionInfoUpdater(getLooper(), mContext, new Phone[]{mPhone},
-                    new CommandsInterface[]{mSimulatedCommands}, mPackageManager);
-            setReady(true);
-        }
-    }
-
     @Before
     public void setUp() throws Exception {
         super.setUp(this.getClass().getSimpleName());
 
-        replaceInstance(SubscriptionInfoUpdater.class, "mIccId", null, new String[1]);
-        replaceInstance(SubscriptionInfoUpdater.class, "mContext", null, null);
-        replaceInstance(SubscriptionInfoUpdater.class, "PROJECT_SIM_NUM", null, 1);
+        replaceInstance(SubscriptionInfoUpdater.class, "sIccId", null, new String[1]);
+        replaceInstance(SubscriptionInfoUpdater.class, "sContext", null, null);
+        replaceInstance(SubscriptionInfoUpdater.class, "SUPPORTED_MODEM_COUNT", null, 1);
         replaceInstance(SubscriptionInfoUpdater.class, "sSimCardState", null, new int[1]);
         replaceInstance(SubscriptionInfoUpdater.class, "sSimApplicationState", null, new int[1]);
         replaceInstance(SubscriptionInfoUpdater.class, "sIsSubInfoInitialized", null, false);
@@ -144,6 +137,7 @@
         doReturn(mUiccSlot).when(mUiccController).getUiccSlotForPhone(anyInt());
         doReturn(1).when(mTelephonyManager).getSimCount();
         doReturn(1).when(mTelephonyManager).getPhoneCount();
+        doReturn(1).when(mTelephonyManager).getActiveModemCount();
 
         when(mContentProvider.update(any(), any(), any(), isNull())).thenAnswer(
                 new Answer<Integer>() {
@@ -167,16 +161,15 @@
                 .getActiveSubIdList(/*visibleOnly*/false);
         mIccRecord = mUiccProfile.getIccRecords();
 
-        mSubscriptionInfoUpdaterHandlerThread = new SubscriptionInfoUpdaterHandlerThread(TAG);
-        mSubscriptionInfoUpdaterHandlerThread.start();
-        waitUntilReady();
+        mUpdater = new SubscriptionInfoUpdater(Looper.myLooper(), mContext,
+            new CommandsInterface[]{mSimulatedCommands});
+        processAllMessages();
 
         assertFalse(mUpdater.isSubInfoInitialized());
     }
 
     @After
     public void tearDown() throws Exception {
-        mSubscriptionInfoUpdaterHandlerThread.quit();
         super.tearDown();
     }
 
@@ -188,9 +181,9 @@
         doReturn(new int[]{FAKE_SUB_ID_1}).when(mSubscriptionController)
                 .getActiveSubIdList(/*visibleOnly*/false);
         mUpdater.updateInternalIccState(
-                IccCardConstants.INTENT_VALUE_ICC_ABSENT, null, FAKE_SUB_ID_1, false);
+                IccCardConstants.INTENT_VALUE_ICC_ABSENT, null, FAKE_SUB_ID_1);
 
-        waitForMs(100);
+        processAllMessages();
         assertTrue(mUpdater.isSubInfoInitialized());
         verify(mSubscriptionController, times(1)).clearSubInfoRecord(eq(FAKE_SUB_ID_1));
 
@@ -208,10 +201,9 @@
                 .getSubInfoUsingSlotIndexPrivileged(eq(FAKE_SUB_ID_1));
         doReturn(new int[]{FAKE_SUB_ID_1}).when(mSubscriptionController)
                 .getActiveSubIdList(/*visibleOnly*/false);
-        mUpdater.updateInternalIccState(
-                IccCardConstants.INTENT_VALUE_ICC_ABSENT, null, FAKE_SUB_ID_1, true);
+        mUpdater.updateInternalIccStateForInactiveSlot(FAKE_SUB_ID_1, null);
 
-        waitForMs(100);
+        processAllMessages();
         assertTrue(mUpdater.isSubInfoInitialized());
         verify(mSubscriptionController, times(1)).clearSubInfoRecord(eq(FAKE_SUB_ID_1));
 
@@ -229,9 +221,9 @@
     @SmallTest
     public void testSimUnknown() throws Exception {
         mUpdater.updateInternalIccState(
-                IccCardConstants.INTENT_VALUE_ICC_UNKNOWN, null, FAKE_SUB_ID_1, false);
+                IccCardConstants.INTENT_VALUE_ICC_UNKNOWN, null, FAKE_SUB_ID_1);
 
-        waitForMs(100);
+        processAllMessages();
         assertFalse(mUpdater.isSubInfoInitialized());
         verify(mSubscriptionContent, times(0)).put(anyString(), any());
         CarrierConfigManager mConfigManager = (CarrierConfigManager)
@@ -244,11 +236,89 @@
 
     @Test
     @SmallTest
+    public void testSimNotReady() throws Exception {
+        mUpdater.updateInternalIccState(
+                IccCardConstants.INTENT_VALUE_ICC_NOT_READY, null, FAKE_SUB_ID_1);
+
+        processAllMessages();
+        assertFalse(mUpdater.isSubInfoInitialized());
+        verify(mSubscriptionContent, never()).put(anyString(), any());
+        CarrierConfigManager mConfigManager = (CarrierConfigManager)
+                mContext.getSystemService(Context.CARRIER_CONFIG_SERVICE);
+        verify(mConfigManager, never()).updateConfigForPhoneId(eq(FAKE_SUB_ID_1),
+                eq(IccCardConstants.INTENT_VALUE_ICC_NOT_READY));
+        verify(mSubscriptionController, never()).clearSubInfo();
+        verify(mSubscriptionController, never()).notifySubscriptionInfoChanged();
+    }
+
+    @Test
+    @SmallTest
+    public void testSimNotReadyEmptyProfile() throws Exception {
+        doReturn(mIccCard).when(mPhone).getIccCard();
+        doReturn(true).when(mIccCard).isEmptyProfile();
+
+        mUpdater.updateInternalIccState(
+                IccCardConstants.INTENT_VALUE_ICC_NOT_READY, null, FAKE_SUB_ID_1);
+
+        processAllMessages();
+        assertTrue(mUpdater.isSubInfoInitialized());
+        // Sub info should be cleared and change should be notified.
+        verify(mSubscriptionController).clearSubInfoRecord(eq(FAKE_SUB_ID_1));
+        verify(mSubscriptionController).notifySubscriptionInfoChanged();
+        // No new sub should be added.
+        verify(mSubscriptionManager, never()).addSubscriptionInfoRecord(any(), anyInt());
+        verify(mSubscriptionContent, never()).put(anyString(), any());
+        CarrierConfigManager mConfigManager = (CarrierConfigManager)
+                mContext.getSystemService(Context.CARRIER_CONFIG_SERVICE);
+        verify(mConfigManager).updateConfigForPhoneId(eq(FAKE_SUB_ID_1),
+                eq(IccCardConstants.INTENT_VALUE_ICC_NOT_READY));
+    }
+
+    @Test
+    @SmallTest
+    public void testSimNotReadyDisabledUiccApps() throws Exception {
+        String iccId = "123456";
+        doReturn(mIccCard).when(mPhone).getIccCard();
+        doReturn(false).when(mIccCard).isEmptyProfile();
+        doReturn(iccId).when(mUiccSlot).getIccId();
+        doReturn(mSubInfo).when(mSubscriptionController).getSubInfoForIccId(iccId);
+        doReturn(false).when(mSubInfo).areUiccApplicationsEnabled();
+
+        mUpdater.updateInternalIccState(
+                IccCardConstants.INTENT_VALUE_ICC_NOT_READY, null, FAKE_SUB_ID_1);
+
+        processAllMessages();
+        assertTrue(mUpdater.isSubInfoInitialized());
+        // Sub info should be cleared and change should be notified.
+        verify(mSubscriptionController).clearSubInfoRecord(eq(FAKE_SUB_ID_1));
+        verify(mSubscriptionController).notifySubscriptionInfoChanged();
+        // No new sub should be added.
+        verify(mSubscriptionManager, never()).addSubscriptionInfoRecord(any(), anyInt());
+        verify(mSubscriptionContent, never()).put(anyString(), any());
+        CarrierConfigManager mConfigManager = (CarrierConfigManager)
+                mContext.getSystemService(Context.CARRIER_CONFIG_SERVICE);
+        verify(mConfigManager).updateConfigForPhoneId(eq(FAKE_SUB_ID_1),
+                eq(IccCardConstants.INTENT_VALUE_ICC_NOT_READY));
+
+        // When becomes ABSENT, UICC_APPLICATIONS_ENABLED should be reset to true.
+        mUpdater.updateInternalIccState(
+                IccCardConstants.INTENT_VALUE_ICC_ABSENT, null, FAKE_SUB_ID_1);
+        processAllMessages();
+        ArgumentCaptor<ContentValues> valueCapture = ArgumentCaptor.forClass(ContentValues.class);
+        verify(mContentProvider).update(eq(SubscriptionManager.CONTENT_URI), valueCapture.capture(),
+                eq(SubscriptionManager.ICC_ID + "=\'" + iccId + "\'"), eq(null));
+        ContentValues contentValues = valueCapture.getValue();
+        assertTrue(contentValues != null && contentValues.getAsBoolean(
+                UICC_APPLICATIONS_ENABLED));
+    }
+
+    @Test
+    @SmallTest
     public void testSimError() throws Exception {
         mUpdater.updateInternalIccState(
-                IccCardConstants.INTENT_VALUE_ICC_CARD_IO_ERROR, null, FAKE_SUB_ID_1, false);
+                IccCardConstants.INTENT_VALUE_ICC_CARD_IO_ERROR, null, FAKE_SUB_ID_1);
 
-        waitForMs(100);
+        processAllMessages();
         assertTrue(mUpdater.isSubInfoInitialized());
         verify(mSubscriptionContent, times(0)).put(anyString(), any());
         CarrierConfigManager mConfigManager = (CarrierConfigManager)
@@ -263,9 +333,9 @@
     @SmallTest
     public void testWrongSimState() throws Exception {
         mUpdater.updateInternalIccState(
-                IccCardConstants.INTENT_VALUE_ICC_IMSI, null, 2, false);
+                IccCardConstants.INTENT_VALUE_ICC_IMSI, null, 2);
 
-        waitForMs(100);
+        processAllMessages();
         assertFalse(mUpdater.isSubInfoInitialized());
         verify(mSubscriptionContent, times(0)).put(anyString(), any());
         CarrierConfigManager mConfigManager = (CarrierConfigManager)
@@ -284,11 +354,13 @@
                 .getSubInfoUsingSlotIndexPrivileged(eq(FAKE_SUB_ID_1));
         doReturn("89012604200000000000").when(mIccRecord).getFullIccId();
         doReturn(FAKE_MCC_MNC_1).when(mTelephonyManager).getSimOperatorNumeric(FAKE_SUB_ID_1);
+        when(mActivityManager.updateMccMncConfiguration(anyString(), anyString())).thenReturn(
+                true);
 
         mUpdater.updateInternalIccState(
-                IccCardConstants.INTENT_VALUE_ICC_LOADED, null, FAKE_SUB_ID_1, false);
+                IccCardConstants.INTENT_VALUE_ICC_LOADED, null, FAKE_SUB_ID_1);
 
-        waitForMs(100);
+        processAllMessages();
         assertTrue(mUpdater.isSubInfoInitialized());
 
         // verify SIM_STATE_CHANGED broadcast. It should be broadcast twice, once for
@@ -324,7 +396,7 @@
         // ACTION_USER_UNLOCKED should trigger another SIM_STATE_CHANGED
         Intent intentSimStateChanged = new Intent(Intent.ACTION_USER_UNLOCKED);
         mContext.sendBroadcast(intentSimStateChanged);
-        waitForMs(100);
+        processAllMessages();
 
         // verify SIM_STATE_CHANGED broadcast
         /* todo: cannot verify as intent is sent using ActivityManagerNative.broadcastStickyIntent()
@@ -352,9 +424,9 @@
         doReturn(Arrays.asList(mSubInfo)).when(mSubscriptionController)
                 .getSubInfoUsingSlotIndexPrivileged(eq(FAKE_SUB_ID_1));
         mUpdater.updateInternalIccState(
-                IccCardConstants.INTENT_VALUE_ICC_LOADED, null, FAKE_SUB_ID_1, false);
+                IccCardConstants.INTENT_VALUE_ICC_LOADED, null, FAKE_SUB_ID_1);
 
-        waitForMs(300);
+        processAllMessages();
         assertTrue(mUpdater.isSubInfoInitialized());
         SubscriptionManager mSubscriptionManager = SubscriptionManager.from(mContext);
         verify(mTelephonyManager).getSimOperatorNumeric(FAKE_SUB_ID_1);
@@ -379,10 +451,9 @@
         doReturn(Arrays.asList(mSubInfo)).when(mSubscriptionController)
                 .getSubInfoUsingSlotIndexPrivileged(eq(FAKE_SUB_ID_1));
         mUpdater.updateInternalIccState(
-                IccCardConstants.INTENT_VALUE_ICC_LOCKED, "TESTING", FAKE_SUB_ID_1, false);
+                IccCardConstants.INTENT_VALUE_ICC_LOCKED, "TESTING", FAKE_SUB_ID_1);
 
-        waitForMs(100);
-
+        processAllMessages();
         assertTrue(mUpdater.isSubInfoInitialized());
         SubscriptionManager mSubscriptionManager = SubscriptionManager.from(mContext);
         verify(mSubscriptionManager, times(1)).addSubscriptionInfoRecord(
@@ -400,11 +471,10 @@
     @SmallTest
     public void testDualSimLoaded() throws Exception {
         // Mock there is two sim cards
-        replaceInstance(SubscriptionInfoUpdater.class, "mIccId", null,
+        replaceInstance(PhoneFactory.class, "sPhones", null, new Phone[]{mPhone, mPhone});
+        replaceInstance(SubscriptionInfoUpdater.class, "sIccId", null,
                 new String[]{null, null});
-        replaceInstance(SubscriptionInfoUpdater.class, "PROJECT_SIM_NUM", null, 2);
-        replaceInstance(SubscriptionInfoUpdater.class, "mPhone", null,
-                new Phone[]{mPhone, mPhone});
+        replaceInstance(SubscriptionInfoUpdater.class, "SUPPORTED_MODEM_COUNT", null, 2);
         replaceInstance(SubscriptionInfoUpdater.class, "sSimCardState", null,
                 new int[]{0, 0});
         replaceInstance(SubscriptionInfoUpdater.class, "sSimApplicationState", null,
@@ -414,7 +484,10 @@
                 .getActiveSubscriptionIdList();
         doReturn(FAKE_SUB_ID_1).when(mSubscriptionController).getPhoneId(eq(FAKE_SUB_ID_1));
         doReturn(FAKE_SUB_ID_2).when(mSubscriptionController).getPhoneId(eq(FAKE_SUB_ID_2));
-        doReturn(2).when(mTelephonyManager).getSimCount();
+        doReturn(2).when(mTelephonyManager).getPhoneCount();
+        doReturn(2).when(mTelephonyManager).getActiveModemCount();
+        when(mActivityManager.updateMccMncConfiguration(anyString(), anyString())).thenReturn(
+                true);
         doReturn(FAKE_MCC_MNC_1).when(mTelephonyManager).getSimOperatorNumeric(eq(FAKE_SUB_ID_1));
         doReturn(FAKE_MCC_MNC_2).when(mTelephonyManager).getSimOperatorNumeric(eq(FAKE_SUB_ID_2));
         verify(mSubscriptionController, times(0)).clearSubInfo();
@@ -428,10 +501,9 @@
         doReturn(Arrays.asList(mSubInfo)).when(mSubscriptionController)
                 .getSubInfoUsingSlotIndexPrivileged(eq(FAKE_SUB_ID_1));
         mUpdater.updateInternalIccState(
-                IccCardConstants.INTENT_VALUE_ICC_LOADED, null, FAKE_SUB_ID_1, false);
+                IccCardConstants.INTENT_VALUE_ICC_LOADED, null, FAKE_SUB_ID_1);
 
-        waitForMs(100);
-
+        processAllMessages();
         verify(mSubscriptionManager, times(1)).addSubscriptionInfoRecord(anyString(), anyInt());
         verify(mSubscriptionController, times(1)).notifySubscriptionInfoChanged();
         verify(mSubscriptionController, times(1)).setMccMnc(anyString(), anyInt());
@@ -444,10 +516,9 @@
         doReturn("89012604200000000001").when(mIccRecord).getFullIccId();
 
         mUpdater.updateInternalIccState(
-                IccCardConstants.INTENT_VALUE_ICC_LOADED, null, FAKE_SUB_ID_2, false);
+                IccCardConstants.INTENT_VALUE_ICC_LOADED, null, FAKE_SUB_ID_2);
 
-        waitForMs(100);
-
+        processAllMessages();
         verify(mSubscriptionManager, times(1)).addSubscriptionInfoRecord(eq("89012604200000000000"),
                 eq(FAKE_SUB_ID_1));
         verify(mSubscriptionManager, times(1)).addSubscriptionInfoRecord(eq("89012604200000000001"),
@@ -464,14 +535,13 @@
         // ICCID will be queried even if it is already available
         doReturn("98106240020000000000").when(mIccRecord).getFullIccId();
 
-        replaceInstance(SubscriptionInfoUpdater.class, "mIccId", null,
+        replaceInstance(SubscriptionInfoUpdater.class, "sIccId", null,
                 new String[]{"89012604200000000000"});
 
         mUpdater.updateInternalIccState(
-                IccCardConstants.INTENT_VALUE_ICC_LOCKED, "TESTING", FAKE_SUB_ID_1, false);
+                IccCardConstants.INTENT_VALUE_ICC_LOCKED, "TESTING", FAKE_SUB_ID_1);
 
-        waitForMs(100);
-
+        processAllMessages();
         assertTrue(mUpdater.isSubInfoInitialized());
         SubscriptionManager mSubscriptionManager = SubscriptionManager.from(mContext);
         verify(mSubscriptionManager, times(1)).addSubscriptionInfoRecord(
@@ -516,9 +586,7 @@
         List<Integer> cardIds = new ArrayList<>();
         cardIds.add(FAKE_CARD_ID);
         mUpdater.updateEmbeddedSubscriptions(cardIds, null /* callback */);
-
-        // Wait for some time until the callback is triggered.
-        waitForMs(100);
+        processAllMessages();
 
         // 3 is new and so a new entry should have been created.
         verify(mSubscriptionController).insertEmptySubInfoRecord(
@@ -621,9 +689,9 @@
 
         // Mock sending a sim loaded for SIM 1
         mUpdater.updateInternalIccState(
-                IccCardConstants.INTENT_VALUE_ICC_LOADED, "TESTING", FAKE_SUB_ID_1, false);
+                IccCardConstants.INTENT_VALUE_ICC_LOADED, "TESTING", FAKE_SUB_ID_1);
 
-        waitForMs(100);
+        processAllMessages();
 
         SubscriptionManager mSubscriptionManager = SubscriptionManager.from(mContext);
         verify(mSubscriptionController, times(1)).notifySubscriptionInfoChanged();
diff --git a/tests/telephonytests/src/com/android/internal/telephony/SubscriptionMonitorTest.java b/tests/telephonytests/src/com/android/internal/telephony/SubscriptionMonitorTest.java
deleted file mode 100644
index c980d94..0000000
--- a/tests/telephonytests/src/com/android/internal/telephony/SubscriptionMonitorTest.java
+++ /dev/null
@@ -1,841 +0,0 @@
-/*
- * Copyright (C) 2006 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;
-
-import static android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID;
-
-import com.android.internal.telephony.MccTable;
-import com.android.internal.telephony.mocks.SubscriptionControllerMock;
-import com.android.internal.telephony.mocks.TelephonyRegistryMock;
-
-import android.content.Context;
-import android.os.AsyncResult;
-import android.os.Handler;
-import android.os.HandlerThread;
-import android.os.Looper;
-import android.os.Message;
-
-import android.test.AndroidTestCase;
-import android.test.suitebuilder.annotation.SmallTest;
-
-import android.telephony.Rlog;
-
-import java.util.concurrent.atomic.AtomicInteger;
-import java.util.concurrent.atomic.AtomicReference;
-
-public class SubscriptionMonitorTest extends AndroidTestCase {
-    private final static String LOG_TAG = "SubscriptionMonitorTest";
-
-    static void failAndStack(String str) {
-        fail(str + "\n" + SubscriptionMonitorTest.stack());
-    }
-
-    static String stack() {
-        StringBuilder sb = new StringBuilder();
-        for(StackTraceElement e : Thread.currentThread().getStackTrace()) {
-            sb.append(e.toString()).append("\n");
-        }
-        return sb.toString();
-    }
-
-    private static class TestHandler extends Handler {
-        public final static int SUBSCRIPTION_CHANGED = 1;
-        public final static int DEFAULT_SUBSCRIPTION_CHANGED = 2;
-        public final static int IN_IDLE = 3;
-
-        HandlerThread handlerThread;
-
-        public TestHandler(Looper looper) {
-            super(looper);
-        }
-
-        public void die() {
-            if(handlerThread != null) {
-                handlerThread.quit();
-                handlerThread = null;
-            }
-        }
-
-        public void blockTilIdle() {
-            Object lock = new Object();
-            synchronized (lock) {
-                Message msg = this.obtainMessage(IN_IDLE, lock);
-                msg.sendToTarget();
-                try {
-                    lock.wait();
-                } catch (InterruptedException e) {}
-            }
-        }
-
-        public static TestHandler makeHandler() {
-            final HandlerThread handlerThread = new HandlerThread("TestHandler");
-            handlerThread.start();
-            final TestHandler result = new TestHandler(handlerThread.getLooper());
-            result.handlerThread = handlerThread;
-            return result;
-        }
-
-        private boolean objectEquals(Object o1, Object o2) {
-            if (o1 == null) return (o2 == null);
-            return o1.equals(o2);
-        }
-
-        private void failAndStack(String str) {
-            SubscriptionMonitorTest.failAndStack(str);
-        }
-
-        @Override
-        public void handleMessage(Message msg) {
-            switch (msg.what) {
-                case SUBSCRIPTION_CHANGED: {
-                    AsyncResult ar = (AsyncResult)(msg.obj);
-                    if (objectEquals(ar.userObj, mSubscriptionChangedObject.get()) == false) {
-                        failAndStack("Subscription Changed object is incorrect!");
-                    }
-                    mSubscriptionChangedCount.incrementAndGet();
-                    Rlog.d(LOG_TAG, "SUBSCRIPTION_CHANGED, inc to " +
-                            mSubscriptionChangedCount.get());
-                    break;
-                }
-                case DEFAULT_SUBSCRIPTION_CHANGED: {
-                    AsyncResult ar = (AsyncResult)(msg.obj);
-                    if (objectEquals(ar.userObj,
-                            mDefaultSubscriptionChangedObject.get()) == false) {
-                        failAndStack("Default Subscription Changed object is incorrect!");
-                    }
-                    mDefaultSubscriptionChangedCount.incrementAndGet();
-                    Rlog.d(LOG_TAG, "DEFAULT_SUBSCRIPTION_CHANGED, inc to " +
-                            mDefaultSubscriptionChangedCount.get());
-                    break;
-                }
-                case IN_IDLE: {
-                    Object lock = msg.obj;
-                    synchronized (lock) {
-                        lock.notify();
-                    }
-                    break;
-                }
-            }
-        }
-
-        private final AtomicInteger mSubscriptionChangedCount = new AtomicInteger(0);
-        private final AtomicReference<Object> mSubscriptionChangedObject =
-                new AtomicReference<Object>();
-
-        private final AtomicInteger mDefaultSubscriptionChangedCount = new AtomicInteger(0);
-        private final AtomicReference<Object> mDefaultSubscriptionChangedObject =
-                new AtomicReference<Object>();
-
-        public void reset() {
-            mSubscriptionChangedCount.set(0);
-            mSubscriptionChangedObject.set(null);
-
-            mDefaultSubscriptionChangedCount.set(0);
-            mDefaultSubscriptionChangedObject.set(null);
-        }
-
-        public void setSubscriptionChangedObject(Object o) {
-            mSubscriptionChangedObject.set(o);
-        }
-        public void setDefaultSubscriptionChangedObject(Object o) {
-            mDefaultSubscriptionChangedObject.set(o);
-        }
-
-        public int getSubscriptionChangedCount() {
-            return mSubscriptionChangedCount.get();
-        }
-        public int getDefaultSubscriptionChangedCount() {
-            return mDefaultSubscriptionChangedCount.get();
-        }
-    }
-
-    /**
-     * Register and unregister normally.
-     * Verify register worked by causing an event.
-     * Verify unregister by causing another event.
-     */
-    @SmallTest
-    public void testRegister() throws Exception {
-        final int numPhones = 2;
-        final ContextFixture contextFixture = new ContextFixture();
-        final Context context = contextFixture.getTestDouble();
-        ITelephonyRegistry.Stub telRegistry = new TelephonyRegistryMock();
-        SubscriptionControllerMock subController =
-                new SubscriptionControllerMock(context, telRegistry, numPhones);
-
-        SubscriptionMonitor testedSubMonitor =
-                new SubscriptionMonitor(telRegistry, context, subController, numPhones);
-
-        TestHandler testHandler = TestHandler.makeHandler();
-        Object subChangedObject = new Object();
-        testHandler.setSubscriptionChangedObject(subChangedObject);
-
-        Object defaultSubChangedObject = new Object();
-        testHandler.setDefaultSubscriptionChangedObject(defaultSubChangedObject);
-
-        // try events before registering
-        subController.setDefaultDataSubId(0);
-        subController.setSlotSubId(0, 0);
-
-        if (testHandler.getSubscriptionChangedCount() != 0) {
-            fail("pretest of SubscriptionChangedCount");
-        }
-        if (testHandler.getDefaultSubscriptionChangedCount() != 0) {
-            fail("pretest of DefaultSubscriptionChangedCount");
-        }
-
-        testedSubMonitor.registerForSubscriptionChanged(0, testHandler,
-                  TestHandler.SUBSCRIPTION_CHANGED, subChangedObject);
-        testHandler.blockTilIdle();
-
-        if (testHandler.getSubscriptionChangedCount() != 1) {
-            fail("test1 of SubscriptionChangedCount");
-        }
-        if (testHandler.getDefaultSubscriptionChangedCount() != 0) {
-            fail("test1 of DefaultSubscriptionChangedCount");
-        }
-
-        testedSubMonitor.registerForDefaultDataSubscriptionChanged(0, testHandler,
-                TestHandler.DEFAULT_SUBSCRIPTION_CHANGED, defaultSubChangedObject);
-        testHandler.blockTilIdle();
-
-        if (testHandler.getSubscriptionChangedCount() != 1) {
-            fail("test2 of SubscriptionChangedCount");
-        }
-        if (testHandler.getDefaultSubscriptionChangedCount() != 1) {
-            fail("test2 of DefaultSubscriptionChangedCount");
-        }
-
-        subController.setDefaultDataSubId(1);
-        testHandler.blockTilIdle();
-
-        if (testHandler.getSubscriptionChangedCount() != 1) {
-            fail("test3 of SubscriptionChangedCount, " +
-                    testHandler.getSubscriptionChangedCount() + " vs 1");
-        }
-        if (testHandler.getDefaultSubscriptionChangedCount() != 2) {
-            fail("test3 of DefaultSubscriptionChangedCount, " +
-                    testHandler.getDefaultSubscriptionChangedCount() + " vs 2");
-        }
-
-        subController.setSlotSubId(0, 1);
-        testHandler.blockTilIdle();
-
-        if (testHandler.getSubscriptionChangedCount() != 2) {
-            fail("test4 of SubscriptionChangedCount");
-        }
-        if (testHandler.getDefaultSubscriptionChangedCount() != 3) {
-            fail("test4 of DefaultSubscriptionChangedCount");
-        }
-
-        testedSubMonitor.unregisterForDefaultDataSubscriptionChanged(0, testHandler);
-        subController.setSlotSubId(0, 0);
-        testHandler.blockTilIdle();
-
-        if (testHandler.getSubscriptionChangedCount() != 3) {
-            fail("test5 of SubscriptionChangedCount, " +
-                    testHandler.getSubscriptionChangedCount() + " vs 3");
-        }
-        if (testHandler.getDefaultSubscriptionChangedCount() != 3) {
-            fail("test5 of DefaultSubscriptionChangedCount, " +
-                    testHandler.getDefaultSubscriptionChangedCount() + " vs 3");
-        }
-
-        testedSubMonitor.unregisterForSubscriptionChanged(0, testHandler);
-
-        subController.setSlotSubId(0, 1);
-        subController.setDefaultDataSubId(0);
-        testHandler.blockTilIdle();
-
-        if (testHandler.getSubscriptionChangedCount() != 3) {
-            fail("test6 of SubscriptionChangedCount, " +
-                    testHandler.getSubscriptionChangedCount() + " vs 3");
-        }
-        if (testHandler.getDefaultSubscriptionChangedCount() != 3) {
-            fail("test6 of DefaultSubscriptionChangedCount, " +
-                    testHandler.getDefaultSubscriptionChangedCount() + " vs 3");
-        }
-
-        testHandler.die();
-    }
-
-    /**
-     * Bad register/unregisters
-     *
-     * Try phoneId that doesn't exist.
-     * Cause an event and verify don't get notified.
-     * Try to unregister multiple times.
-     */
-    @SmallTest
-    public void testBadRegister() throws Exception {
-        final int numPhones = 2;
-        final ContextFixture contextFixture = new ContextFixture();
-        final Context context = contextFixture.getTestDouble();
-        ITelephonyRegistry.Stub telRegistry = new TelephonyRegistryMock();
-        SubscriptionControllerMock subController =
-                new SubscriptionControllerMock(context, telRegistry, numPhones);
-
-        SubscriptionMonitor testedSubMonitor =
-                new SubscriptionMonitor(telRegistry, context, subController, numPhones);
-
-        TestHandler testHandler = TestHandler.makeHandler();
-        Object subChangedObject = new Object();
-        testHandler.setSubscriptionChangedObject(subChangedObject);
-
-        Object defaultSubChangedObject = new Object();
-        testHandler.setDefaultSubscriptionChangedObject(defaultSubChangedObject);
-
-        try {
-            testedSubMonitor.registerForSubscriptionChanged(-1, testHandler,
-                      TestHandler.SUBSCRIPTION_CHANGED, subChangedObject);
-            fail("IllegalArgumentException expected with bad phoneId");
-        } catch (IllegalArgumentException e) {}
-        try {
-            testedSubMonitor.registerForDefaultDataSubscriptionChanged(-1, testHandler,
-                    TestHandler.DEFAULT_SUBSCRIPTION_CHANGED, defaultSubChangedObject);
-            fail("IllegalArgumentException expected with bad phoneId");
-        } catch (IllegalArgumentException e) {}
-        try {
-            testedSubMonitor.registerForSubscriptionChanged(numPhones, testHandler,
-                      TestHandler.SUBSCRIPTION_CHANGED, subChangedObject);
-            fail("IllegalArgumentException expected with bad phoneId");
-        } catch (IllegalArgumentException e) {}
-        try {
-            testedSubMonitor.registerForDefaultDataSubscriptionChanged(numPhones, testHandler,
-                    TestHandler.DEFAULT_SUBSCRIPTION_CHANGED, defaultSubChangedObject);
-            fail("IllegalArgumentException expected with bad phoneId");
-        } catch (IllegalArgumentException e) {}
-
-        subController.setDefaultDataSubId(0);
-        subController.setSlotSubId(0, 0);
-
-        if (testHandler.getSubscriptionChangedCount() != 0) {
-            fail("getSubscriptionChangedCount reported non-zero!");
-        }
-        if (testHandler.getDefaultSubscriptionChangedCount() != 0) {
-            fail("getDefaultSubscriptionChangedCount reported non-zero!");
-        }
-
-        testHandler.die();
-    }
-
-    /**
-     * Try to force spurious notifications - register/unregister in tight loop with
-     * events happening in the unregistered gap.
-     */
-    @SmallTest
-    public void testSpuriousNotifications() throws Exception {
-        final int numPhones = 2;
-        final ContextFixture contextFixture = new ContextFixture();
-        final Context context = contextFixture.getTestDouble();
-        ITelephonyRegistry.Stub telRegistry = new TelephonyRegistryMock();
-        SubscriptionControllerMock subController =
-                new SubscriptionControllerMock(context, telRegistry, numPhones);
-
-        SubscriptionMonitor testedSubMonitor =
-                new SubscriptionMonitor(telRegistry, context, subController, numPhones);
-
-        TestHandler testHandler = TestHandler.makeHandler();
-        Object subChangedObject = new Object();
-        testHandler.setSubscriptionChangedObject(subChangedObject);
-
-        Object defaultSubChangedObject = new Object();
-        testHandler.setDefaultSubscriptionChangedObject(defaultSubChangedObject);
-
-        final int PHONE_ID = 0;
-        final int FIRST_SUB_ID = 0;
-        final int SECOND_SUB_ID = 1;
-
-        testedSubMonitor.registerForSubscriptionChanged(PHONE_ID, testHandler,
-                TestHandler.SUBSCRIPTION_CHANGED, subChangedObject);
-        testedSubMonitor.registerForDefaultDataSubscriptionChanged(PHONE_ID, testHandler,
-                TestHandler.DEFAULT_SUBSCRIPTION_CHANGED, defaultSubChangedObject);
-        final int LOOP_COUNT = 1000;
-        for (int i = 0; i < LOOP_COUNT; i++) {
-            testedSubMonitor.unregisterForSubscriptionChanged(PHONE_ID, testHandler);
-            testedSubMonitor.unregisterForDefaultDataSubscriptionChanged(PHONE_ID, testHandler);
-
-            subController.setDefaultDataSubId(FIRST_SUB_ID);
-            subController.setSlotSubId(PHONE_ID, FIRST_SUB_ID);
-
-            subController.setDefaultDataSubId(SECOND_SUB_ID);
-            subController.setSlotSubId(PHONE_ID, SECOND_SUB_ID);
-
-            testedSubMonitor.registerForSubscriptionChanged(PHONE_ID, testHandler,
-                    TestHandler.SUBSCRIPTION_CHANGED, subChangedObject);
-            testedSubMonitor.registerForDefaultDataSubscriptionChanged(PHONE_ID, testHandler,
-                    TestHandler.DEFAULT_SUBSCRIPTION_CHANGED, defaultSubChangedObject);
-        }
-        testHandler.blockTilIdle();
-
-        // should get one for every registration
-        if (testHandler.getSubscriptionChangedCount() != 1 + LOOP_COUNT) {
-            fail("getSubscriptionChangedCount reported " +
-                    testHandler.getSubscriptionChangedCount() + " != " + (1 + LOOP_COUNT));
-        }
-        if (testHandler.getDefaultSubscriptionChangedCount() != 1 + LOOP_COUNT) {
-            fail("getDefaultSubscriptionChangedCount reported " +
-                    testHandler.getDefaultSubscriptionChangedCount() + " != " + (1 + LOOP_COUNT));
-        }
-
-        testHandler.die();
-    }
-
-    /**
-     * Test duplicate registrations - both should survive
-     * Also test duplicate unreg - shouldn't crash..
-     */
-    @SmallTest
-    public void testMultiRegUnregistration() throws Exception {
-        final int numPhones = 2;
-        final ContextFixture contextFixture = new ContextFixture();
-        final Context context = contextFixture.getTestDouble();
-        ITelephonyRegistry.Stub telRegistry = new TelephonyRegistryMock();
-        SubscriptionControllerMock subController =
-                new SubscriptionControllerMock(context, telRegistry, numPhones);
-
-        SubscriptionMonitor testedSubMonitor =
-                new SubscriptionMonitor(telRegistry, context, subController, numPhones);
-
-        TestHandler testHandler = TestHandler.makeHandler();
-        Object subChangedObject = new Object();
-        testHandler.setSubscriptionChangedObject(subChangedObject);
-
-        Object defaultSubChangedObject = new Object();
-        testHandler.setDefaultSubscriptionChangedObject(defaultSubChangedObject);
-
-        final int PHONE_ID = 0;
-        final int FIRST_SUB_ID = 0;
-        final int SECOND_SUB_ID = 1;
-
-        testedSubMonitor.registerForSubscriptionChanged(PHONE_ID, testHandler,
-                TestHandler.SUBSCRIPTION_CHANGED, subChangedObject);
-        testedSubMonitor.registerForDefaultDataSubscriptionChanged(PHONE_ID, testHandler,
-                TestHandler.DEFAULT_SUBSCRIPTION_CHANGED, defaultSubChangedObject);
-
-        testedSubMonitor.registerForSubscriptionChanged(PHONE_ID, testHandler,
-                TestHandler.SUBSCRIPTION_CHANGED, subChangedObject);
-        testedSubMonitor.registerForDefaultDataSubscriptionChanged(PHONE_ID, testHandler,
-                TestHandler.DEFAULT_SUBSCRIPTION_CHANGED, defaultSubChangedObject);
-
-        subController.setDefaultDataSubId(FIRST_SUB_ID);
-        subController.setSlotSubId(PHONE_ID, FIRST_SUB_ID);
-
-        subController.setDefaultDataSubId(SECOND_SUB_ID);
-        subController.setSlotSubId(PHONE_ID, SECOND_SUB_ID);
-
-        testHandler.blockTilIdle();
-
-        // should get 1 for each registration and 4 for the two events
-        if (testHandler.getSubscriptionChangedCount() != 6) {
-            fail("getSubscriptionChangedCount reported " +
-                    testHandler.getSubscriptionChangedCount() + " != 6");
-        }
-        // 2 for the 2 registrations, 2 for the single event in the first cluster (2 listeners)
-        // 2 for the setDefatulDataSub in the second cluster (lost data sub)
-        // 2 for the setSlotSubId (regain default)
-        if (testHandler.getDefaultSubscriptionChangedCount() != 8) {
-            fail("getDefaultSubscriptionChangedCount reported " +
-                    testHandler.getDefaultSubscriptionChangedCount() + " != 8");
-        }
-
-        testedSubMonitor.unregisterForSubscriptionChanged(PHONE_ID, testHandler);
-        testedSubMonitor.unregisterForDefaultDataSubscriptionChanged(PHONE_ID, testHandler);
-        testedSubMonitor.unregisterForSubscriptionChanged(PHONE_ID, testHandler);
-        testedSubMonitor.unregisterForDefaultDataSubscriptionChanged(PHONE_ID, testHandler);
-
-        testHandler.die();
-    }
-
-    /**
-     * Try event flood while registered - verify receive all.
-     */
-    @SmallTest
-    public void testEventFloodNotifications() throws Exception {
-        final int numPhones = 2;
-        final ContextFixture contextFixture = new ContextFixture();
-        final Context context = contextFixture.getTestDouble();
-        ITelephonyRegistry.Stub telRegistry = new TelephonyRegistryMock();
-        SubscriptionControllerMock subController =
-                new SubscriptionControllerMock(context, telRegistry, numPhones);
-
-        SubscriptionMonitor testedSubMonitor =
-                new SubscriptionMonitor(telRegistry, context, subController, numPhones);
-
-        TestHandler testHandler = TestHandler.makeHandler();
-        Object subChangedObject = new Object();
-        testHandler.setSubscriptionChangedObject(subChangedObject);
-
-        Object defaultSubChangedObject = new Object();
-        testHandler.setDefaultSubscriptionChangedObject(defaultSubChangedObject);
-
-        final int PHONE_ID = 0;
-        final int FIRST_SUB_ID = 0;
-        final int SECOND_SUB_ID = 1;
-
-        testedSubMonitor.registerForSubscriptionChanged(PHONE_ID, testHandler,
-                TestHandler.SUBSCRIPTION_CHANGED, subChangedObject);
-        testedSubMonitor.registerForDefaultDataSubscriptionChanged(PHONE_ID, testHandler,
-                TestHandler.DEFAULT_SUBSCRIPTION_CHANGED, defaultSubChangedObject);
-
-        final int LOOP_COUNT = 1;
-        for (int i = 0; i < LOOP_COUNT; i++) {
-            subController.setDefaultDataSubId(FIRST_SUB_ID);
-            subController.setSlotSubId(PHONE_ID, FIRST_SUB_ID);
-
-            subController.setDefaultDataSubId(SECOND_SUB_ID);
-            subController.setSlotSubId(PHONE_ID, SECOND_SUB_ID);
-        }
-        testHandler.blockTilIdle();
-
-        // should get one for registration + 2 per loop
-        if (testHandler.getSubscriptionChangedCount() != 1 + (2 * LOOP_COUNT)) {
-            fail("getSubscriptionChangedCount reported " +
-                    testHandler.getSubscriptionChangedCount() + " != " + (1 + (2 * LOOP_COUNT)));
-        }
-        // should get one for registration + 3 for first loop + 4 for subsequent loops
-        if (testHandler.getDefaultSubscriptionChangedCount() != (4 * LOOP_COUNT)) {
-            fail("getDefaultSubscriptionChangedCount reported " +
-                    testHandler.getDefaultSubscriptionChangedCount() + " != " +
-                    (4 * LOOP_COUNT));
-        }
-
-        testHandler.die();
-    }
-
-    /**
-     * Try tests with no default set
-     */
-    @SmallTest
-    public void testNoDefaultNotifications() throws Exception {
-        final int numPhones = 2;
-        final ContextFixture contextFixture = new ContextFixture();
-        final Context context = contextFixture.getTestDouble();
-        ITelephonyRegistry.Stub telRegistry = new TelephonyRegistryMock();
-        SubscriptionControllerMock subController =
-                new SubscriptionControllerMock(context, telRegistry, numPhones);
-
-        SubscriptionMonitor testedSubMonitor =
-                new SubscriptionMonitor(telRegistry, context, subController, numPhones);
-
-        TestHandler testHandler = TestHandler.makeHandler();
-        Object subChangedObject = new Object();
-        testHandler.setSubscriptionChangedObject(subChangedObject);
-
-        Object defaultSubChangedObject = new Object();
-        testHandler.setDefaultSubscriptionChangedObject(defaultSubChangedObject);
-
-        final int PHONE_ID = 0;
-        final int FIRST_SUB_ID = 0;
-        final int SECOND_SUB_ID = 1;
-
-        subController.setDefaultDataSubId(INVALID_SUBSCRIPTION_ID);
-        subController.setSlotSubId(PHONE_ID, FIRST_SUB_ID);
-
-        testedSubMonitor.registerForSubscriptionChanged(PHONE_ID, testHandler,
-                TestHandler.SUBSCRIPTION_CHANGED, subChangedObject);
-        testedSubMonitor.registerForDefaultDataSubscriptionChanged(PHONE_ID, testHandler,
-                TestHandler.DEFAULT_SUBSCRIPTION_CHANGED, defaultSubChangedObject);
-
-
-        subController.setSlotSubId(PHONE_ID, SECOND_SUB_ID);
-        subController.setSlotSubId(PHONE_ID, FIRST_SUB_ID);
-
-        testHandler.blockTilIdle();
-
-        if (testHandler.getSubscriptionChangedCount() != 3) {
-            fail("getSubscriptionChangedCount reported " +
-                    testHandler.getSubscriptionChangedCount() + " != 3");
-        }
-        if (testHandler.getDefaultSubscriptionChangedCount() != 1) {
-            fail("getDefaultSubscriptionChangedCount reported " +
-                    testHandler.getDefaultSubscriptionChangedCount() + " != 1");
-        }
-
-        testHandler.die();
-    }
-
-    @SmallTest
-    public void testNoSubChange() throws Exception {
-        String TAG = "testNoSubChange";
-        final int numPhones = 2;
-        final ContextFixture contextFixture = new ContextFixture();
-        final Context context = contextFixture.getTestDouble();
-        ITelephonyRegistry.Stub telRegistry = new TelephonyRegistryMock();
-        SubscriptionControllerMock subController =
-                new SubscriptionControllerMock(context, telRegistry, numPhones);
-
-        SubscriptionMonitor testedSubMonitor =
-                new SubscriptionMonitor(telRegistry, context, subController, numPhones);
-
-        TestHandler testHandler = TestHandler.makeHandler();
-        Object subChangedObject = new Object();
-        testHandler.setSubscriptionChangedObject(subChangedObject);
-
-        Object defaultSubChangedObject = new Object();
-        testHandler.setDefaultSubscriptionChangedObject(defaultSubChangedObject);
-
-        final int PHONE_ID = 0;
-        final int FIRST_SUB_ID = 0;
-        final int SECOND_SUB_ID = 1;
-
-        testHandler.blockTilIdle();
-        Rlog.d(TAG, "1");
-
-        testedSubMonitor.registerForSubscriptionChanged(PHONE_ID, testHandler,
-                TestHandler.SUBSCRIPTION_CHANGED, subChangedObject);
-
-        testHandler.blockTilIdle();
-        Rlog.d(TAG, "2");
-
-        testedSubMonitor.registerForDefaultDataSubscriptionChanged(PHONE_ID, testHandler,
-                TestHandler.DEFAULT_SUBSCRIPTION_CHANGED, defaultSubChangedObject);
-
-        testHandler.blockTilIdle();
-        Rlog.d(TAG, "3");
-
-        subController.setSlotSubId(PHONE_ID, FIRST_SUB_ID);
-
-        testHandler.blockTilIdle();
-        Rlog.d(TAG, "4");
-
-        subController.setDefaultDataSubId(FIRST_SUB_ID);
-
-        testHandler.blockTilIdle();
-        Rlog.d(TAG, "5");
-
-        if (testHandler.getSubscriptionChangedCount() != 2) {
-            fail("getSubscriptionChangedCount reported " +
-                    testHandler.getSubscriptionChangedCount() + " != 2");
-        }
-        // 1 gained for reg  and 1 for the setting above
-        if (testHandler.getDefaultSubscriptionChangedCount() != 2) {
-            fail("getDefaultSubscriptionChangedCount reported " +
-                    testHandler.getDefaultSubscriptionChangedCount() + " != 2");
-        }
-
-        Rlog.d(TAG, "6");
-
-        // cause a notification that subscription info changed
-        subController.notifySubscriptionInfoChanged();
-        testHandler.blockTilIdle();
-
-        Rlog.d(TAG, "7");
-
-        if (testHandler.getSubscriptionChangedCount() != 2) {
-            fail("getSubscriptionChangedCount reported " +
-                    testHandler.getSubscriptionChangedCount() + " != 2");
-        }
-        if (testHandler.getDefaultSubscriptionChangedCount() != 2) {
-            fail("getDefaultSubscriptionChangedCount reported " +
-                    testHandler.getDefaultSubscriptionChangedCount() + " != 2");
-        }
-
-        // now change the default - should cause a default notification (we lost the default)
-        subController.setDefaultDataSubId(SECOND_SUB_ID);
-
-        testHandler.blockTilIdle();
-        Rlog.d(TAG, "8");
-
-        if (testHandler.getSubscriptionChangedCount() != 2) {
-            fail("getSubscriptionChangedCount reported " +
-                    testHandler.getSubscriptionChangedCount() + " != 2");
-        }
-        if (testHandler.getDefaultSubscriptionChangedCount() != 3) {
-            fail("getDefaultSubscriptionChangedCount reported " +
-                    testHandler.getDefaultSubscriptionChangedCount() + " != 3");
-        }
-        testHandler.die();
-    }
-
-    /**
-     * Try setting the subIds first and then the default subId and verify we get all our
-     * notifications.
-     */
-    @SmallTest
-    public void testSubBeforeDefaultNotifications() throws Exception {
-        final int numPhones = 2;
-        final ContextFixture contextFixture = new ContextFixture();
-        final Context context = contextFixture.getTestDouble();
-        ITelephonyRegistry.Stub telRegistry = new TelephonyRegistryMock();
-        SubscriptionControllerMock subController =
-                new SubscriptionControllerMock(context, telRegistry, numPhones);
-
-        SubscriptionMonitor testedSubMonitor =
-                new SubscriptionMonitor(telRegistry, context, subController, numPhones);
-
-        TestHandler testHandler = TestHandler.makeHandler();
-        Object subChangedObject = new Object();
-        testHandler.setSubscriptionChangedObject(subChangedObject);
-
-        Object defaultSubChangedObject = new Object();
-        testHandler.setDefaultSubscriptionChangedObject(defaultSubChangedObject);
-
-        final int PHONE_ID = 0;
-        final int SECOND_PHONE_ID = 1;
-        final int FIRST_SUB_ID = 0;
-        final int SECOND_SUB_ID = 1;
-        testedSubMonitor.registerForSubscriptionChanged(PHONE_ID, testHandler,
-                TestHandler.SUBSCRIPTION_CHANGED, subChangedObject);
-        testedSubMonitor.registerForDefaultDataSubscriptionChanged(PHONE_ID, testHandler,
-                TestHandler.DEFAULT_SUBSCRIPTION_CHANGED, defaultSubChangedObject);
-        subController.setSlotSubId(PHONE_ID, -2);
-        subController.setSlotSubId(SECOND_PHONE_ID, -3);
-        testHandler.blockTilIdle();
-        // should get one for registration and 1 for the change
-        if (testHandler.getSubscriptionChangedCount() != 2) {
-            fail("test1 " + testHandler.getSubscriptionChangedCount() + " != 2");
-        }
-        // should get one for registration
-        if (testHandler.getDefaultSubscriptionChangedCount() != 1) {
-            fail("test2 " + testHandler.getDefaultSubscriptionChangedCount() + " != 1");
-        }
-
-        subController.setDefaultDataSubId(FIRST_SUB_ID);
-        testHandler.blockTilIdle();
-
-        // no change
-        if (testHandler.getSubscriptionChangedCount() != 2) {
-            fail("test3 " + testHandler.getSubscriptionChangedCount() + " != 2");
-        }
-        if (testHandler.getDefaultSubscriptionChangedCount() != 1) {
-            fail("test4 " + testHandler.getDefaultSubscriptionChangedCount() + " != 1");
-        }
-
-        subController.setSlotSubId(PHONE_ID, FIRST_SUB_ID);
-        testHandler.blockTilIdle();
-
-        // should get one more default-change-notification
-        if (testHandler.getSubscriptionChangedCount() != 3) {
-            fail("test5 " + testHandler.getSubscriptionChangedCount() + " != 3");
-        }
-        if (testHandler.getDefaultSubscriptionChangedCount() != 2) {
-            fail("test6 " + testHandler.getDefaultSubscriptionChangedCount() + " != 2");
-        }
-
-        subController.setDefaultDataSubId(SECOND_SUB_ID);
-        testHandler.blockTilIdle();
-
-        // should get one more default-change-notification
-        if (testHandler.getSubscriptionChangedCount() != 3) {
-            fail("test7 " + testHandler.getSubscriptionChangedCount() + " != 3");
-        }
-        if (testHandler.getDefaultSubscriptionChangedCount() != 3) {
-            fail("test8 " + testHandler.getDefaultSubscriptionChangedCount() + " != 3");
-        }
-
-        subController.setDefaultDataSubId(FIRST_SUB_ID);
-        testHandler.blockTilIdle();
-
-        // should get one more default-change-notification
-        if (testHandler.getSubscriptionChangedCount() != 3) {
-            fail("test9 " + testHandler.getSubscriptionChangedCount() + " != 3");
-        }
-        if (testHandler.getDefaultSubscriptionChangedCount() != 4) {
-            fail("test10 " + testHandler.getDefaultSubscriptionChangedCount() + " != 4");
-        }
-
-        testHandler.die();
-    }
-
-    /**
-     * It turns out when we swap sims on a single sim we do something like:
-     *   Phone[0] subId  1 -> -2
-     *   Phone[0] subId -2 ->  2
-     *   Default change  1 ->  2
-     * Try that and verify we get all the subId and default changes we expect.
-     */
-    @SmallTest
-    public void testSimSwapNotifications() throws Exception {
-        final int numPhones = 1;
-        final ContextFixture contextFixture = new ContextFixture();
-        final Context context = contextFixture.getTestDouble();
-        ITelephonyRegistry.Stub telRegistry = new TelephonyRegistryMock();
-        SubscriptionControllerMock subController =
-                new SubscriptionControllerMock(context, telRegistry, numPhones);
-
-        SubscriptionMonitor testedSubMonitor =
-                new SubscriptionMonitor(telRegistry, context, subController, numPhones);
-
-        TestHandler testHandler = TestHandler.makeHandler();
-        Object subChangedObject = new Object();
-        testHandler.setSubscriptionChangedObject(subChangedObject);
-
-        Object defaultSubChangedObject = new Object();
-        testHandler.setDefaultSubscriptionChangedObject(defaultSubChangedObject);
-
-        final int PHONE_ID = 0;
-        final int FIRST_SUB_ID = 0;
-        final int SECOND_SUB_ID = 1;
-        testedSubMonitor.registerForSubscriptionChanged(PHONE_ID, testHandler,
-                TestHandler.SUBSCRIPTION_CHANGED, subChangedObject);
-        testedSubMonitor.registerForDefaultDataSubscriptionChanged(PHONE_ID, testHandler,
-                TestHandler.DEFAULT_SUBSCRIPTION_CHANGED, defaultSubChangedObject);
-        subController.setSlotSubId(PHONE_ID, -2);
-        testHandler.blockTilIdle();
-        // should get one for registration and 1 for the change
-        if (testHandler.getSubscriptionChangedCount() != 2) {
-            fail("test1 " + testHandler.getSubscriptionChangedCount() + " != 2");
-        }
-        // should get one for registration
-        if (testHandler.getDefaultSubscriptionChangedCount() != 1) {
-            fail("test2 " + testHandler.getDefaultSubscriptionChangedCount() + " != 1");
-        }
-
-        subController.setSlotSubId(PHONE_ID, FIRST_SUB_ID);
-        testHandler.blockTilIdle();
-        if (testHandler.getSubscriptionChangedCount() != 3) {
-            fail("test3 " + testHandler.getSubscriptionChangedCount() + " != 3");
-        }
-
-        subController.setDefaultDataSubId(FIRST_SUB_ID);
-        testHandler.blockTilIdle();
-        if (testHandler.getDefaultSubscriptionChangedCount() != 2) {
-            fail("test4 " + testHandler.getDefaultSubscriptionChangedCount() + " != 2");
-        }
-
-        // ok - now for the sim swap
-        subController.setSlotSubId(PHONE_ID, -2);
-        testHandler.blockTilIdle();
-        if (testHandler.getDefaultSubscriptionChangedCount() != 3) {
-            fail("test5 " + testHandler.getDefaultSubscriptionChangedCount() + " != 3");
-        }
-        if (testHandler.getSubscriptionChangedCount() != 4) {
-            fail("test6 " + testHandler.getSubscriptionChangedCount() + " != 4");
-        }
-
-        subController.setSlotSubId(PHONE_ID, SECOND_SUB_ID);
-        testHandler.blockTilIdle();
-
-        if (testHandler.getSubscriptionChangedCount() != 5) {
-            fail("test7 " + testHandler.getSubscriptionChangedCount() + " != 5");
-        }
-
-        subController.setDefaultDataSubId(SECOND_SUB_ID);
-        testHandler.blockTilIdle();
-
-        if (testHandler.getDefaultSubscriptionChangedCount() != 4) {
-            fail("test8 " + testHandler.getDefaultSubscriptionChangedCount() + " != 4");
-        }
-        // no change
-        if (testHandler.getSubscriptionChangedCount() != 5) {
-            fail("test9 " + testHandler.getSubscriptionChangedCount() + " != 5");
-        }
-
-        testHandler.die();
-    }
-}
diff --git a/tests/telephonytests/src/com/android/internal/telephony/TelephonyPermissionsTest.java b/tests/telephonytests/src/com/android/internal/telephony/TelephonyPermissionsTest.java
index d94863c..dbaa29a 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/TelephonyPermissionsTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/TelephonyPermissionsTest.java
@@ -18,16 +18,16 @@
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
-import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.ArgumentMatchers.nullable;
 import static org.mockito.Mockito.doNothing;
 import static org.mockito.Mockito.doThrow;
 import static org.mockito.Mockito.when;
 
 import android.app.AppOpsManager;
-import android.app.admin.DevicePolicyManager;
 import android.content.Context;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager;
@@ -36,6 +36,7 @@
 import android.os.Bundle;
 import android.os.IBinder;
 import android.os.ServiceManager;
+import android.permission.PermissionManager;
 import android.provider.DeviceConfig;
 import android.provider.Settings;
 import android.telephony.SubscriptionManager;
@@ -45,6 +46,7 @@
 import android.test.suitebuilder.annotation.SmallTest;
 
 import com.android.internal.util.test.FakeSettingsProvider;
+import com.android.server.pm.permission.PermissionManagerService;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -62,6 +64,7 @@
     private static final int PID = Binder.getCallingPid();
     private static final int UID = Binder.getCallingUid();
     private static final String PACKAGE = "com.example";
+    private static final String FEATURE = "com.example.feature";
     private static final String MSG = "message";
 
     @Mock
@@ -79,7 +82,13 @@
     @Mock
     private ApplicationInfo mMockApplicationInfo;
     @Mock
-    private DevicePolicyManager mMockDevicePolicyManager;
+    private TelephonyManager mTelephonyManagerMock;
+    @Mock
+    private TelephonyManager mTelephonyManagerMockForSub1;
+    @Mock
+    private TelephonyManager mTelephonyManagerMockForSub2;
+    @Mock
+    private PermissionManagerService mMockPermissionManagerService;
 
     private MockContentResolver mMockContentResolver;
     private FakeSettingsConfigProvider mFakeSettingsConfigProvider;
@@ -87,31 +96,37 @@
     @Before
     public void setUp() throws Exception {
         MockitoAnnotations.initMocks(this);
+
+        when(mMockContext.getSystemService(Context.TELEPHONY_SERVICE)).thenReturn(
+                mTelephonyManagerMock);
+        when(mTelephonyManagerMock.createForSubscriptionId(anyInt())).thenReturn(
+                mTelephonyManagerMock);
         when(mMockContext.getSystemService(Context.APP_OPS_SERVICE)).thenReturn(mMockAppOps);
         when(mMockContext.getSystemService(Context.TELEPHONY_SUBSCRIPTION_SERVICE)).thenReturn(
                 mMockSubscriptionManager);
-        when(mMockContext.getSystemService(Context.DEVICE_POLICY_SERVICE)).thenReturn(
-                mMockDevicePolicyManager);
-        when(mMockSubscriptionManager.getActiveSubscriptionIdList(anyBoolean())).thenReturn(
+        when(mMockSubscriptionManager.getCompleteActiveSubscriptionIdList()).thenReturn(
                 new int[]{SUB_ID});
 
+        PermissionManager permissionManager = new PermissionManager(mMockContext, null,
+                mMockPermissionManagerService);
+        when(mMockContext.getSystemService(Context.PERMISSION_SERVICE)).thenReturn(
+                permissionManager);
+
         // By default, assume we have no permissions or app-ops bits.
         doThrow(new SecurityException()).when(mMockContext)
                 .enforcePermission(anyString(), eq(PID), eq(UID), eq(MSG));
         doThrow(new SecurityException()).when(mMockContext)
                 .enforcePermission(anyString(), eq(PID), eq(UID), eq(MSG));
-        when(mMockAppOps.noteOp(anyInt(), eq(UID), eq(PACKAGE)))
-                .thenReturn(AppOpsManager.MODE_ERRORED);
-        when(mMockAppOps.noteOpNoThrow(anyString(), eq(UID), eq(PACKAGE))).thenReturn(
-                AppOpsManager.MODE_ERRORED);
+        when(mMockAppOps.noteOp(anyString(), eq(UID), eq(PACKAGE), eq(FEATURE),
+                nullable(String.class))).thenReturn(AppOpsManager.MODE_ERRORED);
+        when(mMockAppOps.noteOpNoThrow(anyString(), eq(UID), eq(PACKAGE), eq(FEATURE),
+                nullable(String.class))).thenReturn(AppOpsManager.MODE_ERRORED);
         when(mMockTelephony.getCarrierPrivilegeStatusForUid(eq(SUB_ID), eq(UID)))
                 .thenReturn(TelephonyManager.CARRIER_PRIVILEGE_STATUS_NO_ACCESS);
         when(mMockTelephony.getCarrierPrivilegeStatusForUid(eq(SUB_ID_2), eq(UID)))
                 .thenReturn(TelephonyManager.CARRIER_PRIVILEGE_STATUS_NO_ACCESS);
         when(mMockContext.checkPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE,
                 PID, UID)).thenReturn(PackageManager.PERMISSION_DENIED);
-        when(mMockDevicePolicyManager.checkDeviceIdentifierAccess(eq(PACKAGE), eq(PID),
-                eq(UID))).thenReturn(false);
         setTelephonyMockAsService();
     }
 
@@ -119,7 +134,7 @@
     public void testCheckReadPhoneState_noPermissions() {
         try {
             TelephonyPermissions.checkReadPhoneState(
-                    mMockContext, () -> mMockTelephony, SUB_ID, PID, UID, PACKAGE, MSG);
+                    mMockContext, SUB_ID, PID, UID, PACKAGE, FEATURE, MSG);
             fail("Should have thrown SecurityException");
         } catch (SecurityException e) {
             // expected
@@ -131,17 +146,17 @@
         doNothing().when(mMockContext).enforcePermission(
                 android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE, PID, UID, MSG);
         assertTrue(TelephonyPermissions.checkReadPhoneState(
-                mMockContext, () -> mMockTelephony, SUB_ID, PID, UID, PACKAGE, MSG));
+                mMockContext, SUB_ID, PID, UID, PACKAGE, FEATURE, MSG));
     }
 
     @Test
     public void testCheckReadPhoneState_hasPermissionAndAppOp() {
         doNothing().when(mMockContext).enforcePermission(
                 android.Manifest.permission.READ_PHONE_STATE, PID, UID, MSG);
-        when(mMockAppOps.noteOp(AppOpsManager.OP_READ_PHONE_STATE, UID, PACKAGE))
-                .thenReturn(AppOpsManager.MODE_ALLOWED);
+        when(mMockAppOps.noteOp(eq(AppOpsManager.OPSTR_READ_PHONE_STATE), eq(UID), eq(PACKAGE),
+                eq(FEATURE), nullable(String.class))).thenReturn(AppOpsManager.MODE_ALLOWED);
         assertTrue(TelephonyPermissions.checkReadPhoneState(
-                mMockContext, () -> mMockTelephony, SUB_ID, PID, UID, PACKAGE, MSG));
+                mMockContext, SUB_ID, PID, UID, PACKAGE, FEATURE, MSG));
     }
 
     @Test
@@ -149,21 +164,23 @@
         doNothing().when(mMockContext).enforcePermission(
                 android.Manifest.permission.READ_PHONE_STATE, PID, UID, MSG);
         assertFalse(TelephonyPermissions.checkReadPhoneState(
-                mMockContext, () -> mMockTelephony, SUB_ID, PID, UID, PACKAGE, MSG));
+                mMockContext, SUB_ID, PID, UID, PACKAGE, FEATURE, MSG));
     }
 
     @Test
     public void testCheckReadPhoneState_hasCarrierPrivileges() throws Exception {
-        when(mMockTelephony.getCarrierPrivilegeStatusForUid(eq(SUB_ID), eq(UID)))
-                .thenReturn(TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS);
+        when(mTelephonyManagerMock.createForSubscriptionId(eq(SUB_ID))).thenReturn(
+                mTelephonyManagerMockForSub1);
+        when(mTelephonyManagerMockForSub1.getCarrierPrivilegeStatus(anyInt())).thenReturn(
+                TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS);
         assertTrue(TelephonyPermissions.checkReadPhoneState(
-                mMockContext, () -> mMockTelephony, SUB_ID, PID, UID, PACKAGE, MSG));
+                mMockContext, SUB_ID, PID, UID, PACKAGE, FEATURE, MSG));
     }
 
     @Test
     public void testCheckReadPhoneStateOnAnyActiveSub_noPermissions() {
         assertFalse(TelephonyPermissions.checkReadPhoneStateOnAnyActiveSub(
-                mMockContext, () -> mMockTelephony, PID, UID, PACKAGE, MSG));
+                mMockContext, PID, UID, PACKAGE, FEATURE, MSG));
     }
 
     @Test
@@ -171,17 +188,17 @@
         doNothing().when(mMockContext).enforcePermission(
                 android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE, PID, UID, MSG);
         assertTrue(TelephonyPermissions.checkReadPhoneStateOnAnyActiveSub(
-                mMockContext, () -> mMockTelephony, PID, UID, PACKAGE, MSG));
+                mMockContext, PID, UID, PACKAGE, FEATURE, MSG));
     }
 
     @Test
     public void testCheckReadPhoneStateOnAnyActiveSub_hasPermissionAndAppOp() {
         doNothing().when(mMockContext).enforcePermission(
                 android.Manifest.permission.READ_PHONE_STATE, PID, UID, MSG);
-        when(mMockAppOps.noteOp(AppOpsManager.OP_READ_PHONE_STATE, UID, PACKAGE))
-                .thenReturn(AppOpsManager.MODE_ALLOWED);
+        when(mMockAppOps.noteOp(eq(AppOpsManager.OPSTR_READ_PHONE_STATE), eq(UID), eq(PACKAGE),
+                eq(FEATURE), nullable(String.class))).thenReturn(AppOpsManager.MODE_ALLOWED);
         assertTrue(TelephonyPermissions.checkReadPhoneStateOnAnyActiveSub(
-                mMockContext, () -> mMockTelephony, PID, UID, PACKAGE, MSG));
+                mMockContext, PID, UID, PACKAGE, FEATURE, MSG));
     }
 
     @Test
@@ -189,22 +206,26 @@
         doNothing().when(mMockContext).enforcePermission(
                 android.Manifest.permission.READ_PHONE_STATE, PID, UID, MSG);
         assertFalse(TelephonyPermissions.checkReadPhoneStateOnAnyActiveSub(
-                mMockContext, () -> mMockTelephony, PID, UID, PACKAGE, MSG));
+                mMockContext, PID, UID, PACKAGE, FEATURE, MSG));
     }
 
     @Test
     public void testCheckReadPhoneStateOnAnyActiveSub_hasCarrierPrivileges() throws Exception {
-        when(mMockTelephony.getCarrierPrivilegeStatusForUid(eq(SUB_ID), eq(UID)))
-                .thenReturn(TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS);
+        when(mTelephonyManagerMock.createForSubscriptionId(eq(SUB_ID))).thenReturn(
+                mTelephonyManagerMockForSub1);
+        when(mTelephonyManagerMockForSub1.getCarrierPrivilegeStatus(anyInt())).thenReturn(
+                TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS);
+
         assertTrue(TelephonyPermissions.checkReadPhoneStateOnAnyActiveSub(
-                mMockContext, () -> mMockTelephony, PID, UID, PACKAGE, MSG));
+                mMockContext, PID, UID, PACKAGE, FEATURE, MSG));
     }
 
     @Test
-    public void testCheckReadPhoneNumber_noPermissions() {
+    public void testCheckReadPhoneNumber_noPermissions() throws Exception {
+        setupMocksForDeviceIdentifiersErrorPath();
         try {
             TelephonyPermissions.checkReadPhoneNumber(
-                    mMockContext, () -> mMockTelephony, SUB_ID, PID, UID, PACKAGE, MSG);
+                    mMockContext, SUB_ID, PID, UID, PACKAGE, FEATURE, MSG);
             fail("Should have thrown SecurityException");
         } catch (SecurityException e) {
             // expected
@@ -212,39 +233,78 @@
     }
 
     @Test
-    public void testCheckReadPhoneNumber_defaultSmsApp() {
-        when(mMockAppOps.noteOp(AppOpsManager.OP_WRITE_SMS, UID, PACKAGE))
-                .thenReturn(AppOpsManager.MODE_ALLOWED);
+    public void testCheckReadPhoneNumber_defaultSmsApp() throws Exception {
+        setupMocksForDeviceIdentifiersErrorPath();
+        when(mMockAppOps.noteOp(eq(AppOpsManager.OPSTR_WRITE_SMS), eq(UID), eq(PACKAGE),
+                eq(FEATURE), nullable(String.class))).thenReturn(AppOpsManager.MODE_ALLOWED);
         assertTrue(TelephonyPermissions.checkReadPhoneNumber(
-                mMockContext, () -> mMockTelephony, SUB_ID, PID, UID, PACKAGE, MSG));
+                mMockContext, SUB_ID, PID, UID, PACKAGE, FEATURE, MSG));
     }
 
     @Test
-    public void testCheckReadPhoneNumber_hasPrivilegedPhoneStatePermission() {
+    public void testCheckReadPhoneNumber_hasPrivilegedPhoneStatePermission() throws Exception {
+        setupMocksForDeviceIdentifiersErrorPath();
         doNothing().when(mMockContext).enforcePermission(
                 android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE, PID, UID, MSG);
         assertTrue(TelephonyPermissions.checkReadPhoneNumber(
-                mMockContext, () -> mMockTelephony, SUB_ID, PID, UID, PACKAGE, MSG));
+                mMockContext, SUB_ID, PID, UID, PACKAGE, FEATURE, MSG));
     }
 
     @Test
-    public void testCheckReadPhoneNumber_hasReadSms() {
+    public void testCheckReadPhoneNumber_hasReadSms() throws Exception {
+        setupMocksForDeviceIdentifiersErrorPath();
         doNothing().when(mMockContext).enforcePermission(
                 android.Manifest.permission.READ_SMS, PID, UID, MSG);
-        when(mMockAppOps.noteOp(AppOpsManager.OP_READ_SMS, UID, PACKAGE))
-                .thenReturn(AppOpsManager.MODE_ALLOWED);
+        when(mMockAppOps.noteOp(eq(AppOpsManager.OPSTR_READ_SMS), eq(UID), eq(PACKAGE), eq(FEATURE),
+                nullable(String.class))).thenReturn(AppOpsManager.MODE_ALLOWED);
         assertTrue(TelephonyPermissions.checkReadPhoneNumber(
-                mMockContext, () -> mMockTelephony, SUB_ID, PID, UID, PACKAGE, MSG));
+                mMockContext, SUB_ID, PID, UID, PACKAGE, FEATURE, MSG));
     }
 
     @Test
-    public void testCheckReadPhoneNumber_hasReadPhoneNumbers() {
+    public void testCheckReadPhoneNumber_hasReadPhoneNumbers() throws Exception {
+        setupMocksForDeviceIdentifiersErrorPath();
         doNothing().when(mMockContext).enforcePermission(
                 android.Manifest.permission.READ_PHONE_NUMBERS, PID, UID, MSG);
-        when(mMockAppOps.noteOp(AppOpsManager.OP_READ_PHONE_NUMBERS, UID, PACKAGE))
-                .thenReturn(AppOpsManager.MODE_ALLOWED);
+        when(mMockAppOps.noteOp(eq(AppOpsManager.OPSTR_READ_PHONE_NUMBERS), eq(UID), eq(PACKAGE),
+                eq(FEATURE), nullable(String.class))).thenReturn(AppOpsManager.MODE_ALLOWED);
         assertTrue(TelephonyPermissions.checkReadPhoneNumber(
-                mMockContext, () -> mMockTelephony, SUB_ID, PID, UID, PACKAGE, MSG));
+                mMockContext, SUB_ID, PID, UID, PACKAGE, FEATURE, MSG));
+    }
+
+    @Test
+    public void testCheckReadPhoneNumber_hasReadSmsNoAppop() throws Exception {
+        // If an app has been granted the READ_SMS permission, but the OPSTR_READ_SMS appop has been
+        // revoked then instead of immediately returning false the phone number access check should
+        // check if the caller has the READ_PHONE_NUMBERS permission and appop.
+        setupMocksForDeviceIdentifiersErrorPath();
+        doNothing().when(mMockContext).enforcePermission(
+                android.Manifest.permission.READ_SMS, PID, UID, MSG);
+        doNothing().when(mMockContext).enforcePermission(
+                android.Manifest.permission.READ_PHONE_NUMBERS, PID, UID, MSG);
+        when(mMockAppOps.noteOp(eq(AppOpsManager.OPSTR_READ_PHONE_NUMBERS), eq(UID), eq(PACKAGE),
+                eq(FEATURE), nullable(String.class))).thenReturn(AppOpsManager.MODE_ALLOWED);
+        assertTrue(TelephonyPermissions.checkReadPhoneNumber(
+                mMockContext, SUB_ID, PID, UID, PACKAGE, FEATURE, MSG));
+    }
+
+    @Test
+    public void testCheckReadPhoneNumber_hasReadSmsAndReadPhoneNumbersNoAppops() throws Exception {
+        // If an app has both the READ_SMS and READ_PHONE_NUMBERS permissions granted but does not
+        // have the corresponding appops instead of returning false for not having the appop granted
+        // a SecurityException should be thrown.
+        setupMocksForDeviceIdentifiersErrorPath();
+        doNothing().when(mMockContext).enforcePermission(
+                android.Manifest.permission.READ_SMS, PID, UID, MSG);
+        doNothing().when(mMockContext).enforcePermission(
+                android.Manifest.permission.READ_PHONE_NUMBERS, PID, UID, MSG);
+        try {
+            TelephonyPermissions.checkReadPhoneNumber(
+                    mMockContext, SUB_ID, PID, UID, PACKAGE, FEATURE, MSG);
+            fail("Should have thrown SecurityException");
+        } catch (SecurityException e) {
+            // expected
+        }
     }
 
     @Test
@@ -252,7 +312,7 @@
         setupMocksForDeviceIdentifiersErrorPath();
         try {
             TelephonyPermissions.checkCallingOrSelfReadDeviceIdentifiers(mMockContext,
-                    SUB_ID, PACKAGE, MSG);
+                    SUB_ID, PACKAGE, FEATURE, MSG);
             fail("Should have thrown SecurityException");
         } catch (SecurityException e) {
             // expected
@@ -260,39 +320,27 @@
     }
 
     @Test
-    public void testCheckReadDeviceIdentifiers_hasPrivilegedPermission() {
-        when(mMockContext.checkPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE,
-                PID, UID)).thenReturn(PackageManager.PERMISSION_GRANTED);
+    public void testCheckReadDeviceIdentifiers_hasPermissionManagerIdentifierAccess() {
+        // The UID, privileged permission, device / profile owner, and appop checks are all now
+        // performed by a SystemAPI in PermissionManager; this test verifies when this API returns
+        // the calling package meets the requirements for device identifier access the telephony
+        // check also returns true.
+        when(mMockPermissionManagerService.checkDeviceIdentifierAccess(PACKAGE, MSG, FEATURE, PID,
+                UID)).thenReturn(PackageManager.PERMISSION_GRANTED);
         assertTrue(
                 TelephonyPermissions.checkCallingOrSelfReadDeviceIdentifiers(mMockContext,
-                        SUB_ID, PACKAGE, MSG));
+                        SUB_ID, PACKAGE, FEATURE, MSG));
     }
 
     @Test
     public void testCheckReadDeviceIdentifiers_hasCarrierPrivileges() throws Exception {
-        when(mMockTelephony.getCarrierPrivilegeStatusForUid(eq(SUB_ID), eq(UID)))
-                .thenReturn(TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS);
+        when(mTelephonyManagerMock.createForSubscriptionId(eq(SUB_ID))).thenReturn(
+                mTelephonyManagerMockForSub1);
+        when(mTelephonyManagerMockForSub1.getCarrierPrivilegeStatus(anyInt())).thenReturn(
+                TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS);
         assertTrue(
                 TelephonyPermissions.checkCallingOrSelfReadDeviceIdentifiers(mMockContext,
-                        SUB_ID, PACKAGE, MSG));
-    }
-
-    @Test
-    public void testCheckReadDeviceIdentifiers_hasAppOp() {
-        when(mMockAppOps.noteOpNoThrow(AppOpsManager.OPSTR_READ_DEVICE_IDENTIFIERS, UID,
-                PACKAGE)).thenReturn(AppOpsManager.MODE_ALLOWED);
-        assertTrue(
-                TelephonyPermissions.checkCallingOrSelfReadDeviceIdentifiers(mMockContext,
-                        SUB_ID, PACKAGE, MSG));
-    }
-
-    @Test
-    public void testCheckReadDeviceIdentifiers_hasDPMDeviceIDAccess() {
-        when(mMockDevicePolicyManager.checkDeviceIdentifierAccess(eq(PACKAGE), eq(PID),
-                eq(UID))).thenReturn(true);
-        assertTrue(
-                TelephonyPermissions.checkCallingOrSelfReadDeviceIdentifiers(mMockContext,
-                        SUB_ID, PACKAGE, MSG));
+                        SUB_ID, PACKAGE, FEATURE, MSG));
     }
 
     @Test
@@ -305,7 +353,7 @@
         setupMocksForDeviceIdentifiersErrorPath();
         try {
             TelephonyPermissions.checkCallingOrSelfReadDeviceIdentifiers(mMockContext,
-                    SUB_ID, PACKAGE, MSG);
+                    SUB_ID, PACKAGE, FEATURE, MSG);
             fail("Should have thrown SecurityException");
         } catch (SecurityException e) {
             // expected
@@ -323,47 +371,37 @@
         mMockApplicationInfo.targetSdkVersion = Build.VERSION_CODES.P;
         assertFalse(
                 TelephonyPermissions.checkCallingOrSelfReadDeviceIdentifiers(mMockContext,
-                        SUB_ID, PACKAGE, MSG));
+                        SUB_ID, PACKAGE, FEATURE, MSG));
     }
 
     @Test
     public void testCheckReadDeviceIdentifiers_hasCarrierPrivilegesOnOtherSubscription()
             throws Exception {
-        when(mMockSubscriptionManager.getActiveSubscriptionIdList(anyBoolean())).thenReturn(
+        when(mMockSubscriptionManager.getCompleteActiveSubscriptionIdList()).thenReturn(
                 new int[]{SUB_ID, SUB_ID_2});
-        when(mMockTelephony.getCarrierPrivilegeStatusForUid(eq(SUB_ID_2), eq(UID))).thenReturn(
+        when(mTelephonyManagerMock.createForSubscriptionId(eq(SUB_ID_2))).thenReturn(
+                mTelephonyManagerMockForSub2);
+        when(mTelephonyManagerMockForSub2.getCarrierPrivilegeStatus(anyInt())).thenReturn(
                 TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS);
         assertTrue(
                 TelephonyPermissions.checkCallingOrSelfReadDeviceIdentifiers(mMockContext,
-                        SUB_ID, PACKAGE, MSG));
-    }
-
-    @Test
-    public void testCheckReadDeviceIdentifiers_hasCarrierPrivilegesOnInvisibleSubscription()
-            throws Exception {
-        when(mMockSubscriptionManager.getActiveSubscriptionIdList(true)).thenReturn(
-                new int[]{SUB_ID});
-        when(mMockSubscriptionManager.getActiveSubscriptionIdList(false)).thenReturn(
-                new int[]{SUB_ID, SUB_ID_2});
-        when(mMockTelephony.getCarrierPrivilegeStatusForUid(eq(SUB_ID_2), eq(UID)))
-                .thenReturn(TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS);
-        assertTrue(
-                TelephonyPermissions.checkCallingOrSelfReadDeviceIdentifiers(mMockContext,
-                        SUB_ID, PACKAGE, MSG));
+                        SUB_ID, PACKAGE, FEATURE, MSG));
     }
 
     @Test
     public void testCheckReadDeviceIdentifiers_hasAppOpNullSubscription() {
         // The appop check comes after the carrier privilege check; this test verifies if the
-        // SubscriptionManager returns a null array for the active subscription IDs this check can
+        // SubscriptionManager returns an empty array for the active subscription IDs this check can
         // still proceed to check if the calling package has the appop and any subsequent checks
         // without a NullPointerException.
-        when(mMockSubscriptionManager.getActiveSubscriptionIdList()).thenReturn(null);
-        when(mMockAppOps.noteOpNoThrow(AppOpsManager.OPSTR_READ_DEVICE_IDENTIFIERS, UID,
-                PACKAGE)).thenReturn(AppOpsManager.MODE_ALLOWED);
+        when(mMockSubscriptionManager.getCompleteActiveSubscriptionIdList())
+                .thenReturn(new int[0]);
+        when(mMockAppOps.noteOpNoThrow(eq(AppOpsManager.OPSTR_READ_DEVICE_IDENTIFIERS), eq(UID),
+                eq(PACKAGE), eq(FEATURE), nullable(String.class))).thenReturn(
+                AppOpsManager.MODE_ALLOWED);
         assertTrue(
                 TelephonyPermissions.checkCallingOrSelfReadDeviceIdentifiers(mMockContext,
-                        SUB_ID, PACKAGE, MSG));
+                        SUB_ID, PACKAGE, FEATURE, MSG));
     }
 
     @Test
@@ -374,7 +412,7 @@
         setupMocksForDeviceIdentifiersErrorPath();
         try {
             TelephonyPermissions.checkCallingOrSelfReadDeviceIdentifiers(mMockContext,
-                    SUB_ID, null, MSG);
+                    SUB_ID, null, FEATURE, MSG);
             fail("Should have thrown SecurityException");
         } catch (SecurityException e) {
             // expected
@@ -385,16 +423,9 @@
     public void testCheckCallingOrSelfReadSubscriberIdentifiers_noPermissions() throws Exception {
         setupMocksForDeviceIdentifiersErrorPath();
         setTelephonyMockAsService();
-        when(mMockContext.checkPermission(
-                eq(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE),
-                anyInt(), anyInt())).thenReturn(PackageManager.PERMISSION_DENIED);
-        when(mMockAppOps.noteOpNoThrow(anyString(), anyInt(), eq(PACKAGE))).thenReturn(
-                AppOpsManager.MODE_ERRORED);
-        when(mMockDevicePolicyManager.checkDeviceIdentifierAccess(eq(PACKAGE), anyInt(),
-                anyInt())).thenReturn(false);
         try {
             TelephonyPermissions.checkCallingOrSelfReadSubscriberIdentifiers(mMockContext,
-                    SUB_ID, PACKAGE, MSG);
+                    SUB_ID, PACKAGE, FEATURE, MSG);
             fail("Should have thrown SecurityException");
         } catch (SecurityException e) {
             // expected
@@ -404,15 +435,15 @@
     @Test
     public void testCheckCallingOrSelfReadSubscriberIdentifiers_carrierPrivileges()
             throws Exception {
+        setupMocksForDeviceIdentifiersErrorPath();
         setTelephonyMockAsService();
-        when(mMockContext.checkPermission(
-                eq(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE),
-                anyInt(), anyInt())).thenReturn(PackageManager.PERMISSION_DENIED);
-        when(mMockTelephony.getCarrierPrivilegeStatusForUid(eq(SUB_ID), anyInt()))
-                .thenReturn(TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS);
+        when(mTelephonyManagerMock.createForSubscriptionId(eq(SUB_ID))).thenReturn(
+                mTelephonyManagerMockForSub1);
+        when(mTelephonyManagerMockForSub1.getCarrierPrivilegeStatus(anyInt())).thenReturn(
+                TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS);
         assertTrue(
                 TelephonyPermissions.checkCallingOrSelfReadSubscriberIdentifiers(mMockContext,
-                        SUB_ID, PACKAGE, MSG));
+                        SUB_ID, PACKAGE, FEATURE, MSG));
     }
 
     @Test
@@ -420,23 +451,116 @@
             throws Exception {
         setupMocksForDeviceIdentifiersErrorPath();
         setTelephonyMockAsService();
-        when(mMockContext.checkPermission(
-                eq(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE),
-                anyInt(), anyInt())).thenReturn(PackageManager.PERMISSION_DENIED);
-        when(mMockSubscriptionManager.getActiveSubscriptionIdList(anyBoolean())).thenReturn(
+        when(mMockSubscriptionManager.getCompleteActiveSubscriptionIdList()).thenReturn(
                 new int[]{SUB_ID, SUB_ID_2});
-        when(mMockTelephony.getCarrierPrivilegeStatusForUid(eq(SUB_ID_2), anyInt())).thenReturn(
+        when(mTelephonyManagerMock.createForSubscriptionId(eq(SUB_ID_2))).thenReturn(
+                mTelephonyManagerMockForSub2);
+        when(mTelephonyManagerMockForSub2.getCarrierPrivilegeStatus(anyInt())).thenReturn(
                 TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS);
         // Carrier privilege on the other active sub shouldn't allow access to this sub.
         try {
             TelephonyPermissions.checkCallingOrSelfReadSubscriberIdentifiers(mMockContext,
-                    SUB_ID, PACKAGE, MSG);
+                    SUB_ID, PACKAGE, FEATURE, MSG);
             fail("Should have thrown SecurityException");
         } catch (SecurityException e) {
             // expected
         }
     }
 
+    /**
+     * Validate the SecurityException will be thrown if call the method without permissions, nor
+     * privileges.
+     */
+    @Test
+    public void
+    testEnforeceCallingOrSelfReadPrecisePhoneStatePermissionOrCarrierPrivilege_noPermissions()
+            throws Exception {
+        // revoke permission READ_PRIVILEGED_PHONE_STATE
+        when(mMockContext.checkCallingOrSelfPermission(
+                android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE)).thenReturn(
+                PackageManager.PERMISSION_DENIED);
+        // revoke permision READ_PRECISE_PHONE_STATE
+        when(mMockContext.checkCallingOrSelfPermission(
+                android.Manifest.permission.READ_PRECISE_PHONE_STATE)).thenReturn(
+                PackageManager.PERMISSION_DENIED);
+        try {
+            TelephonyPermissions
+                    .enforeceCallingOrSelfReadPrecisePhoneStatePermissionOrCarrierPrivilege(
+                    mMockContext, SUB_ID, MSG);
+            fail("Should have thrown SecurityException");
+        } catch (SecurityException se) {
+            // expected
+        }
+    }
+
+    /**
+     * Validate that no SecurityException thrown when we have either permission
+     * READ_PRECISE_PHONE_STATE or READ_PRIVILEGED_PHONE_STATE.
+     */
+    @Test
+    public void
+    testEnforeceCallingOrSelfReadPrecisePhoneStatePermissionOrCarrierPrivilege_withPermissions()
+            throws Exception {
+        // grant READ_PRIVILEGED_PHONE_STATE permission
+        when(mMockContext.checkCallingOrSelfPermission(
+                android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE)).thenReturn(
+                PackageManager.PERMISSION_GRANTED);
+        try {
+            TelephonyPermissions
+                    .enforeceCallingOrSelfReadPrecisePhoneStatePermissionOrCarrierPrivilege(
+                    mMockContext, SUB_ID, MSG);
+        } catch (SecurityException se) {
+            fail();
+        }
+
+        // revoke permission READ_PRIVILEGED_PHONE_STATE
+        when(mMockContext.checkCallingOrSelfPermission(
+                android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE)).thenReturn(
+                PackageManager.PERMISSION_DENIED);
+
+        // grant READ_PRECISE_PHONE_STATE permission
+        when(mMockContext.checkCallingOrSelfPermission(
+                android.Manifest.permission.READ_PRECISE_PHONE_STATE)).thenReturn(
+                PackageManager.PERMISSION_GRANTED);
+        try {
+            TelephonyPermissions
+                    .enforeceCallingOrSelfReadPrecisePhoneStatePermissionOrCarrierPrivilege(
+                    mMockContext, SUB_ID, MSG);
+        } catch (SecurityException se) {
+            fail();
+        }
+    }
+
+    /**
+     * Validate that no SecurityException thrown when we have carrier privileges.
+     */
+    @Test
+    public void
+    testEnforeceCallingOrSelfReadPrecisePhoneStatePermissionOrCarrierPrivilege_withPrivileges()
+            throws Exception {
+        // revoke permission READ_PRIVILEGED_PHONE_STATE
+        when(mMockContext.checkCallingOrSelfPermission(
+                android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE)).thenReturn(
+                PackageManager.PERMISSION_DENIED);
+        // revoke permision READ_PRECISE_PHONE_STATE
+        when(mMockContext.checkCallingOrSelfPermission(
+                android.Manifest.permission.READ_PRECISE_PHONE_STATE)).thenReturn(
+                PackageManager.PERMISSION_DENIED);
+
+        setTelephonyMockAsService();
+        when(mTelephonyManagerMock.createForSubscriptionId(eq(SUB_ID))).thenReturn(
+                mTelephonyManagerMockForSub1);
+        when(mTelephonyManagerMockForSub1.getCarrierPrivilegeStatus(anyInt())).thenReturn(
+                TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS);
+        try {
+            TelephonyPermissions
+                    .enforeceCallingOrSelfReadPrecisePhoneStatePermissionOrCarrierPrivilege(
+                    mMockContext, SUB_ID, MSG);
+        } catch (SecurityException se) {
+            fail("Should NOT throw SecurityException");
+        }
+    }
+
     // Put mMockTelephony into service cache so that TELEPHONY_SUPPLIER will get it.
     private void setTelephonyMockAsService() throws Exception {
         when(mMockTelephonyBinder.queryLocalInterface(anyString())).thenReturn(mMockTelephony);
@@ -485,13 +609,16 @@
         // regardless of if the package satisfies the previous requirements for device ID access.
         mMockApplicationInfo.targetSdkVersion = Build.VERSION_CODES.Q;
         when(mMockContext.getPackageManager()).thenReturn(mMockPackageManager);
-        when(mMockPackageManager.getApplicationInfoAsUser(eq(PACKAGE), anyInt(),
-                anyInt())).thenReturn(mMockApplicationInfo);
+        when(mMockPackageManager.getApplicationInfoAsUser(eq(PACKAGE), anyInt(), any()))
+            .thenReturn(mMockApplicationInfo);
 
         when(mMockContext.checkCallingOrSelfPermission(
                 android.Manifest.permission.READ_DEVICE_CONFIG)).thenReturn(
                 PackageManager.PERMISSION_GRANTED);
 
+        when(mMockPermissionManagerService.checkDeviceIdentifierAccess(any(), any(), any(),
+                anyInt(), anyInt())).thenReturn(PackageManager.PERMISSION_DENIED);
+
         // TelephonyPermissions queries DeviceConfig to determine if the identifier access
         // restrictions should be enabled; since DeviceConfig uses
         // Activity.currentActivity.getContentResolver as the resolver for Settings.Config.getString
diff --git a/tests/telephonytests/src/com/android/internal/telephony/TelephonyRegistryTest.java b/tests/telephonytests/src/com/android/internal/telephony/TelephonyRegistryTest.java
index 84d50aa..626dee2 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/TelephonyRegistryTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/TelephonyRegistryTest.java
@@ -17,78 +17,160 @@
 
 import static android.telephony.PhoneStateListener.LISTEN_ACTIVE_DATA_SUBSCRIPTION_ID_CHANGE;
 import static android.telephony.PhoneStateListener.LISTEN_PHONE_CAPABILITY_CHANGE;
+import static android.telephony.PhoneStateListener.LISTEN_RADIO_POWER_STATE_CHANGED;
 import static android.telephony.PhoneStateListener.LISTEN_SRVCC_STATE_CHANGED;
+import static android.telephony.SubscriptionManager.ACTION_DEFAULT_SUBSCRIPTION_CHANGED;
+import static android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID;
+import static android.telephony.TelephonyManager.ACTION_MULTI_SIM_CONFIG_CHANGED;
+import static android.telephony.TelephonyManager.RADIO_POWER_OFF;
+import static android.telephony.TelephonyManager.RADIO_POWER_ON;
+import static android.telephony.TelephonyManager.RADIO_POWER_UNAVAILABLE;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.fail;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.when;
 
-import android.os.HandlerThread;
+import android.content.Intent;
+import android.net.LinkProperties;
 import android.os.ServiceManager;
+import android.telephony.Annotation;
 import android.telephony.PhoneCapability;
 import android.telephony.PhoneStateListener;
+import android.telephony.PreciseDataConnectionState;
+import android.telephony.SubscriptionInfo;
+import android.telephony.SubscriptionManager;
+import android.telephony.TelephonyDisplayInfo;
 import android.telephony.TelephonyManager;
+import android.telephony.data.ApnSetting;
 import android.test.suitebuilder.annotation.SmallTest;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
 
 import com.android.server.TelephonyRegistry;
 
 import org.junit.After;
 import org.junit.Before;
+import org.junit.Ignore;
 import org.junit.Test;
+import org.junit.runner.RunWith;
 import org.mockito.Mock;
 
+import java.util.Map;
+import java.util.concurrent.atomic.AtomicInteger;
+
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper
 public class TelephonyRegistryTest extends TelephonyTest {
     @Mock
-    private ISub.Stub mISubStub;
-    private PhoneStateListener mPhoneStateListener;
+    private SubscriptionInfo mMockSubInfo;
+    private PhoneStateListenerWrapper mPhoneStateListener;
     private TelephonyRegistry mTelephonyRegistry;
     private PhoneCapability mPhoneCapability;
     private int mActiveSubId;
+    private TelephonyDisplayInfo mTelephonyDisplayInfo;
     private int mSrvccState = -1;
+    private int mRadioPowerState = RADIO_POWER_UNAVAILABLE;
+    // All events contribute to TelephonyRegistry.ENFORCE_PHONE_STATE_PERMISSION_MASK
+    private static final Map<Integer, String> READ_PHONE_STATE_EVENTS = Map.of(
+            PhoneStateListener.LISTEN_CALL_FORWARDING_INDICATOR,
+            "PhoneStateListener.LISTEN_CALL_FORWARDING_INDICATOR",
+            PhoneStateListener.LISTEN_MESSAGE_WAITING_INDICATOR,
+            "PhoneStateListener.LISTEN_MESSAGE_WAITING_INDICATOR",
+            PhoneStateListener.LISTEN_EMERGENCY_NUMBER_LIST,
+            "PhoneStateListener.LISTEN_EMERGENCY_NUMBER_LIST");
+
+    // All events contribute to TelephonyRegistry.PRECISE_PHONE_STATE_PERMISSION_MASK
+    private static final Map<Integer, String> READ_PRECISE_PHONE_STATE_EVENTS = Map.of(
+            PhoneStateListener.LISTEN_PRECISE_CALL_STATE,
+            "PhoneStateListener.LISTEN_PRECISE_CALL_STATE",
+            PhoneStateListener.LISTEN_PRECISE_DATA_CONNECTION_STATE,
+            "PhoneStateListener.LISTEN_PRECISE_DATA_CONNECTION_STATE",
+            PhoneStateListener.LISTEN_CALL_DISCONNECT_CAUSES,
+            "PhoneStateListener.LISTEN_CALL_DISCONNECT_CAUSES",
+            PhoneStateListener.LISTEN_CALL_ATTRIBUTES_CHANGED,
+            "PhoneStateListener.LISTEN_CALL_ATTRIBUTES_CHANGED",
+            PhoneStateListener.LISTEN_IMS_CALL_DISCONNECT_CAUSES,
+            "PhoneStateListener.LISTEN_IMS_CALL_DISCONNECT_CAUSES",
+            PhoneStateListener.LISTEN_REGISTRATION_FAILURE,
+            "PhoneStateListener.LISTEN_REGISTRATION_FAILURE",
+            PhoneStateListener.LISTEN_BARRING_INFO,
+            "PhoneStateListener.LISTEN_BARRING_INFO");
+
+    // All events contribute to TelephonyRegistry.PREVILEGED_PHONE_STATE_PERMISSION_MASK
+    // TODO: b/148021947 will create the permission group with PREVILIGED_STATE_PERMISSION_MASK
+    private static final Map<Integer, String> READ_PREVILIGED_PHONE_STATE_EVENTS = Map.of(
+            PhoneStateListener.LISTEN_SRVCC_STATE_CHANGED,
+            "PhoneStateListener.LISTEN_SRVCC_STATE_CHANGED",
+            PhoneStateListener.LISTEN_OEM_HOOK_RAW_EVENT,
+            "PhoneStateListener.LISTEN_OEM_HOOK_RAW_EVENT",
+            PhoneStateListener.LISTEN_RADIO_POWER_STATE_CHANGED,
+            "PhoneStateListener.LISTEN_RADIO_POWER_STATE_CHANGED",
+            PhoneStateListener.LISTEN_VOICE_ACTIVATION_STATE,
+            "PhoneStateListener.LISTEN_VOICE_ACTIVATION_STATE");
+
+    // All events contribute to TelephonyRegistry.READ_ACTIVE_EMERGENCY_SESSION_PERMISSION_MASK
+    private static final Map<Integer, String> READ_ACTIVE_EMERGENCY_SESSION_EVENTS = Map.of(
+            PhoneStateListener.LISTEN_OUTGOING_EMERGENCY_CALL,
+            "PhoneStateListener.LISTEN_OUTGOING_EMERGENCY_CALL",
+            PhoneStateListener.LISTEN_OUTGOING_EMERGENCY_SMS,
+            "PhoneStateListener.LISTEN_OUTGOING_EMERGENCY_SMS");
 
     public class PhoneStateListenerWrapper extends PhoneStateListener {
+        // This class isn't mockable to get invocation counts because the IBinder is null and
+        // crashes the TelephonyRegistry. Make a cheesy verify(times()) alternative.
+        public AtomicInteger invocationCount = new AtomicInteger(0);
+
         @Override
         public void onSrvccStateChanged(int srvccState) {
+            invocationCount.incrementAndGet();
             mSrvccState = srvccState;
-            setReady(true);
         }
 
         @Override
         public void onPhoneCapabilityChanged(PhoneCapability capability) {
+            invocationCount.incrementAndGet();
             mPhoneCapability = capability;
-            setReady(true);
         }
         @Override
         public void onActiveDataSubscriptionIdChanged(int activeSubId) {
+            invocationCount.incrementAndGet();
             mActiveSubId = activeSubId;
-            setReady(true);
+        }
+        @Override
+        public void onRadioPowerStateChanged(@Annotation.RadioPowerState int state) {
+            invocationCount.incrementAndGet();
+            mRadioPowerState = state;
+        }
+        @Override
+        public void onPreciseDataConnectionStateChanged(PreciseDataConnectionState preciseState) {
+            invocationCount.incrementAndGet();
+        }
+        @Override
+        public void onDisplayInfoChanged(TelephonyDisplayInfo displayInfo) {
+            mTelephonyDisplayInfo = displayInfo;
         }
     }
 
     private void addTelephonyRegistryService() {
         mServiceManagerMockedServices.put("telephony.registry", mTelephonyRegistry.asBinder());
+        mTelephonyRegistry.systemRunning();
     }
 
-    private HandlerThread mHandlerThread = new HandlerThread("ListenerThread") {
-        @Override
-        public void onLooperPrepared() {
-            mTelephonyRegistry = new TelephonyRegistry(mContext);
-            addTelephonyRegistryService();
-            mPhoneStateListener = new PhoneStateListenerWrapper();
-            setReady(true);
-        }
-    };
-
     @Before
     public void setUp() throws Exception {
         super.setUp("TelephonyRegistryTest");
-        // ServiceManager.getService("isub") will return this stub for any call to
-        // SubscriptionManager.
-        mServiceManagerMockedServices.put("isub", mISubStub);
-        mHandlerThread.start();
-        waitUntilReady();
+        TelephonyRegistry.ConfigurationProvider mockConfigurationProvider =
+                mock(TelephonyRegistry.ConfigurationProvider.class);
+        when(mockConfigurationProvider.getRegistrationLimit()).thenReturn(-1);
+        when(mockConfigurationProvider.isRegistrationLimitEnabledInPlatformCompat(anyInt()))
+                .thenReturn(false);
+        mTelephonyRegistry = new TelephonyRegistry(mContext, mockConfigurationProvider);
+        addTelephonyRegistryService();
+        mPhoneStateListener = new PhoneStateListenerWrapper();
+        processAllMessages();
         assertEquals(mTelephonyRegistry.asBinder(),
                 ServiceManager.getService("telephony.registry"));
     }
@@ -96,27 +178,26 @@
     @After
     public void tearDown() throws Exception {
         mTelephonyRegistry = null;
-        mHandlerThread.quit();
         super.tearDown();
     }
 
     @Test @SmallTest
     public void testPhoneCapabilityChanged() {
+        doReturn(mMockSubInfo).when(mSubscriptionManager).getActiveSubscriptionInfo(anyInt());
+        doReturn(0/*slotIndex*/).when(mMockSubInfo).getSimSlotIndex();
         // mTelephonyRegistry.listen with notifyNow = true should trigger callback immediately.
-        setReady(false);
         PhoneCapability phoneCapability = new PhoneCapability(1, 2, 3, null, false);
         mTelephonyRegistry.notifyPhoneCapabilityChanged(phoneCapability);
-        mTelephonyRegistry.listen(mContext.getOpPackageName(),
-                mPhoneStateListener.callback,
+        mTelephonyRegistry.listenWithFeature(mContext.getOpPackageName(),
+                mContext.getAttributionTag(), mPhoneStateListener.callback,
                 LISTEN_PHONE_CAPABILITY_CHANGE, true);
-        waitUntilReady();
+        processAllMessages();
         assertEquals(phoneCapability, mPhoneCapability);
 
         // notifyPhoneCapabilityChanged with a new capability. Callback should be triggered.
-        setReady(false);
         phoneCapability = new PhoneCapability(3, 2, 2, null, false);
         mTelephonyRegistry.notifyPhoneCapabilityChanged(phoneCapability);
-        waitUntilReady();
+        processAllMessages();
         assertEquals(phoneCapability, mPhoneCapability);
     }
 
@@ -124,22 +205,20 @@
     @Test @SmallTest
     public void testActiveDataSubChanged() {
         // mTelephonyRegistry.listen with notifyNow = true should trigger callback immediately.
-        setReady(false);
         int[] activeSubs = {0, 1, 2};
         when(mSubscriptionManager.getActiveSubscriptionIdList()).thenReturn(activeSubs);
         int activeSubId = 0;
         mTelephonyRegistry.notifyActiveDataSubIdChanged(activeSubId);
-        mTelephonyRegistry.listen(mContext.getOpPackageName(),
-                mPhoneStateListener.callback,
+        mTelephonyRegistry.listenWithFeature(mContext.getOpPackageName(),
+                mContext.getAttributionTag(), mPhoneStateListener.callback,
                 LISTEN_ACTIVE_DATA_SUBSCRIPTION_ID_CHANGE, true);
-        waitUntilReady();
+        processAllMessages();
         assertEquals(activeSubId, mActiveSubId);
 
         // notifyPhoneCapabilityChanged with a new capability. Callback should be triggered.
-        setReady(false);
         mActiveSubId = 1;
         mTelephonyRegistry.notifyActiveDataSubIdChanged(activeSubId);
-        waitUntilReady();
+        processAllMessages();
         assertEquals(activeSubId, mActiveSubId);
     }
 
@@ -151,23 +230,22 @@
     @Test
     @SmallTest
     public void testSrvccStateChanged() throws Exception {
-        // Return a phone ID of 0 for all sub ids given.
-        doReturn(0/*phoneId*/).when(mISubStub).getPhoneId(anyInt());
-        setReady(false);
+        // Return a slotIndex / phoneId of 0 for all sub ids given.
+        doReturn(mMockSubInfo).when(mSubscriptionManager).getActiveSubscriptionInfo(anyInt());
+        doReturn(0/*slotIndex*/).when(mMockSubInfo).getSimSlotIndex();
         int srvccState = TelephonyManager.SRVCC_STATE_HANDOVER_STARTED;
-        mTelephonyRegistry.notifySrvccStateChanged(0 /*subId*/, srvccState);
+        mTelephonyRegistry.notifySrvccStateChanged(1 /*subId*/, srvccState);
         // Should receive callback when listen is called that contains the latest notify result.
-        mTelephonyRegistry.listenForSubscriber(0 /*subId*/, mContext.getOpPackageName(),
-                mPhoneStateListener.callback,
+        mTelephonyRegistry.listenForSubscriber(1 /*subId*/, mContext.getOpPackageName(),
+                mContext.getAttributionTag(), mPhoneStateListener.callback,
                 LISTEN_SRVCC_STATE_CHANGED, true);
-        waitUntilReady();
+        processAllMessages();
         assertEquals(srvccState, mSrvccState);
 
         // trigger callback
-        setReady(false);
         srvccState = TelephonyManager.SRVCC_STATE_HANDOVER_COMPLETED;
-        mTelephonyRegistry.notifySrvccStateChanged(0 /*subId*/, srvccState);
-        waitUntilReady();
+        mTelephonyRegistry.notifySrvccStateChanged(1 /*subId*/, srvccState);
+        processAllMessages();
         assertEquals(srvccState, mSrvccState);
     }
 
@@ -184,11 +262,213 @@
         mTelephonyRegistry.notifySrvccStateChanged(0 /*subId*/, srvccState);
         try {
             mTelephonyRegistry.listenForSubscriber(0 /*subId*/, mContext.getOpPackageName(),
-                    mPhoneStateListener.callback,
+                    mContext.getAttributionTag(), mPhoneStateListener.callback,
                     LISTEN_SRVCC_STATE_CHANGED, true);
             fail();
         } catch (SecurityException e) {
             // pass test!
         }
     }
+
+    /**
+     * Test multi sim config change.
+     */
+    @Test
+    public void testMultiSimConfigChange() {
+        mTelephonyRegistry.listenForSubscriber(1, mContext.getOpPackageName(),
+                mContext.getAttributionTag(), mPhoneStateListener.callback,
+                LISTEN_RADIO_POWER_STATE_CHANGED, true);
+        processAllMessages();
+        assertEquals(RADIO_POWER_UNAVAILABLE, mRadioPowerState);
+
+        // Notify RADIO_POWER_ON on invalid phoneId. Shouldn't go through.
+        mTelephonyRegistry.notifyRadioPowerStateChanged(1, 1, RADIO_POWER_ON);
+        processAllMessages();
+        assertEquals(RADIO_POWER_UNAVAILABLE, mRadioPowerState);
+
+        // Switch to DSDS and re-send RADIO_POWER_ON on phone 1. This time it should be notified.
+        doReturn(2).when(mTelephonyManager).getActiveModemCount();
+        mContext.sendBroadcast(new Intent(ACTION_MULTI_SIM_CONFIG_CHANGED));
+        mTelephonyRegistry.notifyRadioPowerStateChanged(1, 1, RADIO_POWER_ON);
+        processAllMessages();
+        assertEquals(RADIO_POWER_ON, mRadioPowerState);
+
+        // Switch back to single SIM mode and re-send on phone 0. This time it should be notified.
+        doReturn(2).when(mTelephonyManager).getActiveModemCount();
+        mContext.sendBroadcast(new Intent(ACTION_MULTI_SIM_CONFIG_CHANGED));
+        mTelephonyRegistry.notifyRadioPowerStateChanged(0, 1, RADIO_POWER_OFF);
+        processAllMessages();
+        assertEquals(RADIO_POWER_OFF, mRadioPowerState);
+    }
+
+    /**
+     * Test multi sim config change.
+     */
+    @Test
+    public void testPreciseDataConnectionStateChanged() {
+        final int subId = 1;
+        doReturn(mMockSubInfo).when(mSubscriptionManager).getActiveSubscriptionInfo(anyInt());
+        doReturn(0/*slotIndex*/).when(mMockSubInfo).getSimSlotIndex();
+        // Initialize the PSL with a PreciseDataConnection
+        mTelephonyRegistry.notifyDataConnectionForSubscriber(
+                /*phoneId*/ 0, subId, ApnSetting.TYPE_DEFAULT,
+                new PreciseDataConnectionState(
+                    0, 0, 0, "default", new LinkProperties(), 0, null));
+        mTelephonyRegistry.listenForSubscriber(subId, mContext.getOpPackageName(),
+                mContext.getAttributionTag(), mPhoneStateListener.callback,
+                PhoneStateListener.LISTEN_PRECISE_DATA_CONNECTION_STATE, true);
+        processAllMessages();
+        // Verify that the PDCS is reported for the only APN
+        assertEquals(mPhoneStateListener.invocationCount.get(), 1);
+
+        // Add IMS APN and verify that the listener is invoked for the IMS APN
+        mTelephonyRegistry.notifyDataConnectionForSubscriber(
+                /*phoneId*/ 0, subId, ApnSetting.TYPE_IMS,
+                new PreciseDataConnectionState(
+                    0, 0, 0, "ims", new LinkProperties(), 0, null));
+        processAllMessages();
+
+        assertEquals(mPhoneStateListener.invocationCount.get(), 2);
+
+        // Unregister the listener
+        mTelephonyRegistry.listenForSubscriber(subId, mContext.getOpPackageName(),
+                mContext.getAttributionTag(), mPhoneStateListener.callback,
+                PhoneStateListener.LISTEN_NONE, true);
+        processAllMessages();
+
+        // Re-register the listener and ensure that both APN types are reported
+        mTelephonyRegistry.listenForSubscriber(subId, mContext.getOpPackageName(),
+                mContext.getAttributionTag(), mPhoneStateListener.callback,
+                PhoneStateListener.LISTEN_PRECISE_DATA_CONNECTION_STATE, true);
+        processAllMessages();
+        assertEquals(mPhoneStateListener.invocationCount.get(), 4);
+
+        // Send a duplicate event to the TelephonyRegistry and verify that the listener isn't
+        // invoked.
+        mTelephonyRegistry.notifyDataConnectionForSubscriber(
+                /*phoneId*/ 0, subId, ApnSetting.TYPE_IMS,
+                new PreciseDataConnectionState(
+                    0, 0, 0, "ims", new LinkProperties(), 0, null));
+        processAllMessages();
+        assertEquals(mPhoneStateListener.invocationCount.get(), 4);
+    }
+
+    /**
+     * Test listen to events that require READ_PHONE_STATE permission.
+     */
+    @Test
+    public void testReadPhoneStatePermission() {
+        // Clear all permission grants for test.
+        mContextFixture.addCallingOrSelfPermission("");
+        for (Map.Entry<Integer, String> entry : READ_PHONE_STATE_EVENTS.entrySet()) {
+            assertSecurityExceptionThrown(entry.getKey(), entry.getValue());
+        }
+
+        // Grant permssion
+        mContextFixture.addCallingOrSelfPermission(android.Manifest.permission.READ_PHONE_STATE);
+        for (Map.Entry<Integer, String> entry : READ_PHONE_STATE_EVENTS.entrySet()) {
+            assertSecurityExceptionNotThrown(entry.getKey(), entry.getValue());
+        }
+    }
+
+    /**
+     * Test listen to events that require READ_PRECISE_PHONE_STATE permission.
+     */
+    // FIXME(b/159082270) - Simply not granting location permission doesn't fix the test because
+    // Location is soft-denied to apps that aren't in the foreground, and soft-denial currently
+    // short-circuits the test.
+    @Ignore("Skip due to b/159082270")
+    @Test
+    public void testReadPrecisePhoneStatePermission() {
+        // Clear all permission grants for test.
+        mContextFixture.addCallingOrSelfPermission("");
+        for (Map.Entry<Integer, String> entry : READ_PRECISE_PHONE_STATE_EVENTS.entrySet()) {
+            assertSecurityExceptionThrown(entry.getKey(), entry.getValue());
+        }
+
+        // Grant permssion
+        mContextFixture.addCallingOrSelfPermission(
+                android.Manifest.permission.READ_PRECISE_PHONE_STATE);
+        for (Map.Entry<Integer, String> entry : READ_PRECISE_PHONE_STATE_EVENTS.entrySet()) {
+            assertSecurityExceptionNotThrown(entry.getKey(), entry.getValue());
+        }
+    }
+
+    /**
+     * Test listen to events that require READ_PRIVILEGED_PHONE_STATE permission.
+     */
+    @Test
+    public void testReadPrivilegedPhoneStatePermission() {
+        // Clear all permission grants for test.
+        mContextFixture.addCallingOrSelfPermission("");
+        for (Map.Entry<Integer, String> entry : READ_PREVILIGED_PHONE_STATE_EVENTS.entrySet()) {
+            assertSecurityExceptionThrown(entry.getKey(), entry.getValue());
+        }
+
+        // Grant permssion
+        mContextFixture.addCallingOrSelfPermission(
+                android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE);
+        for (Map.Entry<Integer, String> entry : READ_PREVILIGED_PHONE_STATE_EVENTS.entrySet()) {
+            assertSecurityExceptionNotThrown(entry.getKey(), entry.getValue());
+        }
+    }
+
+    /**
+     * Test listen to events that require READ_ACTIVE_EMERGENCY_SESSION permission.
+     */
+    @Test
+    public void testReadActiveEmergencySessionPermission() {
+        // Clear all permission grants for test.
+        mContextFixture.addCallingOrSelfPermission("");
+        for (Map.Entry<Integer, String> entry : READ_ACTIVE_EMERGENCY_SESSION_EVENTS.entrySet()) {
+            assertSecurityExceptionThrown(entry.getKey(), entry.getValue());
+        }
+
+        // Grant permssion
+        mContextFixture.addCallingOrSelfPermission(
+                android.Manifest.permission.READ_ACTIVE_EMERGENCY_SESSION);
+        for (Map.Entry<Integer, String> entry : READ_ACTIVE_EMERGENCY_SESSION_EVENTS.entrySet()) {
+            assertSecurityExceptionNotThrown(entry.getKey(), entry.getValue());
+        }
+    }
+
+    @Test
+    public void testNotifyDisplayInfoChanged() {
+        mContext.sendBroadcast(new Intent(ACTION_DEFAULT_SUBSCRIPTION_CHANGED)
+                .putExtra(SubscriptionManager.EXTRA_SUBSCRIPTION_INDEX, 12)
+                .putExtra(SubscriptionManager.EXTRA_SLOT_INDEX, 0));
+        processAllMessages();
+        mTelephonyRegistry.listenForSubscriber(2, mContext.getOpPackageName(),
+                mContext.getAttributionTag(), mPhoneStateListener.callback,
+                PhoneStateListener.LISTEN_DISPLAY_INFO_CHANGED, false);
+
+        // Notify with invalid subId on default phone. Should NOT trigger callback.
+        TelephonyDisplayInfo displayInfo = new TelephonyDisplayInfo(0, 0);
+        mTelephonyRegistry.notifyDisplayInfoChanged(0, INVALID_SUBSCRIPTION_ID, displayInfo);
+        processAllMessages();
+        assertEquals(null, mTelephonyDisplayInfo);
+
+        // Notify with the matching subId on default phone. Should trigger callback.
+        mTelephonyRegistry.notifyDisplayInfoChanged(0, 2, displayInfo);
+        processAllMessages();
+        assertEquals(displayInfo, mTelephonyDisplayInfo);
+    }
+
+    private void assertSecurityExceptionThrown(int event, String eventDesc) {
+        try {
+            mTelephonyRegistry.listen(mContext.getOpPackageName(),
+                    mPhoneStateListener.callback, event, true);
+            fail("SecurityException should throw when listen " + eventDesc + " without permission");
+        } catch (SecurityException expected) {
+        }
+    }
+
+    private void assertSecurityExceptionNotThrown(int event, String eventDesc) {
+        try {
+            mTelephonyRegistry.listen(mContext.getOpPackageName(),
+                    mPhoneStateListener.callback, event, true);
+        } catch (SecurityException unexpected) {
+            fail("SecurityException thrown when listen " + eventDesc + " with permission");
+        }
+    }
 }
diff --git a/tests/telephonytests/src/com/android/internal/telephony/TelephonyTest.java b/tests/telephonytests/src/com/android/internal/telephony/TelephonyTest.java
index 09fcc24..b8277ad 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/TelephonyTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/TelephonyTest.java
@@ -17,6 +17,7 @@
 package com.android.internal.telephony;
 
 import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.Matchers.anyBoolean;
@@ -30,6 +31,7 @@
 import android.app.ActivityManager;
 import android.app.AppOpsManager;
 import android.app.IActivityManager;
+import android.app.usage.NetworkStatsManager;
 import android.content.ContentResolver;
 import android.content.Context;
 import android.content.IIntentSender;
@@ -43,23 +45,29 @@
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.IBinder;
-import android.os.IDeviceIdleController;
 import android.os.Looper;
+import android.os.Message;
+import android.os.MessageQueue;
 import android.os.RegistrantList;
 import android.os.ServiceManager;
+import android.os.UserManager;
+import android.permission.PermissionManager;
 import android.provider.BlockedNumberContract;
 import android.provider.DeviceConfig;
 import android.provider.Settings;
 import android.telephony.AccessNetworkConstants;
+import android.telephony.CarrierConfigManager;
 import android.telephony.NetworkRegistrationInfo;
 import android.telephony.ServiceState;
 import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyManager;
+import android.telephony.TelephonyRegistryManager;
 import android.telephony.emergency.EmergencyNumber;
 import android.telephony.euicc.EuiccManager;
 import android.telephony.ims.ImsCallProfile;
 import android.test.mock.MockContentProvider;
 import android.test.mock.MockContentResolver;
+import android.testing.TestableLooper;
 import android.util.Log;
 import android.util.Singleton;
 
@@ -76,6 +84,9 @@
 import com.android.internal.telephony.imsphone.ImsExternalCallTracker;
 import com.android.internal.telephony.imsphone.ImsPhone;
 import com.android.internal.telephony.imsphone.ImsPhoneCallTracker;
+import com.android.internal.telephony.metrics.MetricsCollector;
+import com.android.internal.telephony.metrics.PersistAtomsStorage;
+import com.android.internal.telephony.metrics.VoiceCallSessionStats;
 import com.android.internal.telephony.test.SimulatedCommands;
 import com.android.internal.telephony.test.SimulatedCommandsVerifier;
 import com.android.internal.telephony.uicc.IccCardStatus;
@@ -87,8 +98,11 @@
 import com.android.internal.telephony.uicc.UiccCardApplication;
 import com.android.internal.telephony.uicc.UiccController;
 import com.android.internal.telephony.uicc.UiccProfile;
+import com.android.server.pm.PackageManagerService;
+import com.android.server.pm.permission.PermissionManagerService;
 
 import org.mockito.Mock;
+import org.mockito.Mockito;
 import org.mockito.MockitoAnnotations;
 import org.mockito.invocation.InvocationOnMock;
 import org.mockito.stubbing.Answer;
@@ -101,6 +115,7 @@
 import java.util.HashSet;
 import java.util.Iterator;
 import java.util.LinkedList;
+import java.util.List;
 import java.util.Set;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
@@ -116,6 +131,23 @@
             new ArrayList<String>(), EmergencyNumber.EMERGENCY_NUMBER_SOURCE_NETWORK_SIGNALING,
             EmergencyNumber.EMERGENCY_CALL_ROUTING_NORMAL);
 
+    private static final Field MESSAGE_QUEUE_FIELD;
+    private static final Field MESSAGE_WHEN_FIELD;
+    private static final Field MESSAGE_NEXT_FIELD;
+
+    static {
+        try {
+            MESSAGE_QUEUE_FIELD = MessageQueue.class.getDeclaredField("mMessages");
+            MESSAGE_QUEUE_FIELD.setAccessible(true);
+            MESSAGE_WHEN_FIELD = Message.class.getDeclaredField("when");
+            MESSAGE_WHEN_FIELD.setAccessible(true);
+            MESSAGE_NEXT_FIELD = Message.class.getDeclaredField("next");
+            MESSAGE_NEXT_FIELD.setAccessible(true);
+        } catch (NoSuchFieldException e) {
+            throw new RuntimeException("Failed to initialize TelephonyTest", e);
+        }
+    }
+
     @Mock
     protected GsmCdmaPhone mPhone;
     @Mock
@@ -149,6 +181,8 @@
     @Mock
     protected DcTracker mDcTracker;
     @Mock
+    protected DisplayInfoController mDisplayInfoController;
+    @Mock
     protected GsmCdmaCall mGsmCdmaCall;
     @Mock
     protected ImsCall mImsCall;
@@ -158,6 +192,10 @@
     protected SubscriptionController mSubscriptionController;
     @Mock
     protected ServiceState mServiceState;
+    @Mock
+    protected PackageManagerService mMockPackageManager;
+    @Mock
+    protected PermissionManagerService mMockPermissionManager;
 
     protected NetworkRegistrationInfo mNetworkRegistrationInfo =
             new NetworkRegistrationInfo.Builder()
@@ -167,8 +205,6 @@
     @Mock
     protected SimulatedCommandsVerifier mSimulatedCommandsVerifier;
     @Mock
-    protected IDeviceIdleController mIDeviceIdleController;
-    @Mock
     protected InboundSmsHandler mInboundSmsHandler;
     @Mock
     protected WspTypeDecoder mWspTypeDecoder;
@@ -246,14 +282,30 @@
     protected UiccCard mUiccCard;
     @Mock
     protected MultiSimSettingController mMultiSimSettingController;
+    @Mock
+    protected IccCard mIccCard;
+    @Mock
+    protected NetworkStatsManager mStatsManager;
+    @Mock
+    protected CarrierPrivilegesTracker mCarrierPrivilegesTracker;
+    @Mock
+    protected VoiceCallSessionStats mVoiceCallSessionStats;
+    @Mock
+    protected PersistAtomsStorage mPersistAtomsStorage;
+    @Mock
+    protected MetricsCollector mMetricsCollector;
 
+    protected ActivityManager mActivityManager;
     protected ImsCallProfile mImsCallProfile;
     protected TelephonyManager mTelephonyManager;
+    protected TelephonyRegistryManager mTelephonyRegistryManager;
     protected SubscriptionManager mSubscriptionManager;
     protected EuiccManager mEuiccManager;
     protected PackageManager mPackageManager;
     protected ConnectivityManager mConnectivityManager;
     protected AppOpsManager mAppOpsManager;
+    protected CarrierConfigManager mCarrierConfigManager;
+    protected UserManager mUserManager;
     protected SimulatedCommands mSimulatedCommands;
     protected ContextFixture mContextFixture;
     protected Context mContext;
@@ -262,7 +314,8 @@
     private boolean mReady;
     protected HashMap<String, IBinder> mServiceManagerMockedServices = new HashMap<>();
     protected Phone[] mPhones;
-
+    protected List<TestableLooper> mTestableLoopers = new ArrayList<>();
+    protected TestableLooper mTestableLooper;
 
     protected HashMap<Integer, ImsManager> mImsManagerInstances = new HashMap<>();
     private HashMap<InstanceKey, Object> mOldInstances = new HashMap<InstanceKey, Object>();
@@ -361,9 +414,13 @@
     protected void setUp(String tag) throws Exception {
         TAG = tag;
         MockitoAnnotations.initMocks(this);
+        TelephonyManager.disableServiceHandleCaching();
+        SubscriptionController.disableCaching();
 
         mPhones = new Phone[] {mPhone};
         mImsCallProfile = new ImsCallProfile();
+        mImsCallProfile.setCallerNumberVerificationStatus(
+                ImsCallProfile.VERIFICATION_STATUS_PASSED);
         mSimulatedCommands = new SimulatedCommands();
         mContextFixture = new ContextFixture();
         mContext = mContextFixture.getTestDouble();
@@ -376,6 +433,9 @@
         doReturn(mUiccProfile).when(mUiccCard).getUiccProfile();
 
         mTelephonyManager = (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE);
+        mActivityManager = (ActivityManager) mContext.getSystemService(Context.ACTIVITY_SERVICE);
+        mTelephonyRegistryManager = (TelephonyRegistryManager) mContext.getSystemService(
+            Context.TELEPHONY_REGISTRY_SERVICE);
         mSubscriptionManager = (SubscriptionManager) mContext.getSystemService(
                 Context.TELEPHONY_SUBSCRIPTION_SERVICE);
         mEuiccManager = (EuiccManager) mContext.getSystemService(Context.EUICC_SERVICE);
@@ -383,6 +443,9 @@
                 mContext.getSystemService(Context.CONNECTIVITY_SERVICE);
         mPackageManager = mContext.getPackageManager();
         mAppOpsManager = (AppOpsManager) mContext.getSystemService(Context.APP_OPS_SERVICE);
+        mCarrierConfigManager =
+                (CarrierConfigManager) mContext.getSystemService(Context.CARRIER_CONFIG_SERVICE);
+        mUserManager = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
 
         //mTelephonyComponentFactory
         doReturn(mTelephonyComponentFactory).when(mTelephonyComponentFactory).inject(anyString());
@@ -404,6 +467,8 @@
                 .makeIccPhoneBookInterfaceManager(nullable(Phone.class));
         doReturn(mDcTracker).when(mTelephonyComponentFactory)
                 .makeDcTracker(nullable(Phone.class), anyInt());
+        doReturn(mDisplayInfoController).when(mTelephonyComponentFactory)
+                .makeDisplayInfoController(nullable(Phone.class));
         doReturn(mWspTypeDecoder).when(mTelephonyComponentFactory)
                 .makeWspTypeDecoder(nullable(byte[].class));
         doReturn(mImsCT).when(mTelephonyComponentFactory)
@@ -412,8 +477,6 @@
                 .getCdmaSubscriptionSourceManagerInstance(nullable(Context.class),
                         nullable(CommandsInterface.class), nullable(Handler.class),
                         anyInt(), nullable(Object.class));
-        doReturn(mIDeviceIdleController).when(mTelephonyComponentFactory)
-                .getIDeviceIdleController();
         doReturn(mImsExternalCallTracker).when(mTelephonyComponentFactory)
                 .makeImsExternalCallTracker(nullable(ImsPhone.class));
         doReturn(mAppSmsManager).when(mTelephonyComponentFactory)
@@ -448,6 +511,8 @@
         doReturn(PhoneConstants.PHONE_TYPE_GSM).when(mPhone).getPhoneType();
         doReturn(mCT).when(mPhone).getCallTracker();
         doReturn(mSST).when(mPhone).getServiceStateTracker();
+        doReturn(mDeviceStateMonitor).when(mPhone).getDeviceStateMonitor();
+        doReturn(mDisplayInfoController).when(mPhone).getDisplayInfoController();
         doReturn(mEmergencyNumberTracker).when(mPhone).getEmergencyNumberTracker();
         doReturn(mCarrierSignalAgent).when(mPhone).getCarrierSignalAgent();
         doReturn(mCarrierActionAgent).when(mPhone).getCarrierActionAgent();
@@ -456,6 +521,9 @@
         doReturn(mTransportManager).when(mPhone).getTransportManager();
         doReturn(mDataEnabledSettings).when(mPhone).getDataEnabledSettings();
         doReturn(mDcTracker).when(mPhone).getDcTracker(anyInt());
+        doReturn(mCarrierPrivilegesTracker).when(mPhone).getCarrierPrivilegesTracker();
+        doReturn(mVoiceCallSessionStats).when(mPhone).getVoiceCallSessionStats();
+        doReturn(mVoiceCallSessionStats).when(mImsPhone).getVoiceCallSessionStats();
         mIccSmsInterfaceManager.mDispatchersController = mSmsDispatchersController;
 
         //mUiccController
@@ -519,15 +587,21 @@
         doReturn(mPhone).when(mInboundSmsHandler).getPhone();
         doReturn(mImsCallProfile).when(mImsCall).getCallProfile();
         doReturn(mIBinder).when(mIIntentSender).asBinder();
-        doReturn(mIIntentSender).when(mIActivityManager).getIntentSender(anyInt(),
-                nullable(String.class), nullable(IBinder.class), nullable(String.class), anyInt(),
-                nullable(Intent[].class), nullable(String[].class), anyInt(),
-                nullable(Bundle.class), anyInt());
+        doReturn(mIIntentSender).when(mIActivityManager).getIntentSenderWithFeature(anyInt(),
+                nullable(String.class), nullable(String.class), nullable(IBinder.class),
+                nullable(String.class), anyInt(), nullable(Intent[].class),
+                nullable(String[].class), anyInt(), nullable(Bundle.class), anyInt());
         doReturn(mTelephonyManager).when(mTelephonyManager).createForSubscriptionId(anyInt());
+        doReturn(true).when(mTelephonyManager).isDataCapable();
+
+        doReturn(TelephonyManager.PHONE_TYPE_GSM).when(mTelephonyManager).getPhoneType();
         doReturn(mServiceState).when(mSST).getServiceState();
         mSST.mSS = mServiceState;
         mSST.mRestrictedState = mRestrictedState;
         mServiceManagerMockedServices.put("connectivity_metrics_logger", mConnMetLoggerBinder);
+        mServiceManagerMockedServices.put("package", mMockPackageManager);
+        mServiceManagerMockedServices.put("permissionmgr", mMockPermissionManager);
+        logd("mMockPermissionManager replaced");
         doReturn(new int[]{AccessNetworkConstants.TRANSPORT_TYPE_WWAN,
                 AccessNetworkConstants.TRANSPORT_TYPE_WLAN})
                 .when(mTransportManager).getAvailableTransports();
@@ -543,6 +617,12 @@
         //SIM
         doReturn(1).when(mTelephonyManager).getSimCount();
         doReturn(1).when(mTelephonyManager).getPhoneCount();
+        doReturn(1).when(mTelephonyManager).getActiveModemCount();
+        // Have getMaxPhoneCount always return the same value with getPhoneCount by default.
+        doAnswer((invocation)->Math.max(mTelephonyManager.getActiveModemCount(),
+                mTelephonyManager.getPhoneCount()))
+                .when(mTelephonyManager).getSupportedModemCount();
+        doReturn(mStatsManager).when(mContext).getSystemService(eq(Context.NETWORK_STATS_SERVICE));
 
         //Data
         //Initial state is: userData enabled, provisioned.
@@ -557,6 +637,10 @@
                 .when(mCellularNetworkValidator).getSubIdInValidation();
         doReturn(true).when(mCellularNetworkValidator).isValidationFeatureSupported();
 
+        // Metrics
+        doReturn(null).when(mContext).getFileStreamPath(anyString());
+        doReturn(mPersistAtomsStorage).when(mMetricsCollector).getAtomsStorage();
+
         //Use reflection to mock singletons
         replaceInstance(CallManager.class, "INSTANCE", null, mCallManager);
         replaceInstance(TelephonyComponentFactory.class, "sInstance", null,
@@ -578,6 +662,7 @@
         replaceInstance(IntentBroadcaster.class, "sIntentBroadcaster", null, mIntentBroadcaster);
         replaceInstance(TelephonyManager.class, "sInstance", null,
                 mContext.getSystemService(Context.TELEPHONY_SERVICE));
+        replaceInstance(TelephonyManager.class, "sServiceHandleCacheEnabled", null, false);
         replaceInstance(PhoneFactory.class, "sMadeDefaults", null, true);
         replaceInstance(PhoneFactory.class, "sPhone", null, mPhone);
         replaceInstance(PhoneFactory.class, "sPhones", null, mPhones);
@@ -590,20 +675,40 @@
         replaceInstance(MultiSimSettingController.class, "sInstance", null,
                 mMultiSimSettingController);
         replaceInstance(SubscriptionInfoUpdater.class, "sIsSubInfoInitialized", null, true);
+        replaceInstance(PhoneFactory.class, "sCommandsInterfaces", null,
+                new CommandsInterface[] {mSimulatedCommands});
+        replaceInstance(PhoneFactory.class, "sMetricsCollector", null, mMetricsCollector);
 
         assertNotNull("Failed to set up SubscriptionController singleton",
                 SubscriptionController.getInstance());
         setReady(false);
+        // create default TestableLooper for test and add to list of monitored loopers
+        mTestableLooper = TestableLooper.get(TelephonyTest.this);
+        if (mTestableLooper != null) {
+            monitorTestableLooper(mTestableLooper);
+        }
     }
 
     protected void tearDown() throws Exception {
+        // Ensure there are no references to handlers between tests.
+        PhoneConfigurationManager.unregisterAllMultiSimConfigChangeRegistrants();
+        // unmonitor TestableLooper for TelephonyTest class
+        if (mTestableLooper != null) {
+            unmonitorTestableLooper(mTestableLooper);
+        }
+        // destroy all newly created TestableLoopers so they can be reused
+        for (TestableLooper looper : mTestableLoopers) {
+            looper.destroy();
+        }
+        TestableLooper.remove(TelephonyTest.this);
 
         mSimulatedCommands.dispose();
-
         SharedPreferences sharedPreferences = mContext.getSharedPreferences((String) null, 0);
         sharedPreferences.edit().clear().commit();
 
         restoreInstances();
+        TelephonyManager.enableServiceHandleCaching();
+        SubscriptionController.enableCaching();
     }
 
     protected static void logd(String s) {
@@ -667,24 +772,36 @@
     protected void setupMockPackagePermissionChecks() throws Exception {
         doReturn(new String[]{TAG}).when(mPackageManager).getPackagesForUid(anyInt());
         doReturn(mPackageInfo).when(mPackageManager).getPackageInfo(eq(TAG), anyInt());
-        doReturn(mPackageInfo).when(mPackageManager).getPackageInfoAsUser(
-                eq(TAG), anyInt(), anyInt());
     }
 
     protected void setupMocksForTelephonyPermissions() throws Exception {
+        setupMocksForTelephonyPermissions(Build.VERSION_CODES.Q);
+    }
+
+    protected void setupMocksForTelephonyPermissions(int targetSdkVersion)
+            throws Exception {
         // If the calling package does not meet the new requirements for device identifier access
         // TelephonyPermissions will query the PackageManager for the ApplicationInfo of the package
         // to determine the target SDK. For apps targeting Q a SecurityException is thrown
         // regardless of if the package satisfies the previous requirements for device ID access.
-        mApplicationInfo.targetSdkVersion = Build.VERSION_CODES.Q;
-        doReturn(mApplicationInfo).when(mPackageManager).getApplicationInfoAsUser(eq(TAG), anyInt(),
-                anyInt());
 
-        // TelephonyPermissions also checks to see if the calling package has been granted
-        // identifier access via an appop; ensure this query does not allow identifier access for
-        // any packages.
-        doReturn(AppOpsManager.MODE_DEFAULT).when(mAppOpsManager).noteOpNoThrow(
-                eq(AppOpsManager.OPSTR_READ_DEVICE_IDENTIFIERS), anyInt(), anyString());
+        // Any tests that query for SubscriptionInfo objects will trigger a phone number access
+        // check that will first query the ApplicationInfo as apps targeting R+ can no longer
+        // access the phone number with the READ_PHONE_STATE permission and instead must meet one of
+        // the other requirements. This ApplicationInfo is generalized to any package name since
+        // some tests will simulate invocation from other packages.
+        mApplicationInfo.targetSdkVersion = targetSdkVersion;
+        doReturn(mApplicationInfo).when(mPackageManager).getApplicationInfoAsUser(anyString(),
+                anyInt(), any());
+
+        // TelephonyPermissions uses a SystemAPI to check if the calling package meets any of the
+        // generic requirements for device identifier access (currently READ_PRIVILEGED_PHONE_STATE,
+        // appop, and device / profile owner checks. This sets up the PermissionManager to return
+        // that access requirements are met.
+        setIdentifierAccess(true);
+        PermissionManager permissionManager = new PermissionManager(mContext, null,
+                mMockPermissionManager);
+        doReturn(permissionManager).when(mContext).getSystemService(eq(Context.PERMISSION_SERVICE));
 
         // TelephonyPermissions queries DeviceConfig to determine if the identifier access
         // restrictions should be enabled; this results in a NPE when DeviceConfig uses
@@ -709,6 +826,28 @@
                 "mContentProvider", providerHolder, iContentProvider);
     }
 
+    protected void setIdentifierAccess(boolean hasAccess) {
+        doReturn(hasAccess ? PackageManager.PERMISSION_GRANTED
+                : PackageManager.PERMISSION_DENIED).when(
+                mMockPermissionManager).checkDeviceIdentifierAccess(any(), any(), any(), anyInt(),
+                anyInt());
+    }
+
+    protected void setCarrierPrivileges(boolean hasCarrierPrivileges) {
+        doReturn(mTelephonyManager).when(mTelephonyManager).createForSubscriptionId(anyInt());
+        doReturn(hasCarrierPrivileges ? TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS
+                : TelephonyManager.CARRIER_PRIVILEGE_STATUS_NO_ACCESS).when(
+                mTelephonyManager).getCarrierPrivilegeStatus(anyInt());
+    }
+
+    protected void setCarrierPrivilegesForSubId(boolean hasCarrierPrivileges, int subId) {
+        TelephonyManager mockTelephonyManager = Mockito.mock(TelephonyManager.class);
+        doReturn(mockTelephonyManager).when(mTelephonyManager).createForSubscriptionId(subId);
+        doReturn(hasCarrierPrivileges ? TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS
+                : TelephonyManager.CARRIER_PRIVILEGE_STATUS_NO_ACCESS).when(
+                mockTelephonyManager).getCarrierPrivilegeStatus(anyInt());
+    }
+
     protected final void waitForHandlerAction(Handler h, long timeoutMillis) {
         final CountDownLatch lock = new CountDownLatch(1);
         h.post(lock::countDown);
@@ -721,16 +860,30 @@
         }
     }
 
-    protected final void waitForHandlerActionDelayed(Handler h, long timeoutMillis, long delayMs) {
-        final CountDownLatch lock = new CountDownLatch(1);
-        h.postDelayed(lock::countDown, delayMs);
-        while (lock.getCount() > 0) {
+    /**
+     * Wait for up to 1 second for the handler message queue to clear.
+     */
+    protected final void waitForLastHandlerAction(Handler h) {
+        CountDownLatch lock = new CountDownLatch(1);
+        // Allow the handler to start work on stuff.
+        h.postDelayed(lock::countDown, 100);
+        int timeoutCount = 0;
+        while (timeoutCount < 5) {
             try {
-                lock.await(timeoutMillis, TimeUnit.MILLISECONDS);
+                if (lock.await(200, TimeUnit.MILLISECONDS)) {
+                    // no messages in queue, stop waiting.
+                    if (!h.hasMessagesOrCallbacks()) break;
+                    lock = new CountDownLatch(1);
+                    // Delay to allow the handler thread to start work on stuff.
+                    h.postDelayed(lock::countDown, 100);
+                }
+
             } catch (InterruptedException e) {
                 // do nothing
             }
+            timeoutCount++;
         }
+        assertTrue("Handler was not empty before timeout elapsed", timeoutCount < 5);
     }
 
     protected final EmergencyNumber getTestEmergencyNumber() {
@@ -748,4 +901,74 @@
         }
         return null;
     }
+
+    /**
+     * Add a TestableLooper to the list of monitored loopers
+     * @param looper added if it doesn't already exist
+     */
+    public void monitorTestableLooper(TestableLooper looper) {
+        if (!mTestableLoopers.contains(looper)) {
+            mTestableLoopers.add(looper);
+        }
+    }
+
+    /**
+     * Remove a TestableLooper from the list of monitored loopers
+     * @param looper removed if it does exist
+     */
+    public void unmonitorTestableLooper(TestableLooper looper) {
+        if (mTestableLoopers.contains(looper)) {
+            mTestableLoopers.remove(looper);
+        }
+    }
+
+    /**
+     * Handle all messages that can be processed at the current time
+     * for all monitored TestableLoopers
+     */
+    public void processAllMessages() {
+        if (mTestableLoopers.isEmpty()) {
+            fail("mTestableLoopers is empty. Please make sure to add @RunWithLooper annotation");
+        }
+        while (!areAllTestableLoopersIdle()) {
+            for (TestableLooper looper : mTestableLoopers) looper.processAllMessages();
+        }
+    }
+
+    /**
+     * Check if there are any messages to be processed in any monitored TestableLooper
+     * Delayed messages to be handled at a later time will be ignored
+     * @return true if there are no messages that can be handled at the current time
+     *         across all monitored TestableLoopers
+     */
+    private boolean areAllTestableLoopersIdle() {
+        for (TestableLooper looper : mTestableLoopers) {
+            if (!looper.getLooper().getQueue().isIdle()) return false;
+        }
+        return true;
+    }
+
+    /**
+     * Effectively moves time forward by reducing the time of all messages
+     * for all monitored TestableLoopers
+     * @param milliSeconds number of milliseconds to move time forward by
+     */
+    public void moveTimeForward(long milliSeconds) {
+        for (TestableLooper looper : mTestableLoopers) {
+            MessageQueue queue = looper.getLooper().getQueue();
+            try {
+                Message msg = (Message) MESSAGE_QUEUE_FIELD.get(queue);
+                while (msg != null) {
+                    long updatedWhen = msg.getWhen() - milliSeconds;
+                    if (updatedWhen < 0) {
+                        updatedWhen = 0;
+                    }
+                    MESSAGE_WHEN_FIELD.set(msg, updatedWhen);
+                    msg = (Message) MESSAGE_NEXT_FIELD.get(msg);
+                }
+            } catch (IllegalAccessException e) {
+                throw new RuntimeException("Access failed in TelephonyTest", e);
+            }
+        }
+    }
 }
diff --git a/tests/telephonytests/src/com/android/internal/telephony/TimeZoneLookupHelperTest.java b/tests/telephonytests/src/com/android/internal/telephony/TimeZoneLookupHelperTest.java
deleted file mode 100644
index e2cf307..0000000
--- a/tests/telephonytests/src/com/android/internal/telephony/TimeZoneLookupHelperTest.java
+++ /dev/null
@@ -1,351 +0,0 @@
-/*
- * Copyright 2017 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;
-
-import com.android.internal.telephony.TimeZoneLookupHelper.CountryResult;
-import com.android.internal.telephony.TimeZoneLookupHelper.OffsetResult;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertTrue;
-
-import libcore.timezone.TimeZoneFinder;
-
-import org.junit.Before;
-import org.junit.Test;
-
-import java.util.Calendar;
-import java.util.Date;
-import java.util.GregorianCalendar;
-import java.util.List;
-import java.util.TimeZone;
-import java.util.concurrent.TimeUnit;
-
-public class TimeZoneLookupHelperTest {
-    // Note: Historical dates are used to avoid the test breaking due to data changes.
-    /* Arbitrary summer date in the Northern hemisphere. */
-    private static final long NH_SUMMER_TIME_MILLIS = createUtcTime(2015, 6, 20, 1, 2, 3);
-    /* Arbitrary winter date in the Northern hemisphere. */
-    private static final long NH_WINTER_TIME_MILLIS = createUtcTime(2015, 1, 20, 1, 2, 3);
-
-    private TimeZoneLookupHelper mTimeZoneLookupHelper;
-
-    @Before
-    public void setUp() {
-        mTimeZoneLookupHelper = new TimeZoneLookupHelper();
-    }
-
-    @Test
-    public void testLookupByNitzdByNitz() {
-        // Historical dates are used to avoid the test breaking due to data changes.
-        // However, algorithm updates may change the exact time zone returned, though it shouldn't
-        // ever be a less exact match.
-        long nhSummerTimeMillis = createUtcTime(2015, 6, 20, 1, 2, 3);
-        long nhWinterTimeMillis = createUtcTime(2015, 1, 20, 1, 2, 3);
-
-        String nhSummerTimeString = "15/06/20,01:02:03";
-        String nhWinterTimeString = "15/01/20,01:02:03";
-
-        // Tests for London, UK.
-        {
-            String lonSummerTimeString = nhSummerTimeString + "+4";
-            int lonSummerOffsetMillis = (int) TimeUnit.HOURS.toMillis(1);
-            int lonSummerDstOffsetMillis = (int) TimeUnit.HOURS.toMillis(1);
-
-            String lonWinterTimeString = nhWinterTimeString + "+0";
-            int lonWinterOffsetMillis = 0;
-            int lonWinterDstOffsetMillis = 0;
-
-            OffsetResult lookupResult;
-
-            // Summer, known DST state (DST == true).
-            NitzData lonSummerNitzDataWithOffset = NitzData.parse(lonSummerTimeString + ",4");
-            lookupResult = mTimeZoneLookupHelper.lookupByNitz(lonSummerNitzDataWithOffset);
-            assertOffsetResultZoneOffsets(nhSummerTimeMillis, lonSummerOffsetMillis,
-                    lonSummerDstOffsetMillis, lookupResult);
-            assertOffsetResultMetadata(false, lookupResult);
-
-            // Winter, known DST state (DST == false).
-            NitzData lonWinterNitzDataWithOffset = NitzData.parse(lonWinterTimeString + ",0");
-            lookupResult = mTimeZoneLookupHelper.lookupByNitz(lonWinterNitzDataWithOffset);
-            assertOffsetResultZoneOffsets(nhWinterTimeMillis, lonWinterOffsetMillis,
-                    lonWinterDstOffsetMillis, lookupResult);
-            assertOffsetResultMetadata(false, lookupResult);
-
-            // Summer, unknown DST state
-            NitzData lonSummerNitzDataWithoutOffset = NitzData.parse(lonSummerTimeString);
-            lookupResult = mTimeZoneLookupHelper.lookupByNitz(lonSummerNitzDataWithoutOffset);
-            assertOffsetResultZoneOffsets(nhSummerTimeMillis, lonSummerOffsetMillis, null,
-                    lookupResult);
-            assertOffsetResultMetadata(false, lookupResult);
-
-            // Winter, unknown DST state
-            NitzData lonWinterNitzDataWithoutOffset = NitzData.parse(lonWinterTimeString);
-            lookupResult = mTimeZoneLookupHelper.lookupByNitz(lonWinterNitzDataWithoutOffset);
-            assertOffsetResultZoneOffsets(nhWinterTimeMillis, lonWinterOffsetMillis, null,
-                    lookupResult);
-            assertOffsetResultMetadata(false, lookupResult);
-        }
-
-        // Tests for Mountain View, CA, US.
-        {
-            String mtvSummerTimeString = nhSummerTimeString + "-32";
-            int mtvSummerOffsetMillis = (int) TimeUnit.HOURS.toMillis(-8);
-            int mtvSummerDstOffsetMillis = (int) TimeUnit.HOURS.toMillis(1);
-
-            String mtvWinterTimeString = nhWinterTimeString + "-28";
-            int mtvWinterOffsetMillis = (int) TimeUnit.HOURS.toMillis(-7);
-            int mtvWinterDstOffsetMillis = 0;
-
-            OffsetResult lookupResult;
-
-            // Summer, known DST state (DST == true).
-            NitzData mtvSummerNitzDataWithOffset = NitzData.parse(mtvSummerTimeString + ",4");
-            lookupResult = mTimeZoneLookupHelper.lookupByNitz(mtvSummerNitzDataWithOffset);
-            assertOffsetResultZoneOffsets(nhSummerTimeMillis, mtvSummerOffsetMillis,
-                    mtvSummerDstOffsetMillis, lookupResult);
-            assertOffsetResultMetadata(false, lookupResult);
-
-            // Winter, known DST state (DST == false).
-            NitzData mtvWinterNitzDataWithOffset = NitzData.parse(mtvWinterTimeString + ",0");
-            lookupResult = mTimeZoneLookupHelper.lookupByNitz(mtvWinterNitzDataWithOffset);
-            assertOffsetResultZoneOffsets(nhWinterTimeMillis, mtvWinterOffsetMillis,
-                    mtvWinterDstOffsetMillis, lookupResult);
-            assertOffsetResultMetadata(false, lookupResult);
-
-            // Summer, unknown DST state
-            NitzData mtvSummerNitzDataWithoutOffset = NitzData.parse(mtvSummerTimeString);
-            lookupResult = mTimeZoneLookupHelper.lookupByNitz(mtvSummerNitzDataWithoutOffset);
-            assertOffsetResultZoneOffsets(nhSummerTimeMillis, mtvSummerOffsetMillis, null,
-                    lookupResult);
-            assertOffsetResultMetadata(false, lookupResult);
-
-            // Winter, unknown DST state
-            NitzData mtvWinterNitzDataWithoutOffset = NitzData.parse(mtvWinterTimeString);
-            lookupResult = mTimeZoneLookupHelper.lookupByNitz(mtvWinterNitzDataWithoutOffset);
-            assertOffsetResultZoneOffsets(nhWinterTimeMillis, mtvWinterOffsetMillis, null,
-                    lookupResult);
-            assertOffsetResultMetadata(false, lookupResult);
-        }
-    }
-
-    @Test
-    public void testLookupByNitzCountry() {
-        // Historical dates are used to avoid the test breaking due to data changes.
-        // However, algorithm updates may change the exact time zone returned, though it shouldn't
-        // ever be a less exact match.
-        long nhSummerTimeMillis = createUtcTime(2015, 6, 20, 1, 2, 3);
-        long nhWinterTimeMillis = createUtcTime(2015, 1, 20, 1, 2, 3);
-
-        // Two countries in the northern hemisphere that share the same Winter and Summer DST
-        // offsets at the dates being used.
-        String deIso = "DE"; // Germany
-        String adIso = "AD"; // Andora
-        String summerTimeNitzString = "15/06/20,01:02:03+8";
-        String winterTimeNitzString = "15/01/20,01:02:03+4";
-
-        // Summer, known DST state (DST == true).
-        {
-            String summerTimeNitzStringWithDst = summerTimeNitzString + ",1";
-            NitzData nitzData = NitzData.parse(summerTimeNitzStringWithDst);
-            int expectedUtcOffset = (int) TimeUnit.HOURS.toMillis(2);
-            Integer expectedDstOffset = (int) TimeUnit.HOURS.toMillis(1);
-            assertEquals(expectedUtcOffset, nitzData.getLocalOffsetMillis());
-            assertEquals(expectedDstOffset, nitzData.getDstAdjustmentMillis());
-
-            OffsetResult expectedResult;
-
-            OffsetResult deSummerWithDstResult =
-                    mTimeZoneLookupHelper.lookupByNitzCountry(nitzData, deIso);
-            expectedResult = new OffsetResult("Europe/Berlin", false /* isOnlyMatch */);
-            assertEquals(expectedResult, deSummerWithDstResult);
-            assertOffsetResultZoneOffsets(nhSummerTimeMillis, expectedUtcOffset, expectedDstOffset,
-                    deSummerWithDstResult);
-
-            OffsetResult adSummerWithDstResult =
-                    mTimeZoneLookupHelper.lookupByNitzCountry(nitzData, adIso);
-            expectedResult = new OffsetResult("Europe/Andorra", true /* isOnlyMatch */);
-            assertEquals(expectedResult, adSummerWithDstResult);
-            assertOffsetResultZoneOffsets(nhSummerTimeMillis, expectedUtcOffset, expectedDstOffset,
-                    adSummerWithDstResult);
-            assertOffsetResultZoneCountry(adIso, adSummerWithDstResult);
-        }
-
-        // Winter, known DST state (DST == false)
-        {
-            String winterTimeNitzStringWithDst = winterTimeNitzString + ",0";
-            NitzData nitzData = NitzData.parse(winterTimeNitzStringWithDst);
-            int expectedUtcOffset = (int) TimeUnit.HOURS.toMillis(1);
-            Integer expectedDstOffset = 0;
-            assertEquals(expectedUtcOffset, nitzData.getLocalOffsetMillis());
-            assertEquals(expectedDstOffset, nitzData.getDstAdjustmentMillis());
-
-            OffsetResult expectedResult;
-
-            OffsetResult deWinterWithDstResult =
-                    mTimeZoneLookupHelper.lookupByNitzCountry(nitzData, deIso);
-            expectedResult = new OffsetResult("Europe/Berlin", false /* isOnlyMatch */);
-            assertEquals(expectedResult, deWinterWithDstResult);
-            assertOffsetResultZoneOffsets(nhWinterTimeMillis, expectedUtcOffset, expectedDstOffset,
-                    deWinterWithDstResult);
-
-            OffsetResult adWinterWithDstResult =
-                    mTimeZoneLookupHelper.lookupByNitzCountry(nitzData, adIso);
-            expectedResult = new OffsetResult("Europe/Andorra", true /* isOnlyMatch */);
-            assertEquals(expectedResult, adWinterWithDstResult);
-            assertOffsetResultZoneOffsets(nhWinterTimeMillis, expectedUtcOffset, expectedDstOffset,
-                    adWinterWithDstResult);
-        }
-
-        // Summer, unknown DST state
-        // For historic reasons, GuessZoneIdByNitzCountry() does not handle unknown DST state - it
-        // assumes that "unknown DST" means "no DST": This leads to no match when DST is actually in
-        // force.
-        {
-            NitzData nitzData = NitzData.parse(summerTimeNitzString);
-            int expectedUtcOffset = (int) TimeUnit.HOURS.toMillis(2);
-            Integer expectedDstOffset = null; // Unknown
-            assertEquals(expectedUtcOffset, nitzData.getLocalOffsetMillis());
-            assertEquals(expectedDstOffset, nitzData.getDstAdjustmentMillis());
-
-            OffsetResult deSummerUnknownDstResult =
-                    mTimeZoneLookupHelper.lookupByNitzCountry(nitzData, deIso);
-            assertNull(deSummerUnknownDstResult);
-
-            OffsetResult adSummerUnknownDstResult =
-                    mTimeZoneLookupHelper.lookupByNitzCountry(nitzData, adIso);
-            assertNull(adSummerUnknownDstResult);
-        }
-
-        // Winter, unknown DST state
-        {
-            NitzData nitzData = NitzData.parse(winterTimeNitzString);
-            int expectedUtcOffset = (int) TimeUnit.HOURS.toMillis(1);
-            Integer expectedDstOffset = null; // Unknown
-            assertEquals(expectedUtcOffset, nitzData.getLocalOffsetMillis());
-            assertEquals(expectedDstOffset, nitzData.getDstAdjustmentMillis());
-
-            OffsetResult expectedResult;
-
-            OffsetResult deWinterUnknownDstResult =
-                    mTimeZoneLookupHelper.lookupByNitzCountry(nitzData, deIso);
-            expectedResult = new OffsetResult("Europe/Berlin", false /* isOnlyMatch */);
-            assertEquals(expectedResult, deWinterUnknownDstResult);
-            assertOffsetResultZoneOffsets(nhWinterTimeMillis, expectedUtcOffset, expectedDstOffset,
-                    deWinterUnknownDstResult);
-
-            OffsetResult adWinterUnknownDstResult =
-                    mTimeZoneLookupHelper.lookupByNitzCountry(nitzData, adIso);
-            expectedResult = new OffsetResult("Europe/Andorra", true /* isOnlyMatch */);
-            assertEquals(expectedResult, adWinterUnknownDstResult);
-            assertOffsetResultZoneOffsets(nhWinterTimeMillis, expectedUtcOffset, expectedDstOffset,
-                    adWinterUnknownDstResult);
-        }
-    }
-
-    @Test
-    public void testLookupByCountry() {
-        // Historical dates are used to avoid the test breaking due to data changes.
-        long nhSummerTimeMillis = createUtcTime(2015, 6, 20, 1, 2, 3);
-        long nhWinterTimeMillis = createUtcTime(2015, 1, 20, 1, 2, 3);
-
-        CountryResult expectedResult;
-
-        // GB has one time zone.
-        expectedResult = new CountryResult("Europe/London", true /* allZonesHaveSameOffset */,
-                nhSummerTimeMillis);
-        assertEquals(expectedResult,
-                mTimeZoneLookupHelper.lookupByCountry("gb", nhSummerTimeMillis));
-        expectedResult = new CountryResult("Europe/London", true /* allZonesHaveSameOffset */,
-                nhWinterTimeMillis);
-        assertEquals(expectedResult,
-                mTimeZoneLookupHelper.lookupByCountry("gb", nhWinterTimeMillis));
-
-        // DE has two time zones according to data, but they agree on offset.
-        expectedResult = new CountryResult("Europe/Berlin", true /* allZonesHaveSameOffset */,
-                nhSummerTimeMillis);
-        assertEquals(expectedResult,
-                mTimeZoneLookupHelper.lookupByCountry("de", nhSummerTimeMillis));
-        expectedResult = new CountryResult("Europe/Berlin", true /* allZonesHaveSameOffset */,
-                nhWinterTimeMillis);
-        assertEquals(expectedResult,
-                mTimeZoneLookupHelper.lookupByCountry("de", nhWinterTimeMillis));
-
-        // US has many time zones that have different offsets.
-        expectedResult = new CountryResult("America/New_York", false /* allZonesHaveSameOffset */,
-                nhSummerTimeMillis);
-        assertEquals(expectedResult,
-                mTimeZoneLookupHelper.lookupByCountry("us", nhSummerTimeMillis));
-        expectedResult = new CountryResult("America/New_York", false /* allZonesHaveSameOffset */,
-                nhWinterTimeMillis);
-        assertEquals(expectedResult,
-                mTimeZoneLookupHelper.lookupByCountry("us", nhWinterTimeMillis));
-    }
-
-    @Test
-    public void testCountryUsesUtc() {
-        assertFalse(mTimeZoneLookupHelper.countryUsesUtc("us", NH_SUMMER_TIME_MILLIS));
-        assertFalse(mTimeZoneLookupHelper.countryUsesUtc("us", NH_WINTER_TIME_MILLIS));
-        assertFalse(mTimeZoneLookupHelper.countryUsesUtc("gb", NH_SUMMER_TIME_MILLIS));
-        assertTrue(mTimeZoneLookupHelper.countryUsesUtc("gb", NH_WINTER_TIME_MILLIS));
-    }
-
-    private static void assertOffsetResultZoneCountry(
-            String isoCountryCode, OffsetResult lookupResult) {
-        String timeZoneId = lookupResult.zoneId;
-        List<String> zoneIdsByCountry =
-                TimeZoneFinder.getInstance().lookupTimeZoneIdsByCountry(isoCountryCode);
-        assertTrue(timeZoneId + " must be used in " + isoCountryCode,
-                zoneIdsByCountry.contains(timeZoneId));
-    }
-
-    /**
-     * Assert the time zone in the OffsetResult has the expected properties at the specified time.
-     */
-    private static void assertOffsetResultZoneOffsets(long time, int expectedOffsetAtTime,
-            Integer expectedDstAtTime, OffsetResult lookupResult) {
-
-        TimeZone timeZone = TimeZone.getTimeZone(lookupResult.zoneId);
-        GregorianCalendar calendar = new GregorianCalendar(timeZone);
-        calendar.setTimeInMillis(time);
-        int actualOffsetAtTime =
-                calendar.get(Calendar.ZONE_OFFSET) + calendar.get(Calendar.DST_OFFSET);
-        assertEquals(expectedOffsetAtTime, actualOffsetAtTime);
-
-        if (expectedDstAtTime != null) {
-            Date date = new Date(time);
-            assertEquals(expectedDstAtTime > 0, timeZone.inDaylightTime(date));
-
-            // The code under test assumes DST means +1 in all cases,
-            // This code makes fewer assumptions.
-            assertEquals(expectedDstAtTime.intValue(), calendar.get(Calendar.DST_OFFSET));
-        }
-    }
-
-    private static void assertOffsetResultMetadata(boolean isOnlyMatch, OffsetResult lookupResult) {
-        assertEquals(isOnlyMatch, lookupResult.isOnlyMatch);
-    }
-
-    private static long createUtcTime(
-            int year, int monthOfYear, int dayOfMonth, int hourOfDay, int minute, int second) {
-        GregorianCalendar calendar = new GregorianCalendar(TimeZone.getTimeZone("UTC"));
-        calendar.clear(); // Clear millis, etc.
-        calendar.set(year, monthOfYear - 1, dayOfMonth, hourOfDay, minute, second);
-        return calendar.getTimeInMillis();
-    }
-}
diff --git a/tests/telephonytests/src/com/android/internal/telephony/WapPushOverSmsTest.java b/tests/telephonytests/src/com/android/internal/telephony/WapPushOverSmsTest.java
index 284a859..48adb64 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/WapPushOverSmsTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/WapPushOverSmsTest.java
@@ -45,7 +45,7 @@
 
 public class WapPushOverSmsTest extends TelephonyTest {
     @Mock
-    protected IMms.Stub mIMmsStub;
+    protected ISms.Stub mISmsStub;
 
     private WapPushOverSms mWapPushOverSmsUT;
 
@@ -55,8 +55,8 @@
 
         // Note that this replaces only cached services in ServiceManager. If a service is not found
         // in the cache, a real instance is used.
-        mServiceManagerMockedServices.put("imms", mIMmsStub);
-        doReturn(mIMmsStub).when(mIMmsStub).queryLocalInterface(anyString());
+        mServiceManagerMockedServices.put("isms", mISmsStub);
+        doReturn(mISmsStub).when(mISmsStub).queryLocalInterface(anyString());
 
         mWapPushOverSmsUT = new WapPushOverSms(mContext);
     }
@@ -84,12 +84,13 @@
                 (byte) 0xFF
         };
 
-        mWapPushOverSmsUT.dispatchWapPdu(pdu, null, mInboundSmsHandler, "123456", 0);
+        long messageId = 9989L;
+        mWapPushOverSmsUT.dispatchWapPdu(pdu, null, mInboundSmsHandler, "123456", 0, messageId);
 
         ArgumentCaptor<Intent> intentArgumentCaptor = ArgumentCaptor.forClass(Intent.class);
         verify(mInboundSmsHandler).dispatchIntent(intentArgumentCaptor.capture(),
                 eq(android.Manifest.permission.RECEIVE_WAP_PUSH),
-                eq(AppOpsManager.OP_RECEIVE_WAP_PUSH),
+                eq(AppOpsManager.OPSTR_RECEIVE_WAP_PUSH),
                 nullable(Bundle.class),
                 isNull(BroadcastReceiver.class),
                 eq(UserHandle.SYSTEM),
@@ -99,6 +100,7 @@
         assertEquals(0xFF, intent.getIntExtra("transactionId", 0));
         assertEquals(0x06, intent.getIntExtra("pduType", 0));
         assertEquals("123456", intent.getStringExtra("address"));
+        assertEquals(messageId, intent.getLongExtra("messageId", 0L));
 
         byte[] header = intent.getByteArrayExtra("header");
         assertEquals(2, header.length);
@@ -118,7 +120,7 @@
 
     @Test @SmallTest
     public void testDispatchWapPduFromBlockedNumber_noIntentsDispatched() throws Exception {
-        when(mIMmsStub.getCarrierConfigValues(anyInt())).thenReturn(new Bundle());
+        when(mISmsStub.getCarrierConfigValuesForSubscriber(anyInt())).thenReturn(new Bundle());
 
         mFakeBlockedNumberContentProvider.mBlockedNumbers.add("16178269168");
 
@@ -139,11 +141,11 @@
                 111, 109, 47, 115, 97, 100, 102, 100, 100, 0};
 
         assertEquals(Telephony.Sms.Intents.RESULT_SMS_HANDLED,
-                mWapPushOverSmsUT.dispatchWapPdu(pdu, null, mInboundSmsHandler, null, 0));
+                mWapPushOverSmsUT.dispatchWapPdu(pdu, null, mInboundSmsHandler, null, 0, 0L));
         verify(mInboundSmsHandler, never()).dispatchIntent(
                 any(Intent.class),
                 any(String.class),
-                anyInt(),
+                any(String.class),
                 any(Bundle.class),
                 any(BroadcastReceiver.class),
                 any(UserHandle.class),
diff --git a/tests/telephonytests/src/com/android/internal/telephony/cdma/CdmaInboundSmsHandlerTest.java b/tests/telephonytests/src/com/android/internal/telephony/cdma/CdmaInboundSmsHandlerTest.java
index 632f46f..a6d30f9 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/cdma/CdmaInboundSmsHandlerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/cdma/CdmaInboundSmsHandlerTest.java
@@ -20,6 +20,7 @@
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assert.fail;
 import static org.mockito.ArgumentMatchers.anyLong;
 import static org.mockito.ArgumentMatchers.nullable;
@@ -35,13 +36,14 @@
 import android.content.Intent;
 import android.database.Cursor;
 import android.os.AsyncResult;
-import android.os.HandlerThread;
 import android.os.RemoteException;
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.provider.Telephony;
 import android.test.mock.MockContentResolver;
 import android.test.suitebuilder.annotation.MediumTest;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
 
 import androidx.test.filters.FlakyTest;
 
@@ -58,12 +60,15 @@
 import org.junit.Before;
 import org.junit.Ignore;
 import org.junit.Test;
+import org.junit.runner.RunWith;
 import org.mockito.ArgumentCaptor;
 import org.mockito.Mock;
 
 import java.lang.reflect.Field;
 import java.lang.reflect.Method;
 
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper
 public class CdmaInboundSmsHandlerTest extends TelephonyTest {
     @Mock
     private SmsStorageMonitor mSmsStorageMonitor;
@@ -73,27 +78,12 @@
     private SmsMessage mCdmaSmsMessage;
 
     private CdmaInboundSmsHandler mCdmaInboundSmsHandler;
-    private CdmaInboundSmsHandlerTestHandler mCdmaInboundSmsHandlerTestHandler;
     private SmsEnvelope mSmsEnvelope = new SmsEnvelope();
     private FakeSmsContentProvider mContentProvider;
     private InboundSmsTracker mInboundSmsTracker;
     private byte[] mSmsPdu = new byte[]{(byte) 0xFF, (byte) 0xFF, (byte) 0xFF};
     private int mSubId0 = 0;
 
-    private class CdmaInboundSmsHandlerTestHandler extends HandlerThread {
-
-        private CdmaInboundSmsHandlerTestHandler(String name) {
-            super(name);
-        }
-
-        @Override
-        public void onLooperPrepared() {
-            mCdmaInboundSmsHandler = CdmaInboundSmsHandler.makeInboundSmsHandler(mContext,
-                    mSmsStorageMonitor, mPhone, null);
-            setReady(true);
-        }
-    }
-
     private IState getCurrentState() {
         try {
             Method method = StateMachine.class.getDeclaredMethod("getCurrentState");
@@ -130,6 +120,7 @@
         doReturn(true).when(mSmsStorageMonitor).isStorageAvailable();
 
         mInboundSmsTracker = new InboundSmsTracker(
+                mContext,
                 mSmsPdu, /* pdu */
                 System.currentTimeMillis(), /* timestamp */
                 -1, /* destPort */
@@ -142,23 +133,26 @@
                 mSubId0);
 
         doReturn(mInboundSmsTracker).when(mTelephonyComponentFactory)
-                .makeInboundSmsTracker(nullable(byte[].class), anyLong(), anyInt(), anyBoolean(),
+                .makeInboundSmsTracker(any(Context.class), nullable(byte[].class), anyLong(),
+                anyInt(), anyBoolean(),
                 anyBoolean(), nullable(String.class), nullable(String.class),
                 nullable(String.class), anyBoolean(), anyInt());
         doReturn(mInboundSmsTracker).when(mTelephonyComponentFactory)
-                .makeInboundSmsTracker(nullable(byte[].class), anyLong(), anyInt(), anyBoolean(),
+                .makeInboundSmsTracker(any(Context.class), nullable(byte[].class), anyLong(),
+                anyInt(), anyBoolean(),
                 nullable(String.class), nullable(String.class), anyInt(), anyInt(),
                 anyInt(), anyBoolean(), nullable(String.class), anyBoolean(), anyInt());
         doReturn(mInboundSmsTracker).when(mTelephonyComponentFactory)
-                .makeInboundSmsTracker(nullable(Cursor.class), anyBoolean());
+                .makeInboundSmsTracker(any(Context.class), nullable(Cursor.class), anyBoolean());
 
         mContentProvider = new FakeSmsContentProvider();
         ((MockContentResolver)mContext.getContentResolver()).addProvider(
                 Telephony.Sms.CONTENT_URI.getAuthority(), mContentProvider);
 
-        mCdmaInboundSmsHandlerTestHandler = new CdmaInboundSmsHandlerTestHandler(TAG);
-        mCdmaInboundSmsHandlerTestHandler.start();
-        waitUntilReady();
+        mCdmaInboundSmsHandler = CdmaInboundSmsHandler.makeInboundSmsHandler(mContext,
+            mSmsStorageMonitor, mPhone, null);
+        monitorTestableLooper(new TestableLooper(mCdmaInboundSmsHandler.getHandler().getLooper()));
+        processAllMessages();
     }
 
     @After
@@ -166,13 +160,13 @@
         // wait for wakelock to be released; timeout at 10s
         int i = 0;
         while (mCdmaInboundSmsHandler.getWakeLock().isHeld() && i < 100) {
+            processAllMessages();
             waitForMs(100);
             i++;
         }
         assertFalse(mCdmaInboundSmsHandler.getWakeLock().isHeld());
         mCdmaInboundSmsHandler = null;
         mContentProvider.shutdown();
-        mCdmaInboundSmsHandlerTestHandler.quit();
         super.tearDown();
     }
 
@@ -182,7 +176,7 @@
 
         // trigger transition to IdleState
         mCdmaInboundSmsHandler.sendMessage(InboundSmsHandler.EVENT_START_ACCEPTING_SMS);
-        waitForMs(50);
+        processAllMessages();
 
         assertEquals("IdleState", getCurrentState().getName());
     }
@@ -198,12 +192,16 @@
         doReturn(SmsEnvelope.TELESERVICE_WMT).when(mCdmaSmsMessage).getTeleService();
         mCdmaInboundSmsHandler.sendMessage(InboundSmsHandler.EVENT_NEW_SMS,
                 new AsyncResult(null, mSmsMessage, null));
-        waitForMs(100);
+        processAllMessages();
 
         ArgumentCaptor<Intent> intentArgumentCaptor = ArgumentCaptor.forClass(Intent.class);
         verify(mContext).sendBroadcast(intentArgumentCaptor.capture());
         assertEquals(Telephony.Sms.Intents.SMS_DELIVER_ACTION,
                 intentArgumentCaptor.getValue().getAction());
+
+        // verify a message id was created on receive.
+        assertNotEquals(0L,
+                intentArgumentCaptor.getValue().getLongExtra("messageId", 0L));
         assertEquals("WaitingState", getCurrentState().getName());
 
         mContextFixture.sendBroadcastToOrderedBroadcastReceivers();
@@ -215,7 +213,7 @@
         assertEquals("WaitingState", getCurrentState().getName());
 
         mContextFixture.sendBroadcastToOrderedBroadcastReceivers();
-        waitForMs(50);
+        processAllMessages();
 
         assertEquals("IdleState", getCurrentState().getName());
     }
@@ -226,6 +224,7 @@
     public void testNewSmsFromBlockedNumber_noBroadcastsSent() {
         String blockedNumber = "123456789";
         mInboundSmsTracker = new InboundSmsTracker(
+                mContext,
                 mSmsPdu, /* pdu */
                 System.currentTimeMillis(), /* timestamp */
                 -1, /* destPort */
@@ -237,7 +236,8 @@
                 false, /* isClass0 */
                 mSubId0);
         doReturn(mInboundSmsTracker).when(mTelephonyComponentFactory)
-                .makeInboundSmsTracker(nullable(byte[].class), anyLong(), anyInt(), anyBoolean(),
+                .makeInboundSmsTracker(any(Context.class), nullable(byte[].class), anyLong(),
+                anyInt(), anyBoolean(),
                 anyBoolean(), nullable(String.class), nullable(String.class),
                 nullable(String.class), anyBoolean(), anyInt());
         mFakeBlockedNumberContentProvider.mBlockedNumbers.add(blockedNumber);
@@ -247,7 +247,7 @@
         doReturn(SmsEnvelope.TELESERVICE_WMT).when(mCdmaSmsMessage).getTeleService();
         mCdmaInboundSmsHandler.sendMessage(InboundSmsHandler.EVENT_NEW_SMS,
                 new AsyncResult(null, mSmsMessage, null));
-        waitForMs(100);
+        processAllMessages();
 
         verify(mContext, never()).sendBroadcast(any(Intent.class));
         assertEquals("IdleState", getCurrentState().getName());
diff --git a/tests/telephonytests/src/com/android/internal/telephony/cdma/CdmaSmsCbTest.java b/tests/telephonytests/src/com/android/internal/telephony/cdma/CdmaSmsCbTest.java
index 93e26c7..3d707da 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/cdma/CdmaSmsCbTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/cdma/CdmaSmsCbTest.java
@@ -17,7 +17,6 @@
 package com.android.internal.telephony.cdma;
 
 import android.hardware.radio.V1_0.CdmaSmsMessage;
-import android.telephony.Rlog;
 import android.telephony.SmsCbCmasInfo;
 import android.telephony.SmsCbMessage;
 import android.telephony.cdma.CdmaSmsCbProgramData;
@@ -32,6 +31,7 @@
 import com.android.internal.telephony.cdma.sms.SmsEnvelope;
 import com.android.internal.telephony.cdma.sms.UserData;
 import com.android.internal.util.BitwiseOutputStream;
+import com.android.telephony.Rlog;
 
 import org.junit.Ignore;
 import org.junit.Test;
@@ -353,7 +353,7 @@
         SmsMessage msg = createBroadcastSmsMessage(123, 456, BearerData.PRIORITY_NORMAL,
                 BearerData.LANGUAGE_ENGLISH, encoding, TEST_TEXT);
 
-        SmsCbMessage cbMessage = msg.parseBroadcastSms("");
+        SmsCbMessage cbMessage = msg.parseBroadcastSms("", 0, 1);
         verifyCbValues(cbMessage);
         assertEquals(123, cbMessage.getServiceCategory());
         assertEquals(456, cbMessage.getSerialNumber());
@@ -385,7 +385,7 @@
         SmsMessage msg = createBroadcastSmsMessage(987, 654, -1, -1,
                 UserData.ENCODING_IS91_EXTENDED_PROTOCOL, IS91_TEXT);
 
-        SmsCbMessage cbMessage = msg.parseBroadcastSms("");
+        SmsCbMessage cbMessage = msg.parseBroadcastSms("", 0, 1);
         verifyCbValues(cbMessage);
         assertEquals(987, cbMessage.getServiceCategory());
         assertEquals(654, cbMessage.getSerialNumber());
@@ -402,7 +402,7 @@
                 serviceCategory, 1234, BearerData.PRIORITY_EMERGENCY, BearerData.LANGUAGE_ENGLISH,
                 UserData.ENCODING_7BIT_ASCII, body, -1, -1, -1, -1, -1);
 
-        SmsCbMessage cbMessage = msg.parseBroadcastSms("");
+        SmsCbMessage cbMessage = msg.parseBroadcastSms("", 0, 1);
         verifyCbValues(cbMessage);
         assertEquals(serviceCategory, cbMessage.getServiceCategory());
         assertEquals(1234, cbMessage.getSerialNumber());
@@ -458,7 +458,7 @@
                 SmsCbCmasInfo.CMAS_RESPONSE_TYPE_MONITOR, SmsCbCmasInfo.CMAS_SEVERITY_SEVERE,
                 SmsCbCmasInfo.CMAS_URGENCY_EXPECTED, SmsCbCmasInfo.CMAS_CERTAINTY_LIKELY);
 
-        SmsCbMessage cbMessage = msg.parseBroadcastSms("");
+        SmsCbMessage cbMessage = msg.parseBroadcastSms("", 0, 1);
         verifyCbValues(cbMessage);
         assertEquals(SmsEnvelope.SERVICE_CATEGORY_CMAS_EXTREME_THREAT,
                 cbMessage.getServiceCategory());
@@ -488,7 +488,7 @@
                 12345, BearerData.PRIORITY_EMERGENCY, BearerData.LANGUAGE_ENGLISH,
                 0x1F, EXTREME_ALERT, -1, -1, -1, -1, -1);
 
-        SmsCbMessage cbMessage = msg.parseBroadcastSms("");
+        SmsCbMessage cbMessage = msg.parseBroadcastSms("", 0, 1);
         assertNull("expected null for unsupported charset", cbMessage);
     }
 
@@ -500,7 +500,7 @@
                 67890, BearerData.PRIORITY_EMERGENCY, BearerData.LANGUAGE_ENGLISH,
                 UserData.ENCODING_KOREAN, EXTREME_ALERT, -1, -1, -1, -1, -1);
 
-        SmsCbMessage cbMessage = msg.parseBroadcastSms("");
+        SmsCbMessage cbMessage = msg.parseBroadcastSms("", 0, 1);
         assertNull("expected null for unsupported charset", cbMessage);
     }
 
@@ -513,7 +513,7 @@
                 BearerData.PRIORITY_EMERGENCY, BearerData.LANGUAGE_ENGLISH,
                 UserData.ENCODING_7BIT_ASCII, null, -1, -1, -1, -1, -1);
 
-        SmsCbMessage cbMessage = msg.parseBroadcastSms("");
+        SmsCbMessage cbMessage = msg.parseBroadcastSms("", 0, 1);
         verifyCbValues(cbMessage);
         assertEquals(SmsEnvelope.SERVICE_CATEGORY_CMAS_PRESIDENTIAL_LEVEL_ALERT,
                 cbMessage.getServiceCategory());
@@ -547,7 +547,7 @@
                 int category = 0x0ff0 + r.nextInt(32);  // half CMAS, half non-CMAS
                 CdmaSmsMessage cdmaSmsMessage = createBroadcastParcel(category);
                 SmsMessage msg = createMessageFromParcel(cdmaSmsMessage, data);
-                SmsCbMessage cbMessage = msg.parseBroadcastSms("");
+                SmsCbMessage cbMessage = msg.parseBroadcastSms("", 0, 1);
                 // with random input, cbMessage will almost always be null (log when it isn't)
                 if (cbMessage != null) {
                     Rlog.d("CdmaSmsCbTest", "success: " + cbMessage);
@@ -581,7 +581,7 @@
                 }
 
                 SmsMessage msg = createMessageFromParcel(cdmaSmsMessage, bos.toByteArray());
-                SmsCbMessage cbMessage = msg.parseBroadcastSms("");
+                SmsCbMessage cbMessage = msg.parseBroadcastSms("", 0, 1);
             } catch (Exception e) {
                 Rlog.d("CdmaSmsCbTest", "exception thrown", e);
                 fail("Exception in decoder at run " + run + " length " + len + ": " + e);
@@ -744,7 +744,7 @@
         CdmaSmsMessage cdmaSmsMessage = createBroadcastParcel(SmsEnvelope.SERVICE_CATEGORY_CMAS_TEST_MESSAGE);
         SmsMessage msg = createMessageFromParcel(cdmaSmsMessage, CMAS_TEST_BEARER_DATA);
 
-        SmsCbMessage cbMessage = msg.parseBroadcastSms("");
+        SmsCbMessage cbMessage = msg.parseBroadcastSms("", 0, 1);
         assertNotNull("expected non-null for bearer data", cbMessage);
         assertEquals("geoScope", cbMessage.getGeographicalScope(), 1);
         assertEquals("serialNumber", cbMessage.getSerialNumber(), 51072);
diff --git a/tests/telephonytests/src/com/android/internal/telephony/cdma/CdmaSmsDispatcherTest.java b/tests/telephonytests/src/com/android/internal/telephony/cdma/CdmaSmsDispatcherTest.java
index dcc25dd..c49ce60 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/cdma/CdmaSmsDispatcherTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/cdma/CdmaSmsDispatcherTest.java
@@ -72,6 +72,7 @@
     public void tearDown() throws Exception {
         mCdmaSmsDispatcher = null;
         mCdmaSmsDispatcherTestHandler.quit();
+        mCdmaSmsDispatcherTestHandler.join();
         super.tearDown();
     }
 
@@ -85,14 +86,14 @@
     @Test @SmallTest
     public void testSendText() {
         mCdmaSmsDispatcher.sendText("111"/* desAddr*/, "222" /*scAddr*/, TAG,
-                null, null, null, null, false, -1, false, -1, false);
+                null, null, null, null, false, -1, false, -1, false, 0L);
         verify(mSimulatedCommandsVerifier).sendCdmaSms(any(byte[].class), any(Message.class));
     }
 
     @Test @SmallTest
     public void testSendTextWithOutDesAddr() {
         mCdmaSmsDispatcher.sendText(null, "222" /*scAddr*/, TAG,
-                null, null, null, null, false, -1, false, -1, false);
+                null, null, null, null, false, -1, false, -1, false, 0L);
         verify(mSimulatedCommandsVerifier, times(0)).sendImsGsmSms(anyString(), anyString(),
                 anyInt(), anyInt(), any(Message.class));
     }
diff --git a/tests/telephonytests/src/com/android/internal/telephony/cdma/sms/CdmaSmsTest.java b/tests/telephonytests/src/com/android/internal/telephony/cdma/sms/CdmaSmsTest.java
index a315bde..0e0f113 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/cdma/sms/CdmaSmsTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/cdma/sms/CdmaSmsTest.java
@@ -22,9 +22,11 @@
 
 import com.android.internal.telephony.GsmAlphabet.TextEncodingDetails;
 import com.android.internal.telephony.SmsHeader;
+import com.android.internal.telephony.SmsMessageBase;
 import com.android.internal.telephony.cdma.SmsMessage;
 import com.android.internal.util.HexDump;
 
+import java.lang.reflect.Field;
 import java.util.ArrayList;
 import java.util.Arrays;
 
@@ -335,7 +337,7 @@
         assertEquals(bearerData.depositIndex, 1382);
         assertEquals(bearerData.userResponseCode, 5);
         assertEquals(bearerData.msgCenterTimeStamp.year, 2008);
-        assertEquals(bearerData.msgCenterTimeStamp.month, 11);
+        assertEquals(bearerData.msgCenterTimeStamp.monthOrdinal, 12);
         assertEquals(bearerData.msgCenterTimeStamp.monthDay, 1);
         assertEquals(bearerData.msgCenterTimeStamp.hour, 11);
         assertEquals(bearerData.msgCenterTimeStamp.minute, 1);
@@ -343,7 +345,7 @@
         assertEquals(bearerData.validityPeriodAbsolute, null);
         assertEquals(bearerData.validityPeriodRelative, 193);
         assertEquals(bearerData.deferredDeliveryTimeAbsolute.year, 1997);
-        assertEquals(bearerData.deferredDeliveryTimeAbsolute.month, 5);
+        assertEquals(bearerData.deferredDeliveryTimeAbsolute.monthOrdinal, 6);
         assertEquals(bearerData.deferredDeliveryTimeAbsolute.monthDay, 18);
         assertEquals(bearerData.deferredDeliveryTimeAbsolute.hour, 0);
         assertEquals(bearerData.deferredDeliveryTimeAbsolute.minute, 0);
@@ -382,7 +384,7 @@
         assertEquals(bearerData.depositIndex, 1382);
         assertEquals(bearerData.userResponseCode, 5);
         assertEquals(bearerData.msgCenterTimeStamp.year, 2008);
-        assertEquals(bearerData.msgCenterTimeStamp.month, 11);
+        assertEquals(bearerData.msgCenterTimeStamp.monthOrdinal, 12);
         assertEquals(bearerData.msgCenterTimeStamp.monthDay, 1);
         assertEquals(bearerData.msgCenterTimeStamp.hour, 11);
         assertEquals(bearerData.msgCenterTimeStamp.minute, 1);
@@ -390,7 +392,7 @@
         assertEquals(bearerData.validityPeriodAbsolute, null);
         assertEquals(bearerData.validityPeriodRelative, 61);
         assertEquals(bearerData.deferredDeliveryTimeAbsolute.year, 1997);
-        assertEquals(bearerData.deferredDeliveryTimeAbsolute.month, 5);
+        assertEquals(bearerData.deferredDeliveryTimeAbsolute.monthOrdinal, 6);
         assertEquals(bearerData.deferredDeliveryTimeAbsolute.monthDay, 18);
         assertEquals(bearerData.deferredDeliveryTimeAbsolute.hour, 0);
         assertEquals(bearerData.deferredDeliveryTimeAbsolute.minute, 0);
@@ -1033,4 +1035,34 @@
             }
         }
     }
+
+    @SmallTest
+    public void testPreprocessFdeaWdpUserData() throws Exception {
+        // Refer to https://patents.google.com/patent/CN103906005A/en
+        String wdpUserData =
+                "0003156D60018103F80008011F805C26B031230B8383634B1B0BA34B7B717BB3732173BB0B81736B" +
+                "6B996B6B2B9B9B0B3B2805A43D7C246414C212522A3A522BD31AD3931210046C841B43A3A381D179" +
+                "798981719199A1718999B97189897A12522A3A522BD31AD393121004402C081815175C486C018999" +
+                "9989B181C9B99991C80454047011AF78";
+
+        SmsMessage cdmaSmsMessage = new SmsMessage();
+
+        Field field = SmsMessageBase.class.getDeclaredField("mUserData");
+        field.setAccessible(true);
+        field.set(cdmaSmsMessage, HexDump.hexStringToByteArray(wdpUserData));
+
+        BearerData bearerData = new BearerData();
+        bearerData.userData = new UserData();
+
+        field = SmsMessage.class.getDeclaredField("mBearerData");
+        field.setAccessible(true);
+        field.set(cdmaSmsMessage, bearerData);
+        bearerData = (BearerData) field.get(cdmaSmsMessage);
+
+        assertTrue(cdmaSmsMessage.preprocessCdmaFdeaWap());
+        assertEquals(BearerData.MESSAGE_TYPE_DELIVER, bearerData.messageType);
+        assertEquals(0x56D6, bearerData.messageId);
+        assertEquals(0x7F, bearerData.userData.numFields);
+        assertNotNull(bearerData.userData.payload);
+    }
 }
diff --git a/tests/telephonytests/src/com/android/internal/telephony/dataconnection/ApnConfigTypeRepositoryTest.java b/tests/telephonytests/src/com/android/internal/telephony/dataconnection/ApnConfigTypeRepositoryTest.java
new file mode 100644
index 0000000..aa97c02
--- /dev/null
+++ b/tests/telephonytests/src/com/android/internal/telephony/dataconnection/ApnConfigTypeRepositoryTest.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) 2020 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.dataconnection;
+
+import static junit.framework.Assert.assertEquals;
+
+import android.os.PersistableBundle;
+import android.telephony.CarrierConfigManager;
+import android.telephony.data.ApnSetting;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class ApnConfigTypeRepositoryTest {
+
+    PersistableBundle mCarrierConfig;
+
+    @Before
+    public void setUp() throws Exception {
+        mCarrierConfig = new PersistableBundle();
+    }
+
+    @Test
+    public void testReturnsDefaultsWhenCarrierConfigNull() {
+        ApnConfigTypeRepository repository = new ApnConfigTypeRepository(null);
+        checkDefaults(repository);
+    }
+
+    @Test
+    public void testReturnsDefaultsWhenCarrierConfigApnContextKeyReturnsNull() {
+        mCarrierConfig.putStringArray(CarrierConfigManager.KEY_APN_PRIORITY_STRING_ARRAY,
+                null);
+
+        ApnConfigTypeRepository repository = new ApnConfigTypeRepository(mCarrierConfig);
+        checkDefaults(repository);
+    }
+
+    @Test
+    public void testReturnsDefaultsWhenCarrierConfigHasInvalidTypes() {
+
+        List<String> apnConfigStringArray = new ArrayList<>();
+        apnConfigStringArray.add("xcap,cbs:3");
+        apnConfigStringArray.add("default:0a");
+
+        mCarrierConfig.putStringArray(CarrierConfigManager.KEY_APN_PRIORITY_STRING_ARRAY,
+                apnConfigStringArray.toArray(new String[0]));
+
+        ApnConfigTypeRepository repository = new ApnConfigTypeRepository(mCarrierConfig);
+        checkDefaults(repository);
+    }
+
+    @Test
+    public void testReturnsCarrierConfigOverride() {
+        List<String> apnConfigStringArray = new ArrayList<>();
+        //Shouldn't match or override any keys
+        apnConfigStringArray.add("xcap,cbs:3");
+
+        //Priorities must be integers
+        apnConfigStringArray.add("default:10a");
+
+        //Key isn't case sensitive, which means that this prority should be taken
+        apnConfigStringArray.add("fotA:10");
+
+        mCarrierConfig.putStringArray(CarrierConfigManager.KEY_APN_PRIORITY_STRING_ARRAY,
+                apnConfigStringArray.toArray(new String[0]));
+
+        ApnConfigTypeRepository repository = new ApnConfigTypeRepository(mCarrierConfig);
+        assertEquals(10, repository.getByType(ApnSetting.TYPE_FOTA).getPriority());
+        checkDefaults(repository);
+    }
+
+    private void checkDefaults(ApnConfigTypeRepository repository) {
+        assertEquals(0, repository.getByType(ApnSetting.TYPE_DEFAULT).getPriority());
+        assertEquals(2, repository.getByType(ApnSetting.TYPE_SUPL).getPriority());
+        assertEquals(3, repository.getByType(ApnSetting.TYPE_HIPRI).getPriority());
+        assertEquals(2, repository.getByType(ApnSetting.TYPE_CBS).getPriority());
+        assertEquals(2, repository.getByType(ApnSetting.TYPE_EMERGENCY).getPriority());
+        assertEquals(3, repository.getByType(ApnSetting.TYPE_XCAP).getPriority());
+    }
+}
diff --git a/tests/telephonytests/src/com/android/internal/telephony/dataconnection/ApnContextTest.java b/tests/telephonytests/src/com/android/internal/telephony/dataconnection/ApnContextTest.java
index ca9f1ac..525afe2 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/dataconnection/ApnContextTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/dataconnection/ApnContextTest.java
@@ -54,8 +54,7 @@
     public void setUp() throws Exception {
         super.setUp(getClass().getSimpleName());
         mNetworkConfig.dependencyMet = true;
-        mApnContext = new ApnContext(mPhone, PhoneConstants.APN_TYPE_DEFAULT, TAG, mNetworkConfig,
-                mDcTracker);
+        mApnContext = new ApnContext(mPhone, ApnSetting.TYPE_DEFAULT, TAG, mDcTracker, 1);
     }
 
     @After
diff --git a/tests/telephonytests/src/com/android/internal/telephony/dataconnection/ApnSettingTest.java b/tests/telephonytests/src/com/android/internal/telephony/dataconnection/ApnSettingTest.java
index 977b45a..de7d12a 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/dataconnection/ApnSettingTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/dataconnection/ApnSettingTest.java
@@ -359,6 +359,7 @@
         assertFalse(ApnSettingUtils.isMeteredApnType(ApnSetting.TYPE_FOTA, mPhone));
         assertFalse(ApnSettingUtils.isMeteredApnType(ApnSetting.TYPE_IA, mPhone));
         assertFalse(ApnSettingUtils.isMeteredApnType(ApnSetting.TYPE_HIPRI, mPhone));
+        assertFalse(ApnSettingUtils.isMeteredApnType(ApnSetting.TYPE_XCAP, mPhone));
 
         // Carrier config settings changes.
         mBundle.putStringArray(CarrierConfigManager.KEY_CARRIER_METERED_APN_TYPES_STRINGS,
@@ -404,6 +405,7 @@
         assertFalse(ApnSettingUtils.isMeteredApnType(ApnSetting.TYPE_DEFAULT, mPhone));
         assertFalse(ApnSettingUtils.isMeteredApnType(ApnSetting.TYPE_MMS, mPhone));
         assertTrue(ApnSettingUtils.isMeteredApnType(ApnSetting.TYPE_FOTA, mPhone));
+        assertFalse(ApnSettingUtils.isMeteredApnType(ApnSetting.TYPE_XCAP, mPhone));
     }
 
     @Test
@@ -434,6 +436,7 @@
                 createApnSetting(ApnSetting.TYPE_DEFAULT | ApnSetting.TYPE_IMS), mPhone));
 
         assertFalse(ApnSettingUtils.isMetered(createApnSetting(ApnSetting.TYPE_IMS), mPhone));
+        assertFalse(ApnSettingUtils.isMetered(createApnSetting(ApnSetting.TYPE_XCAP), mPhone));
     }
 
     @Test
@@ -472,6 +475,7 @@
         assertFalse(ApnSettingUtils.isMeteredApnType(ApnSetting.TYPE_FOTA, mPhone));
         assertFalse(ApnSettingUtils.isMeteredApnType(ApnSetting.TYPE_IA, mPhone));
         assertFalse(ApnSettingUtils.isMeteredApnType(ApnSetting.TYPE_HIPRI, mPhone));
+        assertFalse(ApnSettingUtils.isMeteredApnType(ApnSetting.TYPE_XCAP, mPhone));
     }
 
     @Test
@@ -552,7 +556,7 @@
                 ApnSetting.TYPE_DEFAULT | ApnSetting.TYPE_MMS | ApnSetting.TYPE_IA)
                 .canHandleType(ApnSetting.TYPE_IA));
 
-        // same for emergency and mcx
+        // same for emergency, mcx, and xcap
         assertFalse(createApnSetting(ApnSetting.TYPE_ALL)
                 .canHandleType(ApnSetting.TYPE_EMERGENCY));
         assertTrue(createApnSetting(
@@ -563,6 +567,11 @@
         assertTrue(createApnSetting(
                 ApnSetting.TYPE_DEFAULT | ApnSetting.TYPE_MMS | ApnSetting.TYPE_MCX)
                 .canHandleType(ApnSetting.TYPE_MCX));
+        assertFalse(createApnSetting(ApnSetting.TYPE_ALL)
+                .canHandleType(ApnSetting.TYPE_XCAP));
+        assertTrue(createApnSetting(
+                ApnSetting.TYPE_DEFAULT | ApnSetting.TYPE_MMS | ApnSetting.TYPE_XCAP)
+                .canHandleType(ApnSetting.TYPE_XCAP));
 
         // check carrier disabled
         assertFalse(createDisabledApnSetting(ApnSetting.TYPE_ALL)
@@ -578,6 +587,9 @@
         assertFalse(createDisabledApnSetting(
                 ApnSetting.TYPE_DEFAULT | ApnSetting.TYPE_MMS | ApnSetting.TYPE_IA)
                 .canHandleType(ApnSetting.TYPE_IA));
+        assertFalse(createDisabledApnSetting(
+                ApnSetting.TYPE_DEFAULT | ApnSetting.TYPE_MMS | ApnSetting.TYPE_XCAP)
+                .canHandleType(ApnSetting.TYPE_XCAP));
     }
 
     @Test
diff --git a/tests/telephonytests/src/com/android/internal/telephony/dataconnection/DataCallResponseTest.java b/tests/telephonytests/src/com/android/internal/telephony/dataconnection/DataCallResponseTest.java
index 20f3d8b..529e81e 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/dataconnection/DataCallResponseTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/dataconnection/DataCallResponseTest.java
@@ -22,8 +22,8 @@
 import static com.android.internal.telephony.dataconnection.DcTrackerTest.FAKE_IFNAME;
 import static com.android.internal.telephony.dataconnection.DcTrackerTest.FAKE_PCSCF_ADDRESS;
 
+import android.net.InetAddresses;
 import android.net.LinkAddress;
-import android.net.NetworkUtils;
 import android.os.Parcel;
 import android.telephony.data.ApnSetting;
 import android.telephony.data.DataCallResponse;
@@ -36,13 +36,22 @@
 
     @SmallTest
     public void testParcel() throws Exception {
-        DataCallResponse response = new DataCallResponse(0, -1, 1, 2,
-                ApnSetting.PROTOCOL_IP, FAKE_IFNAME,
-                Arrays.asList(new LinkAddress(NetworkUtils.numericToInetAddress(FAKE_ADDRESS), 0)),
-                Arrays.asList(NetworkUtils.numericToInetAddress(FAKE_DNS)),
-                Arrays.asList(NetworkUtils.numericToInetAddress(FAKE_GATEWAY)),
-                Arrays.asList(NetworkUtils.numericToInetAddress(FAKE_PCSCF_ADDRESS)),
-                1440);
+        DataCallResponse response = new DataCallResponse.Builder()
+                .setCause(0)
+                .setSuggestedRetryTime(-1)
+                .setId(1)
+                .setLinkStatus(2)
+                .setProtocolType(ApnSetting.PROTOCOL_IP)
+                .setInterfaceName(FAKE_IFNAME)
+                .setAddresses(Arrays.asList(
+                        new LinkAddress(InetAddresses.parseNumericAddress(FAKE_ADDRESS), 0)))
+                .setDnsAddresses(Arrays.asList(InetAddresses.parseNumericAddress(FAKE_DNS)))
+                .setGatewayAddresses(Arrays.asList(InetAddresses.parseNumericAddress(FAKE_GATEWAY)))
+                .setPcscfAddresses(
+                        Arrays.asList(InetAddresses.parseNumericAddress(FAKE_PCSCF_ADDRESS)))
+                .setMtuV4(1440)
+                .setMtuV6(1440)
+                .build();
 
         Parcel p = Parcel.obtain();
         response.writeToParcel(p, 0);
@@ -54,34 +63,62 @@
 
     @SmallTest
     public void testEquals() throws Exception {
-        DataCallResponse response = new DataCallResponse(0, -1, 1, 2,
-                ApnSetting.PROTOCOL_IP, FAKE_IFNAME,
-                Arrays.asList(new LinkAddress(NetworkUtils.numericToInetAddress(FAKE_ADDRESS), 0)),
-                Arrays.asList(NetworkUtils.numericToInetAddress(FAKE_DNS)),
-                Arrays.asList(NetworkUtils.numericToInetAddress(FAKE_GATEWAY)),
-                Arrays.asList(NetworkUtils.numericToInetAddress(FAKE_PCSCF_ADDRESS)),
-                1440);
+        DataCallResponse response = new DataCallResponse.Builder()
+                .setCause(0)
+                .setSuggestedRetryTime(-1)
+                .setId(1)
+                .setLinkStatus(2)
+                .setProtocolType(ApnSetting.PROTOCOL_IP)
+                .setInterfaceName(FAKE_IFNAME)
+                .setAddresses(Arrays.asList(
+                        new LinkAddress(InetAddresses.parseNumericAddress(FAKE_ADDRESS), 0)))
+                .setDnsAddresses(Arrays.asList(InetAddresses.parseNumericAddress(FAKE_DNS)))
+                .setGatewayAddresses(Arrays.asList(InetAddresses.parseNumericAddress(FAKE_GATEWAY)))
+                .setPcscfAddresses(
+                        Arrays.asList(InetAddresses.parseNumericAddress(FAKE_PCSCF_ADDRESS)))
+                .setMtuV4(1440)
+                .setMtuV6(1400)
+                .build();
 
-        DataCallResponse response1 = new DataCallResponse(0, -1, 1, 2,
-                ApnSetting.PROTOCOL_IP, FAKE_IFNAME,
-                Arrays.asList(new LinkAddress(NetworkUtils.numericToInetAddress(FAKE_ADDRESS), 0)),
-                Arrays.asList(NetworkUtils.numericToInetAddress(FAKE_DNS)),
-                Arrays.asList(NetworkUtils.numericToInetAddress(FAKE_GATEWAY)),
-                Arrays.asList(NetworkUtils.numericToInetAddress(FAKE_PCSCF_ADDRESS)),
-                1440);
+        DataCallResponse response1 = new DataCallResponse.Builder()
+                .setCause(0)
+                .setSuggestedRetryTime(-1)
+                .setId(1)
+                .setLinkStatus(2)
+                .setProtocolType(ApnSetting.PROTOCOL_IP)
+                .setInterfaceName(FAKE_IFNAME)
+                .setAddresses(Arrays.asList(
+                        new LinkAddress(InetAddresses.parseNumericAddress(FAKE_ADDRESS), 0)))
+                .setDnsAddresses(Arrays.asList(InetAddresses.parseNumericAddress(FAKE_DNS)))
+                .setGatewayAddresses(Arrays.asList(InetAddresses.parseNumericAddress(FAKE_GATEWAY)))
+                .setPcscfAddresses(
+                        Arrays.asList(InetAddresses.parseNumericAddress(FAKE_PCSCF_ADDRESS)))
+                .setMtuV4(1440)
+                .setMtuV6(1400)
+                .build();
 
         assertEquals(response, response);
         assertEquals(response, response1);
 
-        DataCallResponse response2 = new DataCallResponse(1, -1, 1, 3,
-                ApnSetting.PROTOCOL_IP, FAKE_IFNAME,
-                Arrays.asList(new LinkAddress(NetworkUtils.numericToInetAddress(FAKE_ADDRESS), 0)),
-                Arrays.asList(NetworkUtils.numericToInetAddress(FAKE_DNS),
-                        NetworkUtils.numericToInetAddress(FAKE_DNS)),
-                Arrays.asList(NetworkUtils.numericToInetAddress(FAKE_GATEWAY)),
-                Arrays.asList(NetworkUtils.numericToInetAddress(FAKE_PCSCF_ADDRESS),
-                        NetworkUtils.numericToInetAddress(FAKE_PCSCF_ADDRESS)),
-                1441);
+        DataCallResponse response2 = new DataCallResponse.Builder()
+                .setCause(1)
+                .setSuggestedRetryTime(-1)
+                .setId(1)
+                .setLinkStatus(3)
+                .setProtocolType(ApnSetting.PROTOCOL_IP)
+                .setInterfaceName(FAKE_IFNAME)
+                .setAddresses(Arrays.asList(
+                        new LinkAddress(InetAddresses.parseNumericAddress(FAKE_ADDRESS), 0)))
+                .setDnsAddresses(Arrays.asList(InetAddresses.parseNumericAddress(FAKE_DNS),
+                        InetAddresses.parseNumericAddress(FAKE_DNS)))
+                .setGatewayAddresses(Arrays.asList(InetAddresses.parseNumericAddress(FAKE_GATEWAY)))
+                .setPcscfAddresses(Arrays.asList(
+                        InetAddresses.parseNumericAddress(FAKE_PCSCF_ADDRESS),
+                        InetAddresses.parseNumericAddress(FAKE_PCSCF_ADDRESS)))
+                .setMtuV4(1441)
+                .setMtuV6(1440)
+                .build();
+
         assertNotSame(response1, response2);
         assertNotSame(response1, null);
         assertNotSame(response1, new String[1]);
diff --git a/tests/telephonytests/src/com/android/internal/telephony/dataconnection/DataConnectionTest.java b/tests/telephonytests/src/com/android/internal/telephony/dataconnection/DataConnectionTest.java
index 018747e..e79bdfd 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/dataconnection/DataConnectionTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/dataconnection/DataConnectionTest.java
@@ -18,8 +18,9 @@
 
 import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_CONGESTED;
 import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_METERED;
-import static android.net.NetworkPolicyManager.OVERRIDE_CONGESTED;
-import static android.net.NetworkPolicyManager.OVERRIDE_UNMETERED;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_TEMPORARILY_NOT_METERED;
+import static android.net.NetworkPolicyManager.SUBSCRIPTION_OVERRIDE_CONGESTED;
+import static android.net.NetworkPolicyManager.SUBSCRIPTION_OVERRIDE_UNMETERED;
 
 import static com.android.internal.telephony.TelephonyTestUtils.waitForMs;
 import static com.android.internal.telephony.dataconnection.DcTrackerTest.FAKE_ADDRESS;
@@ -31,22 +32,22 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.anyInt;
 import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.eq;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 
 import android.content.IntentFilter;
 import android.content.pm.ServiceInfo;
+import android.net.InetAddresses;
 import android.net.KeepalivePacketData;
 import android.net.LinkAddress;
 import android.net.LinkProperties;
 import android.net.NattKeepalivePacketData;
 import android.net.NetworkCapabilities;
 import android.net.NetworkInfo;
-import android.net.NetworkUtils;
 import android.os.AsyncResult;
 import android.os.Handler;
 import android.os.HandlerThread;
@@ -71,7 +72,6 @@
 import com.android.internal.telephony.dataconnection.DataConnection.SetupResult;
 import com.android.internal.util.IState;
 import com.android.internal.util.StateMachine;
-import com.android.server.pm.PackageManagerService;
 
 import org.junit.After;
 import org.junit.Before;
@@ -95,12 +95,11 @@
     ApnContext mApnContext;
     @Mock
     DcFailBringUp mDcFailBringUp;
-    @Mock
-    PackageManagerService mMockPackageManagerInternal;
 
     private DataConnection mDc;
     private DataConnectionTestHandler mDataConnectionTestHandler;
     private DcController mDcc;
+    private CellularDataService mCellularDataService;
 
     private ApnSetting mApn1 = ApnSetting.makeApnSetting(
             2163,                   // id
@@ -263,7 +262,7 @@
     }
 
     private void addDataService() {
-        CellularDataService cellularDataService = new CellularDataService();
+        mCellularDataService = new CellularDataService();
         ServiceInfo serviceInfo = new ServiceInfo();
         serviceInfo.packageName = "com.android.phone";
         serviceInfo.permission = "android.permission.BIND_TELEPHONY_DATA_SERVICE";
@@ -272,7 +271,7 @@
                 DataService.SERVICE_INTERFACE,
                 null,
                 "com.android.phone",
-                cellularDataService.mBinder,
+                mCellularDataService.mBinder,
                 serviceInfo,
                 filter);
     }
@@ -280,9 +279,8 @@
 
     @Before
     public void setUp() throws Exception {
-        logd("+Setup!");
         super.setUp(getClass().getSimpleName());
-        mServiceManagerMockedServices.put("package", mMockPackageManagerInternal);
+        logd("+Setup!");
         doReturn("fake.action_detached").when(mPhone).getActionDetached();
         replaceInstance(ConnectionParams.class, "mApnContext", mCp, mApnContext);
         replaceInstance(ConnectionParams.class, "mRilRat", mCp,
@@ -293,8 +291,8 @@
         mDcFailBringUp.saveParameters(0, 0, -2);
         doReturn(mDcFailBringUp).when(mDcTesterFailBringUpAll).getDcFailBringUp();
 
-        mContextFixture.putStringArrayResource(com.android.internal.R.array.
-                config_mobile_tcp_buffers, new String[]{
+        mContextFixture.putStringArrayResource(com.android.internal.R.array
+                .config_mobile_tcp_buffers, new String[]{
                 "umts:131072,262144,1452032,4096,16384,399360",
                 "hspa:131072,262144,2441216,4096,16384,399360",
                 "hsupa:131072,262144,2441216,4096,16384,399360",
@@ -310,7 +308,6 @@
                 "com.android.phone");
 
         mDcp.mApnContext = mApnContext;
-
         addDataService();
 
         mDataConnectionTestHandler = new DataConnectionTestHandler(getClass().getSimpleName());
@@ -326,6 +323,8 @@
         mDc = null;
         mDcc = null;
         mDataConnectionTestHandler.quit();
+        mDataConnectionTestHandler.join();
+        mCellularDataService.onDestroy();
         super.tearDown();
     }
 
@@ -420,77 +419,138 @@
     @Test
     @SmallTest
     public void testModemSuggestRetry() throws Exception {
-        DataCallResponse response = new DataCallResponse(0, 0, 1, 2,
-                ApnSetting.PROTOCOL_IP, FAKE_IFNAME,
-                Arrays.asList(new LinkAddress(NetworkUtils.numericToInetAddress(FAKE_ADDRESS), 0)),
-                Arrays.asList(NetworkUtils.numericToInetAddress(FAKE_DNS)),
-                Arrays.asList(NetworkUtils.numericToInetAddress(FAKE_GATEWAY)),
-                Arrays.asList(NetworkUtils.numericToInetAddress(FAKE_PCSCF_ADDRESS)),
-                1440);
-
+        DataCallResponse response = new DataCallResponse.Builder()
+                .setCause(0)
+                .setSuggestedRetryTime(0)
+                .setId(1)
+                .setLinkStatus(2)
+                .setProtocolType(ApnSetting.PROTOCOL_IP)
+                .setInterfaceName(FAKE_IFNAME)
+                .setAddresses(Arrays.asList(
+                        new LinkAddress(InetAddresses.parseNumericAddress(FAKE_ADDRESS), 0)))
+                .setDnsAddresses(Arrays.asList(InetAddresses.parseNumericAddress(FAKE_DNS)))
+                .setGatewayAddresses(Arrays.asList(InetAddresses.parseNumericAddress(FAKE_GATEWAY)))
+                .setPcscfAddresses(
+                        Arrays.asList(InetAddresses.parseNumericAddress(FAKE_PCSCF_ADDRESS)))
+                .setMtuV4(1440)
+                .setMtuV6(1440)
+                .build();
         assertEquals(response.getSuggestedRetryTime(), getSuggestedRetryDelay(response));
 
-        response = new DataCallResponse(0, 1000, 1, 2,
-                ApnSetting.PROTOCOL_IP, FAKE_IFNAME,
-                Arrays.asList(new LinkAddress(NetworkUtils.numericToInetAddress(FAKE_ADDRESS), 0)),
-                Arrays.asList(NetworkUtils.numericToInetAddress(FAKE_DNS)),
-                Arrays.asList(NetworkUtils.numericToInetAddress(FAKE_GATEWAY)),
-                Arrays.asList(NetworkUtils.numericToInetAddress(FAKE_PCSCF_ADDRESS)),
-                1440);
+        response = new DataCallResponse.Builder()
+                .setCause(0)
+                .setSuggestedRetryTime(1000)
+                .setId(1)
+                .setLinkStatus(2)
+                .setProtocolType(ApnSetting.PROTOCOL_IP)
+                .setInterfaceName(FAKE_IFNAME)
+                .setAddresses(Arrays.asList(
+                        new LinkAddress(InetAddresses.parseNumericAddress(FAKE_ADDRESS), 0)))
+                .setDnsAddresses(Arrays.asList(InetAddresses.parseNumericAddress(FAKE_DNS)))
+                .setGatewayAddresses(Arrays.asList(InetAddresses.parseNumericAddress(FAKE_GATEWAY)))
+                .setPcscfAddresses(
+                        Arrays.asList(InetAddresses.parseNumericAddress(FAKE_PCSCF_ADDRESS)))
+                .setMtuV4(1440)
+                .setMtuV6(1440)
+                .build();
         assertEquals(response.getSuggestedRetryTime(), getSuggestedRetryDelay(response));
 
-        response = new DataCallResponse(0, 9999, 1, 2,
-                ApnSetting.PROTOCOL_IP, FAKE_IFNAME,
-                Arrays.asList(new LinkAddress(NetworkUtils.numericToInetAddress(FAKE_ADDRESS), 0)),
-                Arrays.asList(NetworkUtils.numericToInetAddress(FAKE_DNS)),
-                Arrays.asList(NetworkUtils.numericToInetAddress(FAKE_GATEWAY)),
-                Arrays.asList(NetworkUtils.numericToInetAddress(FAKE_PCSCF_ADDRESS)),
-                1440);
+        response = new DataCallResponse.Builder()
+                .setCause(0)
+                .setSuggestedRetryTime(9999)
+                .setId(1)
+                .setLinkStatus(2)
+                .setProtocolType(ApnSetting.PROTOCOL_IP)
+                .setInterfaceName(FAKE_IFNAME)
+                .setAddresses(Arrays.asList(
+                        new LinkAddress(InetAddresses.parseNumericAddress(FAKE_ADDRESS), 0)))
+                .setDnsAddresses(Arrays.asList(InetAddresses.parseNumericAddress(FAKE_DNS)))
+                .setGatewayAddresses(Arrays.asList(InetAddresses.parseNumericAddress(FAKE_GATEWAY)))
+                .setPcscfAddresses(
+                        Arrays.asList(InetAddresses.parseNumericAddress(FAKE_PCSCF_ADDRESS)))
+                .setMtuV4(1440)
+                .setMtuV6(1440)
+                .build();
         assertEquals(response.getSuggestedRetryTime(), getSuggestedRetryDelay(response));
     }
 
     @Test
     @SmallTest
     public void testModemNotSuggestRetry() throws Exception {
-        DataCallResponse response = new DataCallResponse(0, -1, 1, 2,
-                ApnSetting.PROTOCOL_IP, FAKE_IFNAME,
-                Arrays.asList(new LinkAddress(NetworkUtils.numericToInetAddress(FAKE_ADDRESS), 0)),
-                Arrays.asList(NetworkUtils.numericToInetAddress(FAKE_DNS)),
-                Arrays.asList(NetworkUtils.numericToInetAddress(FAKE_GATEWAY)),
-                Arrays.asList(NetworkUtils.numericToInetAddress(FAKE_PCSCF_ADDRESS)),
-                1440);
-
+        DataCallResponse response = new DataCallResponse.Builder()
+                .setCause(0)
+                .setSuggestedRetryTime(-1)
+                .setId(1)
+                .setLinkStatus(2)
+                .setProtocolType(ApnSetting.PROTOCOL_IP)
+                .setInterfaceName(FAKE_IFNAME)
+                .setAddresses(Arrays.asList(
+                        new LinkAddress(InetAddresses.parseNumericAddress(FAKE_ADDRESS), 0)))
+                .setDnsAddresses(Arrays.asList(InetAddresses.parseNumericAddress(FAKE_DNS)))
+                .setGatewayAddresses(Arrays.asList(InetAddresses.parseNumericAddress(FAKE_GATEWAY)))
+                .setPcscfAddresses(
+                        Arrays.asList(InetAddresses.parseNumericAddress(FAKE_PCSCF_ADDRESS)))
+                .setMtuV4(1440)
+                .setMtuV6(1440)
+                .build();
         assertEquals(RetryManager.NO_SUGGESTED_RETRY_DELAY, getSuggestedRetryDelay(response));
 
-        response = new DataCallResponse(0, -5, 1, 2,
-                ApnSetting.PROTOCOL_IP, FAKE_IFNAME,
-                Arrays.asList(new LinkAddress(NetworkUtils.numericToInetAddress(FAKE_ADDRESS), 0)),
-                Arrays.asList(NetworkUtils.numericToInetAddress(FAKE_DNS)),
-                Arrays.asList(NetworkUtils.numericToInetAddress(FAKE_GATEWAY)),
-                Arrays.asList(NetworkUtils.numericToInetAddress(FAKE_PCSCF_ADDRESS)),
-                1440);
+        response = new DataCallResponse.Builder()
+                .setCause(0)
+                .setSuggestedRetryTime(-5)
+                .setId(1)
+                .setLinkStatus(2)
+                .setProtocolType(ApnSetting.PROTOCOL_IP)
+                .setInterfaceName(FAKE_IFNAME)
+                .setAddresses(Arrays.asList(
+                        new LinkAddress(InetAddresses.parseNumericAddress(FAKE_ADDRESS), 0)))
+                .setDnsAddresses(Arrays.asList(InetAddresses.parseNumericAddress(FAKE_DNS)))
+                .setGatewayAddresses(Arrays.asList(InetAddresses.parseNumericAddress(FAKE_GATEWAY)))
+                .setPcscfAddresses(
+                        Arrays.asList(InetAddresses.parseNumericAddress(FAKE_PCSCF_ADDRESS)))
+                .setMtuV4(1440)
+                .setMtuV6(1440)
+                .build();
         assertEquals(RetryManager.NO_SUGGESTED_RETRY_DELAY, getSuggestedRetryDelay(response));
 
-        response = new DataCallResponse(0, Integer.MIN_VALUE, 1, 2,
-                ApnSetting.PROTOCOL_IP, FAKE_IFNAME,
-                Arrays.asList(new LinkAddress(NetworkUtils.numericToInetAddress(FAKE_ADDRESS), 0)),
-                Arrays.asList(NetworkUtils.numericToInetAddress(FAKE_DNS)),
-                Arrays.asList(NetworkUtils.numericToInetAddress(FAKE_GATEWAY)),
-                Arrays.asList(NetworkUtils.numericToInetAddress(FAKE_PCSCF_ADDRESS)),
-                1440);
+        response = new DataCallResponse.Builder()
+                .setCause(0)
+                .setSuggestedRetryTime(Integer.MIN_VALUE)
+                .setId(1)
+                .setLinkStatus(2)
+                .setProtocolType(ApnSetting.PROTOCOL_IP)
+                .setInterfaceName(FAKE_IFNAME)
+                .setAddresses(Arrays.asList(
+                        new LinkAddress(InetAddresses.parseNumericAddress(FAKE_ADDRESS), 0)))
+                .setDnsAddresses(Arrays.asList(InetAddresses.parseNumericAddress(FAKE_DNS)))
+                .setGatewayAddresses(Arrays.asList(InetAddresses.parseNumericAddress(FAKE_GATEWAY)))
+                .setPcscfAddresses(
+                        Arrays.asList(InetAddresses.parseNumericAddress(FAKE_PCSCF_ADDRESS)))
+                .setMtuV4(1440)
+                .setMtuV6(1440)
+                .build();
         assertEquals(RetryManager.NO_SUGGESTED_RETRY_DELAY, getSuggestedRetryDelay(response));
     }
 
     @Test
     @SmallTest
     public void testModemSuggestNoRetry() throws Exception {
-        DataCallResponse response = new DataCallResponse(0, Integer.MAX_VALUE, 1, 2,
-                ApnSetting.PROTOCOL_IP, FAKE_IFNAME,
-                Arrays.asList(new LinkAddress(NetworkUtils.numericToInetAddress(FAKE_ADDRESS), 0)),
-                Arrays.asList(NetworkUtils.numericToInetAddress(FAKE_DNS)),
-                Arrays.asList(NetworkUtils.numericToInetAddress(FAKE_GATEWAY)),
-                Arrays.asList(NetworkUtils.numericToInetAddress(FAKE_PCSCF_ADDRESS)),
-                1440);
+        DataCallResponse response = new DataCallResponse.Builder()
+                .setCause(0)
+                .setSuggestedRetryTime(Integer.MAX_VALUE)
+                .setId(1)
+                .setLinkStatus(2)
+                .setProtocolType(ApnSetting.PROTOCOL_IP)
+                .setInterfaceName(FAKE_IFNAME)
+                .setAddresses(Arrays.asList(
+                        new LinkAddress(InetAddresses.parseNumericAddress(FAKE_ADDRESS), 0)))
+                .setDnsAddresses(Arrays.asList(InetAddresses.parseNumericAddress(FAKE_DNS)))
+                .setGatewayAddresses(Arrays.asList(InetAddresses.parseNumericAddress(FAKE_GATEWAY)))
+                .setPcscfAddresses(
+                        Arrays.asList(InetAddresses.parseNumericAddress(FAKE_PCSCF_ADDRESS)))
+                .setMtuV4(1440)
+                .setMtuV6(1440)
+                .build();
         assertEquals(RetryManager.NO_RETRY, getSuggestedRetryDelay(response));
     }
 
@@ -556,8 +616,8 @@
 
         testConnectEvent();
 
-        assertFalse(getNetworkCapabilities()
-                .hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED));
+        assertFalse(getNetworkCapabilities().hasCapability(NET_CAPABILITY_NOT_METERED));
+        assertFalse(getNetworkCapabilities().hasCapability(NET_CAPABILITY_TEMPORARILY_NOT_METERED));
     }
 
     @Test
@@ -571,8 +631,7 @@
 
         testConnectEvent();
 
-        assertTrue(getNetworkCapabilities()
-                .hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED));
+        assertTrue(getNetworkCapabilities().hasCapability(NET_CAPABILITY_NOT_METERED));
     }
 
     @Test
@@ -582,17 +641,18 @@
                 new String[] { "default" });
         testConnectEvent();
 
-        assertFalse(getNetworkCapabilities().hasCapability(NET_CAPABILITY_NOT_METERED));
+        assertFalse(getNetworkCapabilities().hasCapability(NET_CAPABILITY_TEMPORARILY_NOT_METERED));
         assertTrue(getNetworkCapabilities().hasCapability(NET_CAPABILITY_NOT_CONGESTED));
 
-        mDc.onSubscriptionOverride(OVERRIDE_UNMETERED, OVERRIDE_UNMETERED);
+        mDc.onSubscriptionOverride(SUBSCRIPTION_OVERRIDE_UNMETERED,
+                SUBSCRIPTION_OVERRIDE_UNMETERED);
 
-        assertTrue(getNetworkCapabilities().hasCapability(NET_CAPABILITY_NOT_METERED));
+        assertTrue(getNetworkCapabilities().hasCapability(NET_CAPABILITY_TEMPORARILY_NOT_METERED));
         assertTrue(getNetworkCapabilities().hasCapability(NET_CAPABILITY_NOT_CONGESTED));
 
-        mDc.onSubscriptionOverride(OVERRIDE_UNMETERED, 0);
+        mDc.onSubscriptionOverride(SUBSCRIPTION_OVERRIDE_UNMETERED, 0);
 
-        assertFalse(getNetworkCapabilities().hasCapability(NET_CAPABILITY_NOT_METERED));
+        assertFalse(getNetworkCapabilities().hasCapability(NET_CAPABILITY_TEMPORARILY_NOT_METERED));
         assertTrue(getNetworkCapabilities().hasCapability(NET_CAPABILITY_NOT_CONGESTED));
     }
 
@@ -606,12 +666,13 @@
         assertFalse(getNetworkCapabilities().hasCapability(NET_CAPABILITY_NOT_METERED));
         assertTrue(getNetworkCapabilities().hasCapability(NET_CAPABILITY_NOT_CONGESTED));
 
-        mDc.onSubscriptionOverride(OVERRIDE_CONGESTED, OVERRIDE_CONGESTED);
+        mDc.onSubscriptionOverride(SUBSCRIPTION_OVERRIDE_CONGESTED,
+                SUBSCRIPTION_OVERRIDE_CONGESTED);
 
         assertFalse(getNetworkCapabilities().hasCapability(NET_CAPABILITY_NOT_METERED));
         assertFalse(getNetworkCapabilities().hasCapability(NET_CAPABILITY_NOT_CONGESTED));
 
-        mDc.onSubscriptionOverride(OVERRIDE_CONGESTED, 0);
+        mDc.onSubscriptionOverride(SUBSCRIPTION_OVERRIDE_CONGESTED, 0);
 
         assertFalse(getNetworkCapabilities().hasCapability(NET_CAPABILITY_NOT_METERED));
         assertTrue(getNetworkCapabilities().hasCapability(NET_CAPABILITY_NOT_CONGESTED));
@@ -670,14 +731,22 @@
     @Test
     @SmallTest
     public void testSetLinkProperties() throws Exception {
-
-        DataCallResponse response = new DataCallResponse(0, -1, 1, 2,
-                ApnSetting.PROTOCOL_IP, FAKE_IFNAME,
-                Arrays.asList(new LinkAddress(NetworkUtils.numericToInetAddress(FAKE_ADDRESS), 0)),
-                Arrays.asList(NetworkUtils.numericToInetAddress(FAKE_DNS)),
-                Arrays.asList(NetworkUtils.numericToInetAddress(FAKE_GATEWAY)),
-                Arrays.asList(NetworkUtils.numericToInetAddress(FAKE_PCSCF_ADDRESS)),
-                1440);
+        DataCallResponse response = new DataCallResponse.Builder()
+                .setCause(0)
+                .setSuggestedRetryTime(-1)
+                .setId(1)
+                .setLinkStatus(2)
+                .setProtocolType(ApnSetting.PROTOCOL_IP)
+                .setInterfaceName(FAKE_IFNAME)
+                .setAddresses(Arrays.asList(
+                        new LinkAddress(InetAddresses.parseNumericAddress(FAKE_ADDRESS), 0)))
+                .setDnsAddresses(Arrays.asList(InetAddresses.parseNumericAddress(FAKE_DNS)))
+                .setGatewayAddresses(Arrays.asList(InetAddresses.parseNumericAddress(FAKE_GATEWAY)))
+                .setPcscfAddresses(
+                        Arrays.asList(InetAddresses.parseNumericAddress(FAKE_PCSCF_ADDRESS)))
+                .setMtuV4(1440)
+                .setMtuV6(1440)
+                .build();
 
         LinkProperties linkProperties = new LinkProperties();
         assertEquals(SetupResult.SUCCESS, setLinkProperties(response, linkProperties));
@@ -686,28 +755,28 @@
         assertEquals(response.getAddresses().size(), linkProperties.getAddresses().size());
         for (int i = 0; i < response.getAddresses().size(); ++i) {
             assertEquals(response.getAddresses().get(i).getAddress(),
-                    NetworkUtils.numericToInetAddress(linkProperties.getLinkAddresses().get(i)
+                    InetAddresses.parseNumericAddress(linkProperties.getLinkAddresses().get(i)
                             .getAddress().getHostAddress()));
         }
 
         assertEquals(response.getDnsAddresses().size(), linkProperties.getDnsServers().size());
         for (int i = 0; i < response.getDnsAddresses().size(); ++i) {
             assertEquals("i = " + i, response.getDnsAddresses().get(i),
-                    NetworkUtils.numericToInetAddress(
+                    InetAddresses.parseNumericAddress(
                             linkProperties.getDnsServers().get(i).getHostAddress()));
         }
 
         assertEquals(response.getGatewayAddresses().size(), linkProperties.getRoutes().size());
         for (int i = 0; i < response.getGatewayAddresses().size(); ++i) {
             assertEquals("i = " + i, response.getGatewayAddresses().get(i),
-                    NetworkUtils.numericToInetAddress(linkProperties.getRoutes().get(i)
+                    InetAddresses.parseNumericAddress(linkProperties.getRoutes().get(i)
                             .getGateway().getHostAddress()));
         }
 
         assertEquals(response.getPcscfAddresses().size(), linkProperties.getPcscfServers().size());
         for (int i = 0; i < response.getPcscfAddresses().size(); ++i) {
             assertEquals("i = " + i, response.getPcscfAddresses().get(i),
-                    NetworkUtils.numericToInetAddress(linkProperties.getPcscfServers().get(i)
+                    InetAddresses.parseNumericAddress(linkProperties.getPcscfServers().get(i)
                             .getHostAddress()));
         }
 
@@ -717,33 +786,45 @@
     @Test
     @SmallTest
     public void testSetLinkPropertiesEmptyAddress() throws Exception {
-
         // 224.224.224.224 is an invalid address.
-        DataCallResponse response = new DataCallResponse(0, -1, 1, 2,
-                ApnSetting.PROTOCOL_IP, FAKE_IFNAME,
-                null,
-                Arrays.asList(NetworkUtils.numericToInetAddress(FAKE_DNS)),
-                Arrays.asList(NetworkUtils.numericToInetAddress(FAKE_GATEWAY)),
-                Arrays.asList(NetworkUtils.numericToInetAddress(FAKE_PCSCF_ADDRESS)),
-                1440);
+        DataCallResponse response = new DataCallResponse.Builder()
+                .setCause(0)
+                .setSuggestedRetryTime(-1)
+                .setId(1)
+                .setLinkStatus(2)
+                .setProtocolType(ApnSetting.PROTOCOL_IP)
+                .setInterfaceName(FAKE_IFNAME)
+                .setDnsAddresses(Arrays.asList(InetAddresses.parseNumericAddress(FAKE_DNS)))
+                .setGatewayAddresses(Arrays.asList(InetAddresses.parseNumericAddress(FAKE_GATEWAY)))
+                .setPcscfAddresses(
+                        Arrays.asList(InetAddresses.parseNumericAddress(FAKE_PCSCF_ADDRESS)))
+                .setMtuV4(1440)
+                .setMtuV6(1440)
+                .build();
 
         LinkProperties linkProperties = new LinkProperties();
-        assertEquals(SetupResult.ERROR_INVALID_ARG,
-                setLinkProperties(response, linkProperties));
+        assertEquals(SetupResult.ERROR_INVALID_ARG, setLinkProperties(response, linkProperties));
     }
 
     @Test
     @SmallTest
     public void testSetLinkPropertiesEmptyDns() throws Exception {
-
         // Empty dns entry.
-        DataCallResponse response = new DataCallResponse(0, -1, 1, 2,
-                ApnSetting.PROTOCOL_IP, FAKE_IFNAME,
-                Arrays.asList(new LinkAddress(NetworkUtils.numericToInetAddress(FAKE_ADDRESS), 0)),
-                null,
-                Arrays.asList(NetworkUtils.numericToInetAddress(FAKE_GATEWAY)),
-                Arrays.asList(NetworkUtils.numericToInetAddress(FAKE_PCSCF_ADDRESS)),
-                1440);
+        DataCallResponse response = new DataCallResponse.Builder()
+                .setCause(0)
+                .setSuggestedRetryTime(-1)
+                .setId(1)
+                .setLinkStatus(2)
+                .setProtocolType(ApnSetting.PROTOCOL_IP)
+                .setInterfaceName(FAKE_IFNAME)
+                .setAddresses(Arrays.asList(
+                        new LinkAddress(InetAddresses.parseNumericAddress(FAKE_ADDRESS), 0)))
+                .setGatewayAddresses(Arrays.asList(InetAddresses.parseNumericAddress(FAKE_GATEWAY)))
+                .setPcscfAddresses(
+                        Arrays.asList(InetAddresses.parseNumericAddress(FAKE_PCSCF_ADDRESS)))
+                .setMtuV4(1440)
+                .setMtuV6(1440)
+                .build();
 
         // Make sure no exception was thrown
         LinkProperties linkProperties = new LinkProperties();
@@ -766,9 +847,9 @@
         // Construct a new KeepalivePacketData request as we would receive from a Network Agent,
         // and check that the packet is sent to the RIL.
         KeepalivePacketData kd = NattKeepalivePacketData.nattKeepalivePacket(
-                NetworkUtils.numericToInetAddress("1.2.3.4"),
+                InetAddresses.parseNumericAddress("1.2.3.4"),
                 1234,
-                NetworkUtils.numericToInetAddress("8.8.8.8"),
+                InetAddresses.parseNumericAddress("8.8.8.8"),
                 4500);
         mDc.obtainMessage(
                 DataConnection.EVENT_KEEPALIVE_START_REQUEST, slotId, interval, kd).sendToTarget();
@@ -790,9 +871,9 @@
         // Construct a new KeepalivePacketData request as we would receive from a Network Agent,
         // and check that the packet is sent to the RIL.
         KeepalivePacketData kd = NattKeepalivePacketData.nattKeepalivePacket(
-                NetworkUtils.numericToInetAddress("1.2.3.4"),
+                InetAddresses.parseNumericAddress("1.2.3.4"),
                 1234,
-                NetworkUtils.numericToInetAddress("8.8.8.8"),
+                InetAddresses.parseNumericAddress("8.8.8.8"),
                 4500);
         mDc.obtainMessage(
                 DataConnection.EVENT_KEEPALIVE_START_REQUEST, slotId, interval, kd).sendToTarget();
@@ -869,9 +950,9 @@
         // Construct a new KeepalivePacketData request as we would receive from a Network Agent,
         // and check that the packet is sent to the RIL.
         KeepalivePacketData kd = NattKeepalivePacketData.nattKeepalivePacket(
-                NetworkUtils.numericToInetAddress("1.2.3.4"),
+                InetAddresses.parseNumericAddress("1.2.3.4"),
                 1234,
-                NetworkUtils.numericToInetAddress("8.8.8.8"),
+                InetAddresses.parseNumericAddress("8.8.8.8"),
                 4500);
         mDc.obtainMessage(
                 DataConnection.EVENT_KEEPALIVE_START_REQUEST, slotId, interval, kd).sendToTarget();
diff --git a/tests/telephonytests/src/com/android/internal/telephony/dataconnection/DataFailCauseTest.java b/tests/telephonytests/src/com/android/internal/telephony/dataconnection/DataFailCauseTest.java
index e881cc9..24d9bd6 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/dataconnection/DataFailCauseTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/dataconnection/DataFailCauseTest.java
@@ -138,9 +138,7 @@
         mFailCauseDataList.add(new DcFailCauseData(0x10000, false, false));
         mFailCauseDataList.add(new DcFailCauseData(0x10001, true, false));
         mFailCauseDataList.add(new DcFailCauseData(0x10002, true, true));
-        mFailCauseDataList.add(new DcFailCauseData(0x10003, false, false));
         mFailCauseDataList.add(new DcFailCauseData(0x10004, false, false));
-        mFailCauseDataList.add(new DcFailCauseData(0x10005, false, false));
 
         CarrierConfigManager configManager = (CarrierConfigManager)
                 mContext.getSystemService(Context.CARRIER_CONFIG_SERVICE);
diff --git a/tests/telephonytests/src/com/android/internal/telephony/dataconnection/DcControllerTest.java b/tests/telephonytests/src/com/android/internal/telephony/dataconnection/DcControllerTest.java
index 457e1a4..a0c9913 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/dataconnection/DcControllerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/dataconnection/DcControllerTest.java
@@ -16,7 +16,6 @@
 
 package com.android.internal.telephony.dataconnection;
 
-import static com.android.internal.telephony.TelephonyTestUtils.waitForMs;
 import static com.android.internal.telephony.dataconnection.DcTrackerTest.FAKE_ADDRESS;
 import static com.android.internal.telephony.dataconnection.DcTrackerTest.FAKE_DNS;
 import static com.android.internal.telephony.dataconnection.DcTrackerTest.FAKE_GATEWAY;
@@ -31,15 +30,18 @@
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 
+import android.net.InetAddresses;
 import android.net.LinkAddress;
 import android.net.LinkProperties;
-import android.net.NetworkUtils;
 import android.os.AsyncResult;
 import android.os.Handler;
-import android.os.HandlerThread;
+import android.os.Looper;
+import android.telephony.AccessNetworkConstants;
 import android.telephony.data.ApnSetting;
 import android.telephony.data.DataCallResponse;
 import android.test.suitebuilder.annotation.SmallTest;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
 
 import com.android.internal.telephony.DctConstants;
 import com.android.internal.telephony.TelephonyTest;
@@ -50,6 +52,7 @@
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
+import org.junit.runner.RunWith;
 import org.mockito.Mock;
 
 import java.lang.reflect.Method;
@@ -57,6 +60,8 @@
 import java.util.Arrays;
 import java.util.List;
 
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper
 public class DcControllerTest extends TelephonyTest {
 
     private static final int DATA_CONNECTION_ACTIVE_PH_LINK_DORMANT = 1;
@@ -72,24 +77,6 @@
     UpdateLinkPropertyResult mResult;
 
     private DcController mDcc;
-    private DcControllerTestHandler mDcControllerTestHandler;
-
-    private class DcControllerTestHandler extends HandlerThread {
-
-        private DcControllerTestHandler(String name) {
-            super(name);
-        }
-
-        private Handler mHandler;
-
-        @Override
-        public void onLooperPrepared() {
-            mHandler = new Handler();
-            mDcc = DcController.makeDcc(mPhone, mDcTracker, mDataServiceManager, mHandler, "");
-            mDcc.start();
-            setReady(true);
-        }
-    }
 
     private IState getCurrentState() {
         try {
@@ -113,15 +100,17 @@
         LinkProperties lp = new LinkProperties();
         mResult = new UpdateLinkPropertyResult(lp);
         doReturn(mResult).when(mDc).updateLinkProperty(any(DataCallResponse.class));
+        doReturn(AccessNetworkConstants.TRANSPORT_TYPE_WWAN)
+                .when(mDataServiceManager).getTransportType();
 
-        mDcControllerTestHandler = new DcControllerTestHandler(TAG);
-        mDcControllerTestHandler.start();
-        waitUntilReady();
+        mDcc = DcController.makeDcc(mPhone, mDcTracker, mDataServiceManager,
+                new Handler(Looper.myLooper()), "");
+        mDcc.start();
+        processAllMessages();
     }
 
     @After
     public void tearDown() throws Exception {
-        mDcControllerTestHandler.quit();
         super.tearDown();
     }
 
@@ -129,22 +118,30 @@
     @SmallTest
     public void testDataDormant() throws Exception {
         assertEquals("DccDefaultState", getCurrentState().getName());
-        ArrayList<DataCallResponse> l = new ArrayList<DataCallResponse>();
-        DataCallResponse dcResponse = new DataCallResponse(0, -1, 1,
-                DATA_CONNECTION_ACTIVE_PH_LINK_DORMANT, ApnSetting.PROTOCOL_IP, FAKE_IFNAME,
-                Arrays.asList(new LinkAddress(NetworkUtils.numericToInetAddress(FAKE_ADDRESS), 0)),
-                Arrays.asList(NetworkUtils.numericToInetAddress(FAKE_DNS)),
-                Arrays.asList(NetworkUtils.numericToInetAddress(FAKE_GATEWAY)),
-                Arrays.asList(NetworkUtils.numericToInetAddress(FAKE_PCSCF_ADDRESS)),
-                1440);
-
+        ArrayList<DataCallResponse> l = new ArrayList<>();
+        DataCallResponse dcResponse = new DataCallResponse.Builder()
+                .setCause(0)
+                .setSuggestedRetryTime(-1)
+                .setId(1)
+                .setLinkStatus(DATA_CONNECTION_ACTIVE_PH_LINK_DORMANT)
+                .setProtocolType(ApnSetting.PROTOCOL_IP)
+                .setInterfaceName(FAKE_IFNAME)
+                .setAddresses(Arrays.asList(
+                        new LinkAddress(InetAddresses.parseNumericAddress(FAKE_ADDRESS), 0)))
+                .setDnsAddresses(Arrays.asList(InetAddresses.parseNumericAddress(FAKE_DNS)))
+                .setGatewayAddresses(Arrays.asList(InetAddresses.parseNumericAddress(FAKE_GATEWAY)))
+                .setPcscfAddresses(
+                        Arrays.asList(InetAddresses.parseNumericAddress(FAKE_PCSCF_ADDRESS)))
+                .setMtuV4(1440)
+                .setMtuV6(1440)
+                .build();
         l.add(dcResponse);
 
         mDc.mCid = 1;
         mDcc.addActiveDcByCid(mDc);
 
         mDcc.sendMessage(EVENT_DATA_STATE_CHANGED, new AsyncResult(null, l, null));
-        waitForMs(100);
+        processAllMessages();
 
         verify(mDcTracker, times(1)).sendStopNetStatPoll(eq(DctConstants.Activity.DORMANT));
     }
diff --git a/tests/telephonytests/src/com/android/internal/telephony/dataconnection/DcRequestTest.java b/tests/telephonytests/src/com/android/internal/telephony/dataconnection/DcRequestTest.java
new file mode 100644
index 0000000..108f87b
--- /dev/null
+++ b/tests/telephonytests/src/com/android/internal/telephony/dataconnection/DcRequestTest.java
@@ -0,0 +1,128 @@
+/*
+ * Copyright (C) 2020 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.dataconnection;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+import static org.mockito.Mockito.when;
+
+import android.net.NetworkCapabilities;
+import android.net.NetworkRequest;
+import android.net.TelephonyNetworkSpecifier;
+import android.telephony.data.ApnSetting;
+
+import com.android.internal.telephony.TelephonyTest;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+
+
+public class DcRequestTest extends TelephonyTest {
+
+    @Mock
+    ApnConfigTypeRepository mApnConfigTypeRepo;
+
+    @Before
+    public void setUp() throws Exception {
+        super.setUp(getClass().getSimpleName());
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        super.tearDown();
+    }
+
+    @Test
+    public void whenNetworkRequestInternetThenPriorityZero() {
+        NetworkRequest request =
+                new NetworkRequest.Builder()
+                        .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR)
+                        .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
+                        .build();
+
+        when(mApnConfigTypeRepo.getByType(ApnSetting.TYPE_DEFAULT))
+                .thenReturn(new ApnConfigType(ApnSetting.TYPE_DEFAULT, 0));
+        DcRequest dcRequest = DcRequest.create(request, mApnConfigTypeRepo);
+
+        assertEquals(0, dcRequest.priority);
+    }
+
+    @Test
+    public void whenNetworkRequestMcxThenApnConfigTypeMcxPriorityReturned() {
+        NetworkRequest request =
+                new NetworkRequest.Builder()
+                        .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR)
+                        //Testing out multiple transport types here
+                        .addTransportType(NetworkCapabilities.TRANSPORT_BLUETOOTH)
+                        .addCapability(NetworkCapabilities.NET_CAPABILITY_MCX)
+                        .build();
+
+        when(mApnConfigTypeRepo.getByType(ApnSetting.TYPE_MCX))
+                .thenReturn(new ApnConfigType(ApnSetting.TYPE_MCX, 21));
+        DcRequest dcRequest = DcRequest.create(request, mApnConfigTypeRepo);
+        assertEquals(21, dcRequest.priority);
+    }
+
+    @Test
+    public void whenNetworkRequestNotCellularThenDcRequestIsNull() {
+        NetworkRequest request =
+                new NetworkRequest.Builder()
+                        .addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
+                        .addCapability(NetworkCapabilities.NET_CAPABILITY_MCX)
+                        .build();
+        when(mApnConfigTypeRepo.getByType(ApnSetting.TYPE_NONE))
+                .thenReturn(null);
+        DcRequest dcRequest = DcRequest.create(request, mApnConfigTypeRepo);
+        assertNull(dcRequest);
+    }
+
+    @Test
+    public void whenNetworkRequestHasNoTransportThenPriorityStays() {
+        //This seems like an invalid case that should be handled differently.
+        NetworkRequest request =
+                new NetworkRequest.Builder()
+                        .addCapability(NetworkCapabilities.NET_CAPABILITY_MCX)
+                        .build();
+
+        when(mApnConfigTypeRepo.getByType(ApnSetting.TYPE_MCX))
+                .thenReturn(new ApnConfigType(ApnSetting.TYPE_MCX, 11));
+        DcRequest dcRequest = DcRequest.create(request, mApnConfigTypeRepo);
+        assertEquals(11, dcRequest.priority);
+    }
+
+    @Test
+    public void whenNetworkRequestNotCellularWithTelephonySpecifierThenDcRequestIsNull() {
+        TelephonyNetworkSpecifier telephonyNetworkSpecifier =
+                new TelephonyNetworkSpecifier.Builder().setSubscriptionId(5).build();
+
+        //This seems like an invalid case that should be handled differently.
+        NetworkRequest request =
+                new NetworkRequest.Builder()
+                        .addTransportType(NetworkCapabilities.TRANSPORT_BLUETOOTH)
+                        .setNetworkSpecifier(telephonyNetworkSpecifier)
+                        .build();
+
+        when(mApnConfigTypeRepo.getByType(ApnSetting.TYPE_NONE))
+                .thenReturn(null);
+
+        DcRequest dcRequest = DcRequest.create(request, mApnConfigTypeRepo);
+
+        assertNull(dcRequest);
+    }
+}
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 e2774d7..3bcf99f 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/dataconnection/DcTrackerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/dataconnection/DcTrackerTest.java
@@ -33,6 +33,7 @@
 import static org.mockito.Mockito.clearInvocations;
 import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.timeout;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
@@ -71,6 +72,7 @@
 import android.telephony.SubscriptionInfo;
 import android.telephony.SubscriptionManager;
 import android.telephony.SubscriptionPlan;
+import android.telephony.TelephonyDisplayInfo;
 import android.telephony.TelephonyManager;
 import android.telephony.data.ApnSetting;
 import android.telephony.data.DataProfile;
@@ -89,7 +91,6 @@
 import com.android.internal.telephony.ISub;
 import com.android.internal.telephony.PhoneConstants;
 import com.android.internal.telephony.TelephonyTest;
-import com.android.server.pm.PackageManagerService;
 
 import org.junit.After;
 import org.junit.Before;
@@ -108,17 +109,11 @@
 import java.util.Arrays;
 import java.util.HashMap;
 import java.util.List;
+import java.util.Objects;
+import java.util.Optional;
 import java.util.concurrent.atomic.AtomicInteger;
 
 public class DcTrackerTest extends TelephonyTest {
-
-    private final static String[] sNetworkAttributes = new String[]{
-            "mobile,0,0,0,-1,true", "mobile_mms,2,0,2,60000,true",
-            "mobile_supl,3,0,2,60000,true", "mobile_dun,4,0,2,60000,true",
-            "mobile_hipri,5,0,3,60000,true", "mobile_fota,10,0,2,60000,true",
-            "mobile_ims,11,0,2,60000,true", "mobile_cbs,12,0,2,60000,true",
-            "mobile_ia,14,0,2,-1,true", "mobile_emergency,15,0,2,-1,true"};
-
     public static final String FAKE_APN1 = "FAKE APN 1";
     public static final String FAKE_APN2 = "FAKE APN 2";
     public static final String FAKE_APN3 = "FAKE APN 3";
@@ -151,8 +146,6 @@
     @Mock
     DataConnection mDataConnection;
     @Mock
-    PackageManagerService mMockPackageManagerInternal;
-    @Mock
     Handler mHandler;
     @Mock
     NetworkPolicyManager mNetworkPolicyManager;
@@ -171,8 +164,10 @@
 
     private Message mMessage;
 
+    private CellularDataService mCellularDataService;
+
     private void addDataService() {
-        CellularDataService cellularDataService = new CellularDataService();
+        mCellularDataService = new CellularDataService();
         ServiceInfo serviceInfo = new ServiceInfo();
         serviceInfo.packageName = "com.android.phone";
         serviceInfo.permission = "android.permission.BIND_TELEPHONY_DATA_SERVICE";
@@ -181,7 +176,7 @@
                 DataService.SERVICE_INTERFACE,
                 null,
                 "com.android.phone",
-                cellularDataService.mBinder,
+                mCellularDataService.mBinder,
                 serviceInfo,
                 filter);
     }
@@ -423,7 +418,7 @@
                             "",                     // user
                             "",                     // password
                             -1,                     // authtype
-                            "mms",                  // types
+                            "mms,xcap",             // types
                             "IP",                   // protocol
                             "IP",                   // roaming_protocol
                             1,                      // carrier_enabled
@@ -445,7 +440,7 @@
 
                     return mc;
                 }
-            } else if (uri.isPathPrefixMatch(
+            } else if (isPathPrefixMatch(uri,
                     Uri.withAppendedPath(Telephony.Carriers.CONTENT_URI, "preferapnset"))) {
                 MatrixCursor mc = new MatrixCursor(
                         new String[]{Telephony.Carriers.APN_SET_ID});
@@ -475,20 +470,18 @@
         doReturn(ServiceState.RIL_RADIO_TECHNOLOGY_LTE).when(mServiceState)
                 .getRilDataRadioTechnology();
 
-        mContextFixture.putStringArrayResource(com.android.internal.R.array.networkAttributes,
-                sNetworkAttributes);
-        mContextFixture.putStringArrayResource(com.android.internal.R.array.
-                config_mobile_tcp_buffers, new String[]{
-                "umts:131072,262144,1452032,4096,16384,399360",
-                "hspa:131072,262144,2441216,4096,16384,399360",
-                "hsupa:131072,262144,2441216,4096,16384,399360",
-                "hsdpa:131072,262144,2441216,4096,16384,399360",
-                "hspap:131072,262144,2441216,4096,16384,399360",
-                "edge:16384,32768,131072,4096,16384,65536",
-                "gprs:4096,8192,24576,4096,8192,24576",
-                "1xrtt:16384,32768,131070,4096,16384,102400",
-                "evdo:131072,262144,1048576,4096,16384,524288",
-                "lte:524288,1048576,8388608,262144,524288,4194304"});
+        mContextFixture.putStringArrayResource(com.android.internal.R.array
+                .config_mobile_tcp_buffers, new String[]{
+                    "umts:131072,262144,1452032,4096,16384,399360",
+                    "hspa:131072,262144,2441216,4096,16384,399360",
+                    "hsupa:131072,262144,2441216,4096,16384,399360",
+                    "hsdpa:131072,262144,2441216,4096,16384,399360",
+                    "hspap:131072,262144,2441216,4096,16384,399360",
+                    "edge:16384,32768,131072,4096,16384,65536",
+                    "gprs:4096,8192,24576,4096,8192,24576",
+                    "1xrtt:16384,32768,131070,4096,16384,102400",
+                    "evdo:131072,262144,1048576,4096,16384,524288",
+                    "lte:524288,1048576,8388608,262144,524288,4194304"});
 
         mContextFixture.putResource(R.string.config_wwan_data_service_package,
                 "com.android.phone");
@@ -498,7 +491,6 @@
         Settings.Global.putInt(mContext.getContentResolver(),
                 Settings.Global.DATA_STALL_RECOVERY_ON_BAD_NETWORK, 0);
 
-        doReturn(true).when(mSimRecords).getRecordsLoaded();
         doReturn(PhoneConstants.State.IDLE).when(mCT).getState();
         doReturn(true).when(mSST).getDesiredPowerState();
         doReturn(true).when(mSST).getPowerStateFromCarrier();
@@ -519,7 +511,6 @@
         doReturn(1).when(mIsub).getDefaultDataSubId();
         doReturn(mIsub).when(mBinder).queryLocalInterface(anyString());
         mServiceManagerMockedServices.put("isub", mBinder);
-        mServiceManagerMockedServices.put("package", mMockPackageManagerInternal);
 
         mContextFixture.putStringArrayResource(
                 com.android.internal.R.array.config_cell_retries_per_error_code,
@@ -528,6 +519,8 @@
         mAlarmManager = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE);
         mBundle = mContextFixture.getCarrierConfigBundle();
 
+        mBundle.putBoolean(CarrierConfigManager.KEY_CARRIER_CONFIG_APPLIED_BOOL, true);
+
         mSimulatedCommands.setDataCallResult(true, createSetupDataCallResult());
         addDataService();
 
@@ -536,7 +529,8 @@
         waitUntilReady();
 
         Intent intent = new Intent(CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED);
-        intent.putExtra(CarrierConfigManager.EXTRA_SLOT_INDEX, 0);
+        intent.putExtra(CarrierConfigManager.EXTRA_SLOT_INDEX, mPhone.getPhoneId());
+        intent.putExtra(SubscriptionManager.EXTRA_SUBSCRIPTION_INDEX, mPhone.getSubId());
         mContext.sendBroadcast(intent);
 
         waitForMs(600);
@@ -547,8 +541,12 @@
     public void tearDown() throws Exception {
         logd("DcTrackerTest -tearDown");
         mDct.removeCallbacksAndMessages(null);
+        mDct.stopHandlerThread();
         mDct = null;
         mDcTrackerTestHandler.quit();
+        mDcTrackerTestHandler.join();
+        mCellularDataService.onDestroy();
+        waitForMs(100);
         super.tearDown();
     }
 
@@ -622,27 +620,40 @@
         }
     }
 
+    private void sendInitializationEvents() {
+        logd("Sending EVENT_CARRIER_CONFIG_CHANGED");
+        mDct.sendEmptyMessage(DctConstants.EVENT_CARRIER_CONFIG_CHANGED);
+        waitForLastHandlerAction(mDcTrackerTestHandler.getThreadHandler());
+
+        logd("Sending EVENT_SIM_STATE_UPDATED");
+        mDct.sendMessage(mDct.obtainMessage(DctConstants.EVENT_SIM_STATE_UPDATED,
+                TelephonyManager.SIM_STATE_LOADED, 0));
+        waitForLastHandlerAction(mDcTrackerTestHandler.getThreadHandler());
+
+        logd("Sending EVENT_DATA_CONNECTION_ATTACHED");
+        mDct.sendMessage(mDct.obtainMessage(DctConstants.EVENT_DATA_CONNECTION_ATTACHED, null));
+        waitForLastHandlerAction(mDcTrackerTestHandler.getThreadHandler());
+
+        waitForMs(200);
+    }
+
     // Test the unmetered APN setup when data is disabled.
     @Test
     @SmallTest
     public void testTrySetupDataUnmeteredDefaultNotSelected() throws Exception {
-        initApns(PhoneConstants.APN_TYPE_FOTA, new String[]{PhoneConstants.APN_TYPE_ALL});
+        initApns(PhoneConstants.APN_TYPE_XCAP, new String[]{PhoneConstants.APN_TYPE_XCAP});
         doReturn(SubscriptionManager.INVALID_SUBSCRIPTION_ID).when(mIsub).getDefaultDataSubId();
 
         mBundle.putStringArray(CarrierConfigManager.KEY_CARRIER_METERED_APN_TYPES_STRINGS,
                 new String[]{PhoneConstants.APN_TYPE_DEFAULT});
 
-        logd("Sending EVENT_RECORDS_LOADED");
-        mDct.sendMessage(mDct.obtainMessage(DctConstants.EVENT_RECORDS_LOADED, null));
-        waitForMs(200);
+        sendInitializationEvents();
 
-        logd("Sending EVENT_DATA_CONNECTION_ATTACHED");
-        mDct.sendMessage(mDct.obtainMessage(DctConstants.EVENT_DATA_CONNECTION_ATTACHED, null));
-        waitForMs(200);
+        mDct.enableApn(ApnSetting.TYPE_XCAP, DcTracker.REQUEST_TYPE_NORMAL, null);
+        waitForLastHandlerAction(mDcTrackerTestHandler.getThreadHandler());
 
-        mDct.sendMessage(mDct.obtainMessage(DctConstants.EVENT_TRY_SETUP_DATA, mApnContext));
+        // Data connection is running on a different thread. Have to wait.
         waitForMs(200);
-
         verify(mSimulatedCommandsVerifier, times(1)).setupDataCall(
                 eq(AccessNetworkType.EUTRAN), any(DataProfile.class),
                 eq(false), eq(false), eq(DataService.REQUEST_REASON_NORMAL), any(),
@@ -659,18 +670,12 @@
         boolean allowed = isDataAllowed(dataConnectionReasons);
         assertFalse(dataConnectionReasons.toString(), allowed);
 
-        logd("Sending EVENT_RECORDS_LOADED");
-        mDct.sendMessage(mDct.obtainMessage(DctConstants.EVENT_RECORDS_LOADED, null));
-        waitForMs(200);
-
-        logd("Sending EVENT_DATA_CONNECTION_ATTACHED");
-        mDct.sendMessage(mDct.obtainMessage(DctConstants.EVENT_DATA_CONNECTION_ATTACHED, null));
-        waitForMs(200);
-
         logd("Sending EVENT_ENABLE_APN");
         // APN id 0 is APN_TYPE_DEFAULT
         mDct.enableApn(ApnSetting.TYPE_DEFAULT, DcTracker.REQUEST_TYPE_NORMAL, null);
-        waitForMs(200);
+        waitForLastHandlerAction(mDcTrackerTestHandler.getThreadHandler());
+
+        sendInitializationEvents();
 
         dataConnectionReasons = new DataConnectionReasons();
         allowed = isDataAllowed(dataConnectionReasons);
@@ -694,7 +699,7 @@
         AsyncResult ar = new AsyncResult(null,
                 new Pair<>(true, DataEnabledSettings.REASON_USER_DATA_ENABLED), null);
         mDct.sendMessage(mDct.obtainMessage(DctConstants.EVENT_DATA_ENABLED_CHANGED, ar));
-        waitForMs(200);
+        waitForLastHandlerAction(mDcTrackerTestHandler.getThreadHandler());
 
         // LOST_CONNECTION(0x10004) is a non-permanent failure, so we'll retry data setup later.
         SetupDataCallResult result = createSetupDataCallResult();
@@ -707,18 +712,12 @@
         boolean allowed = isDataAllowed(dataConnectionReasons);
         assertFalse(dataConnectionReasons.toString(), allowed);
 
-        logd("Sending EVENT_RECORDS_LOADED");
-        mDct.sendMessage(mDct.obtainMessage(DctConstants.EVENT_RECORDS_LOADED, null));
-        waitForMs(200);
-
-        logd("Sending EVENT_DATA_CONNECTION_ATTACHED");
-        mDct.sendMessage(mDct.obtainMessage(DctConstants.EVENT_DATA_CONNECTION_ATTACHED, null));
-        waitForMs(200);
-
         logd("Sending EVENT_ENABLE_APN");
         // APN id 0 is APN_TYPE_DEFAULT
         mDct.enableApn(ApnSetting.TYPE_DEFAULT, DcTracker.REQUEST_TYPE_NORMAL, null);
-        waitForMs(200);
+        waitForLastHandlerAction(mDcTrackerTestHandler.getThreadHandler());
+
+        sendInitializationEvents();
 
         dataConnectionReasons = new DataConnectionReasons();
         allowed = isDataAllowed(dataConnectionReasons);
@@ -732,23 +731,18 @@
                 any(Message.class));
         verifyDataProfile(dpCaptor.getValue(), FAKE_APN1, 0, 21, 1, NETWORK_TYPE_LTE_BITMASK);
 
-        // Verify the retry manger schedule another data call setup.
-        verify(mAlarmManager, times(1)).setExact(eq(AlarmManager.ELAPSED_REALTIME_WAKEUP),
-                anyLong(), any(PendingIntent.class));
-
         // This time we'll let RIL command succeed.
         mSimulatedCommands.setDataCallResult(true, createSetupDataCallResult());
 
-        // Simulate the timer expires.
-        Intent intent = new Intent("com.android.internal.telephony.data-reconnect.default");
-        intent.putExtra("reconnect_alarm_extra_type", PhoneConstants.APN_TYPE_DEFAULT);
-        intent.putExtra("reconnect_alarm_extra_transport_type",
-                AccessNetworkConstants.TRANSPORT_TYPE_WWAN);
-        intent.putExtra(PhoneConstants.SUBSCRIPTION_KEY, 0);
-        intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
-        mContext.sendBroadcast(intent);
-        waitForMs(200);
+        //Send event for reconnecting data
+        initApns(PhoneConstants.APN_TYPE_DEFAULT, new String[]{PhoneConstants.APN_TYPE_ALL});
+        mDct.sendMessage(mDct.obtainMessage(DctConstants.EVENT_DATA_RECONNECT,
+                        mPhone.getPhoneId(), AccessNetworkConstants.TRANSPORT_TYPE_WWAN,
+                        mApnContext));
+        waitForLastHandlerAction(mDcTrackerTestHandler.getThreadHandler());
 
+        // Data connection is running on a different thread. Have to wait.
+        waitForMs(200);
         dpCaptor = ArgumentCaptor.forClass(DataProfile.class);
         // Verify if RIL command was sent properly.
         verify(mSimulatedCommandsVerifier, times(2)).setupDataCall(
@@ -773,13 +767,7 @@
         mDct.enableApn(ApnSetting.TYPE_IMS, DcTracker.REQUEST_TYPE_NORMAL, null);
         mDct.enableApn(ApnSetting.TYPE_DEFAULT, DcTracker.REQUEST_TYPE_NORMAL, null);
 
-        logd("Sending EVENT_RECORDS_LOADED");
-        mDct.sendMessage(mDct.obtainMessage(DctConstants.EVENT_RECORDS_LOADED, null));
-        waitForMs(200);
-
-        logd("Sending EVENT_DATA_CONNECTION_ATTACHED");
-        mDct.sendMessage(mDct.obtainMessage(DctConstants.EVENT_DATA_CONNECTION_ATTACHED, null));
-        waitForMs(200);
+        sendInitializationEvents();
 
         ArgumentCaptor<DataProfile> dpCaptor = ArgumentCaptor.forClass(DataProfile.class);
         verify(mSimulatedCommandsVerifier, times(2)).setupDataCall(
@@ -794,8 +782,10 @@
         AsyncResult ar = new AsyncResult(null,
                 new Pair<>(false, DataEnabledSettings.REASON_USER_DATA_ENABLED), null);
         mDct.sendMessage(mDct.obtainMessage(DctConstants.EVENT_DATA_ENABLED_CHANGED, ar));
-        waitForMs(200);
+        waitForLastHandlerAction(mDcTrackerTestHandler.getThreadHandler());
 
+        // Data connection is running on a different thread. Have to wait.
+        waitForMs(200);
         // expected tear down all metered DataConnections
         verify(mSimulatedCommandsVerifier, times(1)).deactivateDataCall(
                 eq(DataService.REQUEST_REASON_NORMAL), anyInt(),
@@ -813,27 +803,41 @@
         mDct.enableApn(ApnSetting.TYPE_MMS, DcTracker.REQUEST_TYPE_NORMAL, null);
         mDct.enableApn(ApnSetting.TYPE_DEFAULT, DcTracker.REQUEST_TYPE_NORMAL, null);
 
-        logd("Sending EVENT_RECORDS_LOADED");
-        mDct.sendMessage(mDct.obtainMessage(DctConstants.EVENT_RECORDS_LOADED, null));
-        waitForMs(200);
-
-        logd("Sending EVENT_DATA_CONNECTION_ATTACHED");
-        mDct.sendMessage(mDct.obtainMessage(DctConstants.EVENT_DATA_CONNECTION_ATTACHED, null));
-        waitForMs(500);
+        sendInitializationEvents();
 
         ArgumentCaptor<DataProfile> dpCaptor = ArgumentCaptor.forClass(DataProfile.class);
         verify(mSimulatedCommandsVerifier, times(2)).setupDataCall(
                 eq(AccessNetworkType.EUTRAN), dpCaptor.capture(),
                 eq(false), eq(false), eq(DataService.REQUEST_REASON_NORMAL), any(),
                 any(Message.class));
-        verifyDataProfile(dpCaptor.getValue(), FAKE_APN1, 0, 21, 1, NETWORK_TYPE_LTE_BITMASK);
+
+
+        List<DataProfile> dataProfiles = dpCaptor.getAllValues();
+        assertEquals(2, dataProfiles.size());
+
+        //Verify FAKE_APN1
+        Optional<DataProfile> fakeApn1 = dataProfiles.stream()
+                .filter(dp -> dp.getApn().equals(FAKE_APN1))
+                .findFirst();
+        assertTrue(fakeApn1.isPresent());
+        verifyDataProfile(fakeApn1.get(), FAKE_APN1, 0, 21, 1, NETWORK_TYPE_LTE_BITMASK);
+
+        //Verify FAKE_APN6
+        Optional<DataProfile> fakeApn6 = dataProfiles.stream()
+                .filter(dp -> dp.getApn().equals(FAKE_APN6))
+                .findFirst();
+        assertTrue(fakeApn6.isPresent());
+        verifyDataProfile(fakeApn6.get(), FAKE_APN6, 0, ApnSetting.TYPE_MMS | ApnSetting.TYPE_XCAP,
+                1, NETWORK_TYPE_LTE_BITMASK);
 
         logd("Sending DATA_DISABLED_CMD for default data");
         doReturn(false).when(mDataEnabledSettings).isDataEnabled();
         doReturn(false).when(mDataEnabledSettings).isDataEnabled(anyInt());
         mDct.obtainMessage(DctConstants.EVENT_DATA_ENABLED_OVERRIDE_RULES_CHANGED).sendToTarget();
-        waitForMs(200);
+        waitForLastHandlerAction(mDcTrackerTestHandler.getThreadHandler());
 
+        // Data connection is running on a different thread. Have to wait.
+        waitForMs(200);
         // expected tear down all metered DataConnections
         verify(mSimulatedCommandsVerifier, times(2)).deactivateDataCall(
                 eq(DataService.REQUEST_REASON_NORMAL), anyInt(),
@@ -844,8 +848,10 @@
         clearInvocations(mSimulatedCommandsVerifier);
         doReturn(true).when(mDataEnabledSettings).isDataEnabled(ApnSetting.TYPE_MMS);
         mDct.obtainMessage(DctConstants.EVENT_DATA_ENABLED_OVERRIDE_RULES_CHANGED).sendToTarget();
-        waitForMs(200);
+        waitForLastHandlerAction(mDcTrackerTestHandler.getThreadHandler());
 
+        // Data connection is running on a different thread. Have to wait.
+        waitForMs(200);
         verify(mSimulatedCommandsVerifier, times(1)).setupDataCall(
                 eq(AccessNetworkType.EUTRAN), dpCaptor.capture(),
                 eq(false), eq(false), eq(DataService.REQUEST_REASON_NORMAL), any(),
@@ -873,16 +879,7 @@
         mDct.enableApn(ApnSetting.TYPE_DEFAULT, DcTracker.REQUEST_TYPE_NORMAL, null);
         waitForHandlerAction(mDct, 1000);
 
-        logd("Sending EVENT_RECORDS_LOADED");
-        mDct.sendMessage(mDct.obtainMessage(DctConstants.EVENT_RECORDS_LOADED, null));
-        waitForHandlerAction(mDct, 1000);
-
-        logd("Sending EVENT_DATA_CONNECTION_ATTACHED");
-        mDct.sendMessage(mDct.obtainMessage(DctConstants.EVENT_DATA_CONNECTION_ATTACHED, null));
-        waitForHandlerAction(mDct, 1000);
-        logd("Handling EVENT_DATA_CONNECTION_ATTACHED complete");
-        // dataconnection is on a different handler
-        waitForMs(200);
+        sendInitializationEvents();
 
         ArgumentCaptor<DataProfile> dpCaptor = ArgumentCaptor.forClass(DataProfile.class);
         verify(mSimulatedCommandsVerifier, times(2)).setupDataCall(
@@ -896,8 +893,10 @@
         logd("Sending DISABLE_ROAMING_CMD");
         mDct.setDataRoamingEnabledByUser(false);
         mDct.sendMessage(mDct.obtainMessage(DctConstants.EVENT_ROAMING_ON));
-        waitForMs(200);
+        waitForLastHandlerAction(mDcTrackerTestHandler.getThreadHandler());
 
+        // Data connection is running on a different thread. Have to wait.
+        waitForMs(200);
         // expected tear down all metered DataConnections
         verify(mSimulatedCommandsVerifier, times(1)).deactivateDataCall(
                 eq(DataService.REQUEST_REASON_NORMAL), anyInt(),
@@ -908,7 +907,7 @@
 
         // reset roaming settings / data enabled settings at end of this test
         mDct.setDataRoamingEnabledByUser(roamingEnabled);
-        waitForMs(200);
+        waitForLastHandlerAction(mDcTrackerTestHandler.getThreadHandler());
     }
 
     @Test
@@ -930,16 +929,10 @@
         logd("Sending DISABLE_ROAMING_CMD");
         mDct.setDataRoamingEnabledByUser(false);
 
-        logd("Sending EVENT_RECORDS_LOADED");
-        mDct.sendMessage(mDct.obtainMessage(DctConstants.EVENT_RECORDS_LOADED, null));
-        waitForMs(200);
+        sendInitializationEvents();
 
-        logd("Sending EVENT_DATA_CONNECTION_ATTACHED");
-        mDct.sendMessage(mDct.obtainMessage(DctConstants.EVENT_DATA_CONNECTION_ATTACHED, null));
-        waitForMs(200);
-
-        waitForMs(200);
         ArgumentCaptor<DataProfile> dpCaptor = ArgumentCaptor.forClass(DataProfile.class);
+
         verify(mSimulatedCommandsVerifier, times(1)).setupDataCall(
                 eq(AccessNetworkType.EUTRAN), dpCaptor.capture(),
                 eq(false), eq(false), eq(DataService.REQUEST_REASON_NORMAL), any(),
@@ -952,7 +945,7 @@
 
         // reset roaming settings / data enabled settings at end of this test
         mDct.setDataRoamingEnabledByUser(roamingEnabled);
-        waitForMs(200);
+        waitForLastHandlerAction(mDcTrackerTestHandler.getThreadHandler());
     }
 
     // Test the default data switch scenario.
@@ -960,67 +953,14 @@
     @Test
     @MediumTest
     public void testDDSResetAutoAttach() throws Exception {
-
-        ContentResolver resolver = mContext.getContentResolver();
-        Settings.Global.putInt(resolver, Settings.Global.DEVICE_PROVISIONED, 1);
-
         mContextFixture.putBooleanResource(
                 com.android.internal.R.bool.config_auto_attach_data_on_creation, true);
-
-        mSimulatedCommands.setDataCallResult(true, createSetupDataCallResult());
-
-        AsyncResult ar = new AsyncResult(null,
-                new Pair<>(true, DataEnabledSettings.REASON_USER_DATA_ENABLED), null);
-        mDct.sendMessage(mDct.obtainMessage(DctConstants.EVENT_DATA_ENABLED_CHANGED, ar));
-        waitForMs(200);
-
-        DataConnectionReasons dataConnectionReasons = new DataConnectionReasons();
-        boolean allowed = isDataAllowed(dataConnectionReasons);
-        assertFalse(dataConnectionReasons.toString(), allowed);
-
-        ArgumentCaptor<Integer> intArgumentCaptor = ArgumentCaptor.forClass(Integer.class);
-        verify(mUiccController, times(1)).registerForIccChanged(eq(mDct),
-                intArgumentCaptor.capture(), eq(null));
-        // Ideally this should send EVENT_ICC_CHANGED.
-        mDct.sendMessage(mDct.obtainMessage(intArgumentCaptor.getValue(), null));
-        waitForMs(100);
-
-        verify(mSimRecords, times(1)).registerForRecordsLoaded(eq(mDct),
-                intArgumentCaptor.capture(), eq(null));
-        // Ideally this should send EVENT_RECORDS_LOADED.
-        mDct.sendMessage(mDct.obtainMessage(intArgumentCaptor.getValue(), null));
-        waitForMs(100);
-
-        verify(mSST, times(1)).registerForDataConnectionAttached(
-                eq(AccessNetworkConstants.TRANSPORT_TYPE_WWAN), eq(mDct),
-                intArgumentCaptor.capture(), eq(null));
-        // Ideally this should send EVENT_DATA_CONNECTION_ATTACHED");
-        mDct.sendMessage(mDct.obtainMessage(intArgumentCaptor.getValue(), null));
-        waitForMs(200);
-
-        NetworkRequest nr = new NetworkRequest.Builder()
-                .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET).build();
-        mDct.requestNetwork(nr, DcTracker.REQUEST_TYPE_NORMAL, null);
-        waitForMs(200);
-
-        verifyDataConnected(FAKE_APN1);
-
+        testDataSetup();
         assertTrue(mDct.shouldAutoAttach());
-        mDct.update();
+        mDct.sendEmptyMessage(DctConstants.EVENT_CARRIER_CONFIG_CHANGED);
+        waitForLastHandlerAction(mDcTrackerTestHandler.getThreadHandler());
         // The auto attach flag should be reset after update
         assertFalse(mDct.shouldAutoAttach());
-
-        verify(mSST, times(1)).registerForDataConnectionDetached(
-                eq(AccessNetworkConstants.TRANSPORT_TYPE_WWAN), eq(mDct),
-                intArgumentCaptor.capture(), eq(null));
-        // Ideally this should send EVENT_DATA_CONNECTION_DETACHED
-        mDct.sendMessage(mDct.obtainMessage(intArgumentCaptor.getValue(), null));
-        waitForMs(200);
-
-        // Data should not be allowed since auto attach flag has been reset.
-        dataConnectionReasons = new DataConnectionReasons();
-        allowed = isDataAllowed(dataConnectionReasons);
-        assertFalse(dataConnectionReasons.toString(), allowed);
     }
 
     // Test for API carrierActionSetMeteredApnsEnabled.
@@ -1039,13 +979,7 @@
         mDct.enableApn(ApnSetting.TYPE_IMS, DcTracker.REQUEST_TYPE_NORMAL, null);
         mDct.enableApn(ApnSetting.TYPE_DEFAULT, DcTracker.REQUEST_TYPE_NORMAL, null);
 
-        logd("Sending EVENT_RECORDS_LOADED");
-        mDct.sendMessage(mDct.obtainMessage(DctConstants.EVENT_RECORDS_LOADED, null));
-        waitForMs(200);
-
-        logd("Sending EVENT_DATA_CONNECTION_ATTACHED");
-        mDct.sendMessage(mDct.obtainMessage(DctConstants.EVENT_DATA_CONNECTION_ATTACHED, null));
-        waitForMs(200);
+        sendInitializationEvents();
 
         ArgumentCaptor<DataProfile> dpCaptor = ArgumentCaptor.forClass(DataProfile.class);
         verify(mSimulatedCommandsVerifier, times(2)).setupDataCall(
@@ -1058,8 +992,10 @@
         AsyncResult ar = new AsyncResult(null,
                 new Pair<>(false, DataEnabledSettings.REASON_DATA_ENABLED_BY_CARRIER), null);
         mDct.sendMessage(mDct.obtainMessage(DctConstants.EVENT_DATA_ENABLED_CHANGED, ar));
-        waitForMs(200);
+        waitForLastHandlerAction(mDcTrackerTestHandler.getThreadHandler());
 
+        // Data connection is running on a different thread. Have to wait.
+        waitForMs(200);
         // Validate all metered data connections have been torn down
         verify(mSimulatedCommandsVerifier, times(1)).deactivateDataCall(
                 eq(DataService.REQUEST_REASON_NORMAL), anyInt(),
@@ -1070,6 +1006,8 @@
 
     private void initApns(String targetApn, String[] canHandleTypes) {
         doReturn(targetApn).when(mApnContext).getApnType();
+        doReturn(ApnSetting.getApnTypesBitmaskFromString(mApnContext.getApnType()))
+                .when(mApnContext).getApnTypeBitmask();
         doReturn(true).when(mApnContext).isConnectable();
         ApnSetting apnSetting = createApnSetting(ApnSetting.getApnTypesBitmaskFromString(
                 TextUtils.join(",", canHandleTypes)));
@@ -1086,14 +1024,30 @@
     @Test
     @SmallTest
     public void testTrySetupDataEmergencyApn() throws Exception {
-        initApns(PhoneConstants.APN_TYPE_EMERGENCY, new String[]{PhoneConstants.APN_TYPE_ALL});
+        initApns(PhoneConstants.APN_TYPE_EMERGENCY,
+                new String[]{PhoneConstants.APN_TYPE_EMERGENCY});
         mDct.sendMessage(mDct.obtainMessage(DctConstants.EVENT_TRY_SETUP_DATA, mApnContext));
+        waitForLastHandlerAction(mDcTrackerTestHandler.getThreadHandler());
+
         waitForMs(200);
 
         verify(mSimulatedCommandsVerifier, times(1)).setupDataCall(
-                eq(AccessNetworkType.EUTRAN), any(DataProfile.class),
-                eq(false), eq(false), eq(DataService.REQUEST_REASON_NORMAL), any(),
-                any(Message.class));
+                eq(AccessNetworkType.EUTRAN), any(DataProfile.class), eq(false), eq(false),
+                eq(DataService.REQUEST_REASON_NORMAL), any(), any(Message.class));
+    }
+
+    // Test the XCAP APN setup.
+    @Test
+    @SmallTest
+    public void testTrySetupDataXcapApn() throws Exception {
+        initApns(PhoneConstants.APN_TYPE_XCAP, new String[]{PhoneConstants.APN_TYPE_XCAP});
+        mDct.enableApn(ApnSetting.TYPE_XCAP, DcTracker.REQUEST_TYPE_NORMAL, null);
+
+        sendInitializationEvents();
+
+        verify(mSimulatedCommandsVerifier, times(1)).setupDataCall(
+                eq(AccessNetworkType.EUTRAN), any(DataProfile.class), eq(false), eq(false),
+                eq(DataService.REQUEST_REASON_NORMAL), any(), any(Message.class));
     }
 
     @Test
@@ -1103,17 +1057,10 @@
                 new String[]{PhoneConstants.APN_TYPE_SUPL, PhoneConstants.APN_TYPE_DEFAULT});
         mBundle.putStringArray(CarrierConfigManager.KEY_CARRIER_METERED_APN_TYPES_STRINGS,
                 new String[]{PhoneConstants.APN_TYPE_DEFAULT});
+        mDct.enableApn(ApnSetting.TYPE_DEFAULT, DcTracker.REQUEST_TYPE_NORMAL, null);
+        mDct.enableApn(ApnSetting.TYPE_SUPL, DcTracker.REQUEST_TYPE_NORMAL, null);
 
-        logd("Sending EVENT_RECORDS_LOADED");
-        mDct.sendMessage(mDct.obtainMessage(DctConstants.EVENT_RECORDS_LOADED, null));
-        waitForMs(200);
-
-        logd("Sending EVENT_DATA_CONNECTION_ATTACHED");
-        mDct.sendMessage(mDct.obtainMessage(DctConstants.EVENT_DATA_CONNECTION_ATTACHED, null));
-        waitForMs(200);
-
-        mDct.sendMessage(mDct.obtainMessage(DctConstants.EVENT_TRY_SETUP_DATA, mApnContext));
-        waitForMs(200);
+        sendInitializationEvents();
 
         // Assert that both APN_TYPE_SUPL & APN_TYPE_DEFAULT are connected even we only setup data
         // for APN_TYPE_SUPL
@@ -1125,24 +1072,16 @@
     @Test
     @SmallTest
     public void testTrySetupDataUnmeteredDataDisabled() throws Exception {
-        initApns(PhoneConstants.APN_TYPE_FOTA, new String[]{PhoneConstants.APN_TYPE_ALL});
-        //mDct.setUserDataEnabled(false);
+        initApns(PhoneConstants.APN_TYPE_SUPL, new String[]{PhoneConstants.APN_TYPE_SUPL});
         doReturn(false).when(mDataEnabledSettings).isDataEnabled();
         doReturn(false).when(mDataEnabledSettings).isDataEnabled(anyInt());
 
         mBundle.putStringArray(CarrierConfigManager.KEY_CARRIER_METERED_APN_TYPES_STRINGS,
-                new String[]{PhoneConstants.APN_TYPE_DEFAULT});
+                new String[]{PhoneConstants.APN_TYPE_FOTA});
 
-        logd("Sending EVENT_RECORDS_LOADED");
-        mDct.sendMessage(mDct.obtainMessage(DctConstants.EVENT_RECORDS_LOADED, null));
-        waitForMs(200);
+        mDct.enableApn(ApnSetting.TYPE_SUPL, DcTracker.REQUEST_TYPE_NORMAL, null);
 
-        logd("Sending EVENT_DATA_CONNECTION_ATTACHED");
-        mDct.sendMessage(mDct.obtainMessage(DctConstants.EVENT_DATA_CONNECTION_ATTACHED, null));
-        waitForMs(200);
-
-        mDct.sendMessage(mDct.obtainMessage(DctConstants.EVENT_TRY_SETUP_DATA, mApnContext));
-        waitForMs(200);
+        sendInitializationEvents();
 
         verify(mSimulatedCommandsVerifier, times(1)).setupDataCall(
                 eq(AccessNetworkType.EUTRAN), any(DataProfile.class),
@@ -1150,28 +1089,43 @@
                 any(Message.class));
     }
 
+    // Test the unmetered default APN setup when data is disabled. Default APN should always honor
+    // the users's setting.
+    @Test
+    @SmallTest
+    public void testTrySetupDataUnmeteredDefaultDataDisabled() throws Exception {
+        initApns(PhoneConstants.APN_TYPE_DEFAULT, new String[]{PhoneConstants.APN_TYPE_DEFAULT});
+        doReturn(false).when(mDataEnabledSettings).isDataEnabled();
+        doReturn(false).when(mDataEnabledSettings).isDataEnabled(anyInt());
+
+        mBundle.putStringArray(CarrierConfigManager.KEY_CARRIER_METERED_APN_TYPES_STRINGS,
+                new String[]{PhoneConstants.APN_TYPE_MMS});
+
+        mDct.enableApn(ApnSetting.TYPE_DEFAULT, DcTracker.REQUEST_TYPE_NORMAL, null);
+
+        sendInitializationEvents();
+
+        verify(mSimulatedCommandsVerifier, never()).setupDataCall(
+                eq(AccessNetworkType.EUTRAN), any(DataProfile.class),
+                eq(false), eq(false), eq(DataService.REQUEST_REASON_NORMAL), any(),
+                any(Message.class));
+    }
+
+
     // Test the metered APN setup when data is disabled.
     @Test
     @SmallTest
     public void testTrySetupMeteredDataDisabled() throws Exception {
-        initApns(PhoneConstants.APN_TYPE_DEFAULT, new String[]{PhoneConstants.APN_TYPE_ALL});
-        //mDct.setUserDataEnabled(false);
+        initApns(PhoneConstants.APN_TYPE_DEFAULT, new String[]{PhoneConstants.APN_TYPE_DEFAULT});
         doReturn(false).when(mDataEnabledSettings).isDataEnabled();
         doReturn(false).when(mDataEnabledSettings).isDataEnabled(anyInt());
 
         mBundle.putStringArray(CarrierConfigManager.KEY_CARRIER_METERED_APN_TYPES_STRINGS,
                 new String[]{PhoneConstants.APN_TYPE_DEFAULT});
 
-        logd("Sending EVENT_RECORDS_LOADED");
-        mDct.sendMessage(mDct.obtainMessage(DctConstants.EVENT_RECORDS_LOADED, null));
-        waitForMs(200);
+        mDct.enableApn(ApnSetting.TYPE_DEFAULT, DcTracker.REQUEST_TYPE_NORMAL, null);
 
-        logd("Sending EVENT_DATA_CONNECTION_ATTACHED");
-        mDct.sendMessage(mDct.obtainMessage(DctConstants.EVENT_DATA_CONNECTION_ATTACHED, null));
-        waitForMs(200);
-
-        mDct.sendMessage(mDct.obtainMessage(DctConstants.EVENT_TRY_SETUP_DATA, mApnContext));
-        waitForMs(200);
+        sendInitializationEvents();
 
         verify(mSimulatedCommandsVerifier, times(0)).setupDataCall(anyInt(), any(DataProfile.class),
                 eq(false), eq(false), eq(DataService.REQUEST_REASON_NORMAL), any(),
@@ -1182,27 +1136,24 @@
     @Test
     @SmallTest
     public void testTrySetupRestrictedDataDisabled() throws Exception {
-        initApns(PhoneConstants.APN_TYPE_DEFAULT, new String[]{PhoneConstants.APN_TYPE_ALL});
-        doReturn(true).when(mApnContext).hasRestrictedRequests(eq(true));
-
-        //mDct.setUserDataEnabled(false);
+        initApns(PhoneConstants.APN_TYPE_DEFAULT, new String[]{PhoneConstants.APN_TYPE_DEFAULT});
         doReturn(false).when(mDataEnabledSettings).isDataEnabled();
         doReturn(false).when(mDataEnabledSettings).isDataEnabled(anyInt());
 
         mBundle.putStringArray(CarrierConfigManager.KEY_CARRIER_METERED_APN_TYPES_STRINGS,
                 new String[]{PhoneConstants.APN_TYPE_DEFAULT});
 
-        logd("Sending EVENT_RECORDS_LOADED");
-        mDct.sendMessage(mDct.obtainMessage(DctConstants.EVENT_RECORDS_LOADED, null));
-        waitForMs(200);
+        sendInitializationEvents();
 
-        logd("Sending EVENT_DATA_CONNECTION_ATTACHED");
-        mDct.sendMessage(mDct.obtainMessage(DctConstants.EVENT_DATA_CONNECTION_ATTACHED, null));
-        waitForMs(200);
+        NetworkRequest nr = new NetworkRequest.Builder()
+                .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
+                .removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED)
+                .build();
+        mDct.requestNetwork(nr, DcTracker.REQUEST_TYPE_NORMAL, null);
+        waitForLastHandlerAction(mDcTrackerTestHandler.getThreadHandler());
 
-        mDct.sendMessage(mDct.obtainMessage(DctConstants.EVENT_TRY_SETUP_DATA, mApnContext));
+        // Data connection is running on a different thread. Have to wait.
         waitForMs(200);
-
         verify(mSimulatedCommandsVerifier, times(1)).setupDataCall(anyInt(), any(DataProfile.class),
                 eq(false), eq(false), eq(DataService.REQUEST_REASON_NORMAL), any(),
                 any(Message.class));
@@ -1212,8 +1163,7 @@
     @Test
     @SmallTest
     public void testTrySetupRestrictedRoamingDisabled() throws Exception {
-        initApns(PhoneConstants.APN_TYPE_DEFAULT, new String[]{PhoneConstants.APN_TYPE_ALL});
-        doReturn(true).when(mApnContext).hasRestrictedRequests(eq(true));
+        initApns(PhoneConstants.APN_TYPE_DEFAULT, new String[]{PhoneConstants.APN_TYPE_DEFAULT});
 
         mDct.setDataRoamingEnabledByUser(false);
         mBundle.putStringArray(CarrierConfigManager.KEY_CARRIER_METERED_APN_TYPES_STRINGS,
@@ -1221,17 +1171,17 @@
         //user is in roaming
         doReturn(true).when(mServiceState).getDataRoaming();
 
-        logd("Sending EVENT_RECORDS_LOADED");
-        mDct.sendMessage(mDct.obtainMessage(DctConstants.EVENT_RECORDS_LOADED, null));
-        waitForMs(200);
+        sendInitializationEvents();
 
-        logd("Sending EVENT_DATA_CONNECTION_ATTACHED");
-        mDct.sendMessage(mDct.obtainMessage(DctConstants.EVENT_DATA_CONNECTION_ATTACHED, null));
-        waitForMs(200);
+        NetworkRequest nr = new NetworkRequest.Builder()
+                .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
+                .removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED)
+                .build();
+        mDct.requestNetwork(nr, DcTracker.REQUEST_TYPE_NORMAL, null);
+        waitForLastHandlerAction(mDcTrackerTestHandler.getThreadHandler());
 
-        mDct.sendMessage(mDct.obtainMessage(DctConstants.EVENT_TRY_SETUP_DATA, mApnContext));
+        // Data connection is running on a different thread. Have to wait.
         waitForMs(200);
-
         verify(mSimulatedCommandsVerifier, times(1)).setupDataCall(anyInt(), any(DataProfile.class),
                 eq(false), eq(false), eq(DataService.REQUEST_REASON_NORMAL), any(),
                 any(Message.class));
@@ -1247,16 +1197,7 @@
         mBundle.putStringArray(CarrierConfigManager.KEY_CARRIER_METERED_APN_TYPES_STRINGS,
                 new String[]{PhoneConstants.APN_TYPE_DEFAULT});
 
-        logd("Sending EVENT_RECORDS_LOADED");
-        mDct.sendMessage(mDct.obtainMessage(DctConstants.EVENT_RECORDS_LOADED, null));
-        waitForMs(200);
-
-        logd("Sending EVENT_DATA_CONNECTION_ATTACHED");
-        mDct.sendMessage(mDct.obtainMessage(DctConstants.EVENT_DATA_CONNECTION_ATTACHED, null));
-        waitForMs(200);
-
-        mDct.sendMessage(mDct.obtainMessage(DctConstants.EVENT_TRY_SETUP_DATA, mApnContext));
-        waitForMs(200);
+        sendInitializationEvents();
 
         verify(mSimulatedCommandsVerifier, times(0)).setupDataCall(anyInt(), any(DataProfile.class),
                 eq(false), eq(false), eq(DataService.REQUEST_REASON_NORMAL), any(),
@@ -1279,16 +1220,7 @@
         mBundle.putStringArray(CarrierConfigManager.KEY_CARRIER_METERED_APN_TYPES_STRINGS,
                 new String[]{PhoneConstants.APN_TYPE_DEFAULT});
 
-        logd("Sending EVENT_RECORDS_LOADED");
-        mDct.sendMessage(mDct.obtainMessage(DctConstants.EVENT_RECORDS_LOADED, null));
-        waitForMs(200);
-
-        logd("Sending EVENT_DATA_CONNECTION_ATTACHED");
-        mDct.sendMessage(mDct.obtainMessage(DctConstants.EVENT_DATA_CONNECTION_ATTACHED, null));
-        waitForMs(200);
-
-        mDct.sendMessage(mDct.obtainMessage(DctConstants.EVENT_TRY_SETUP_DATA, mApnContext));
-        waitForMs(200);
+        sendInitializationEvents();
 
         verify(mSimulatedCommandsVerifier, times(0)).setupDataCall(anyInt(), any(DataProfile.class),
                 eq(false), eq(false), eq(DataService.REQUEST_REASON_NORMAL), any(),
@@ -1305,16 +1237,7 @@
         mBundle.putStringArray(CarrierConfigManager.KEY_CARRIER_METERED_APN_TYPES_STRINGS,
                 new String[]{PhoneConstants.APN_TYPE_DEFAULT});
 
-        logd("Sending EVENT_RECORDS_LOADED");
-        mDct.sendMessage(mDct.obtainMessage(DctConstants.EVENT_RECORDS_LOADED, null));
-        waitForMs(200);
-
-        logd("Sending EVENT_DATA_CONNECTION_ATTACHED");
-        mDct.sendMessage(mDct.obtainMessage(DctConstants.EVENT_DATA_CONNECTION_ATTACHED, null));
-        waitForMs(200);
-
-        mDct.sendMessage(mDct.obtainMessage(DctConstants.EVENT_TRY_SETUP_DATA, mApnContext));
-        waitForMs(200);
+        sendInitializationEvents();
 
         verify(mSimulatedCommandsVerifier, times(0)).setupDataCall(anyInt(), any(DataProfile.class),
                 eq(false), eq(false), eq(DataService.REQUEST_REASON_NORMAL), any(),
@@ -1323,6 +1246,7 @@
 
     // Test update waiting apn list when on data rat change
     @FlakyTest /* flakes 0.86% of the time */
+    @Ignore
     @Test
     @SmallTest
     public void testUpdateWaitingApnListOnDataRatChange() throws Exception {
@@ -1337,13 +1261,7 @@
         mDct.enableApn(ApnSetting.TYPE_DEFAULT, DcTracker.REQUEST_TYPE_NORMAL, null);
         initApns(PhoneConstants.APN_TYPE_DEFAULT, new String[]{PhoneConstants.APN_TYPE_ALL});
 
-        logd("Sending EVENT_RECORDS_LOADED");
-        mDct.sendMessage(mDct.obtainMessage(DctConstants.EVENT_RECORDS_LOADED, null));
-        waitForMs(200);
-
-        logd("Sending EVENT_DATA_CONNECTION_ATTACHED");
-        mDct.sendMessage(mDct.obtainMessage(DctConstants.EVENT_DATA_CONNECTION_ATTACHED, null));
-        waitForMs(200);
+        sendInitializationEvents();
 
         ArgumentCaptor<DataProfile> dpCaptor = ArgumentCaptor.forClass(DataProfile.class);
         // Verify if RIL command was sent properly.
@@ -1363,8 +1281,10 @@
         doReturn(mNetworkRegistrationInfo).when(mServiceState).getNetworkRegistrationInfo(
                 anyInt(), anyInt());
         mDct.sendMessage(mDct.obtainMessage(DctConstants.EVENT_DATA_RAT_CHANGED, null));
-        waitForMs(200);
+        waitForLastHandlerAction(mDcTrackerTestHandler.getThreadHandler());
 
+        // Data connection is running on a different thread. Have to wait.
+        waitForMs(200);
         // Verify the disconnected data call due to rat change and retry manger schedule another
         // data call setup
         verify(mSimulatedCommandsVerifier, times(1)).deactivateDataCall(
@@ -1373,16 +1293,15 @@
         verify(mAlarmManager, times(1)).setExact(eq(AlarmManager.ELAPSED_REALTIME_WAKEUP),
                 anyLong(), any(PendingIntent.class));
 
-        // Simulate the timer expires.
-        Intent intent = new Intent("com.android.internal.telephony.data-reconnect.default");
-        intent.putExtra("reconnect_alarm_extra_type", PhoneConstants.APN_TYPE_DEFAULT);
-        intent.putExtra(PhoneConstants.SUBSCRIPTION_KEY, 0);
-        intent.putExtra("reconnect_alarm_extra_transport_type",
-                AccessNetworkConstants.TRANSPORT_TYPE_WWAN);
-        intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
-        mContext.sendBroadcast(intent);
-        waitForMs(200);
+        //Send event for reconnecting data
+        initApns(PhoneConstants.APN_TYPE_DEFAULT, new String[]{PhoneConstants.APN_TYPE_ALL});
+        mDct.sendMessage(mDct.obtainMessage(DctConstants.EVENT_DATA_RECONNECT,
+                        mPhone.getPhoneId(), AccessNetworkConstants.TRANSPORT_TYPE_WWAN,
+                        mApnContext));
+        waitForLastHandlerAction(mDcTrackerTestHandler.getThreadHandler());
 
+        // Data connection is running on a different thread. Have to wait.
+        waitForMs(200);
         // Verify if RIL command was sent properly.
         verify(mSimulatedCommandsVerifier).setupDataCall(
                 eq(AccessNetworkType.EUTRAN), dpCaptor.capture(),
@@ -1396,9 +1315,8 @@
     @Test
     @SmallTest
     public void testFetchDunApn() {
-        logd("Sending EVENT_RECORDS_LOADED");
-        mDct.sendMessage(mDct.obtainMessage(DctConstants.EVENT_RECORDS_LOADED, null));
-        waitForMs(200);
+
+        sendInitializationEvents();
 
         String dunApnString = "[ApnSettingV3]HOT mobile PC,pc.hotm,,,,,,,,,440,10,,DUN,,,true,"
                 + "0,,,,,,,,";
@@ -1421,9 +1339,9 @@
     @Test
     @SmallTest
     public void testFetchDunApnWithPreferredApnSet() {
-        logd("Sending EVENT_RECORDS_LOADED");
-        mDct.sendMessage(mDct.obtainMessage(DctConstants.EVENT_RECORDS_LOADED, null));
-        waitForMs(200);
+        logd("Sending EVENT_CARRIER_CONFIG_CHANGED");
+        mDct.sendEmptyMessage(DctConstants.EVENT_CARRIER_CONFIG_CHANGED);
+        waitForLastHandlerAction(mDcTrackerTestHandler.getThreadHandler());
 
         // apnSetId=1
         String dunApnString1 = "[ApnSettingV5]HOT mobile PC,pc.hotm,,,,,,,,,440,10,,DUN,,,true,"
@@ -1465,13 +1383,7 @@
         mDct.enableApn(ApnSetting.TYPE_DEFAULT, DcTracker.REQUEST_TYPE_NORMAL, null);
         initApns(PhoneConstants.APN_TYPE_DEFAULT, new String[]{PhoneConstants.APN_TYPE_ALL});
 
-        logd("Sending EVENT_RECORDS_LOADED");
-        mDct.sendMessage(mDct.obtainMessage(DctConstants.EVENT_RECORDS_LOADED, null));
-        waitForMs(200);
-
-        logd("Sending EVENT_DATA_CONNECTION_ATTACHED");
-        mDct.sendMessage(mDct.obtainMessage(DctConstants.EVENT_DATA_CONNECTION_ATTACHED, null));
-        waitForMs(200);
+        sendInitializationEvents();
 
         ArgumentCaptor<DataProfile> dpCaptor = ArgumentCaptor.forClass(DataProfile.class);
         // Verify if RIL command was sent properly.
@@ -1491,8 +1403,10 @@
         doReturn(mNetworkRegistrationInfo).when(mServiceState).getNetworkRegistrationInfo(
                 anyInt(), anyInt());
         mDct.sendMessage(mDct.obtainMessage(DctConstants.EVENT_DATA_RAT_CHANGED, null));
-        waitForMs(200);
+        waitForLastHandlerAction(mDcTrackerTestHandler.getThreadHandler());
 
+        // Data connection is running on a different thread. Have to wait.
+        waitForMs(200);
         // Verify data connection is on
         verify(mSimulatedCommandsVerifier, times(0)).deactivateDataCall(
                 eq(DataService.REQUEST_REASON_NORMAL), anyInt(),
@@ -1506,7 +1420,7 @@
         doReturn(mNetworkRegistrationInfo).when(mServiceState).getNetworkRegistrationInfo(
                 anyInt(), anyInt());
         mDct.sendMessage(mDct.obtainMessage(DctConstants.EVENT_DATA_RAT_CHANGED, null));
-        waitForMs(200);
+        waitForLastHandlerAction(mDcTrackerTestHandler.getThreadHandler());
 
         // Verify the same data connection
         assertEquals(FAKE_APN4, mDct.getActiveApnString(PhoneConstants.APN_TYPE_DEFAULT));
@@ -1596,13 +1510,7 @@
         mDct.enableApn(ApnSetting.TYPE_IMS, DcTracker.REQUEST_TYPE_NORMAL, null);
         mDct.enableApn(ApnSetting.TYPE_DEFAULT, DcTracker.REQUEST_TYPE_NORMAL, null);
 
-        logd("Sending EVENT_RECORDS_LOADED");
-        mDct.sendMessage(mDct.obtainMessage(DctConstants.EVENT_RECORDS_LOADED, null));
-        waitForMs(200);
-
-        logd("Sending EVENT_DATA_CONNECTION_ATTACHED");
-        mDct.sendMessage(mDct.obtainMessage(DctConstants.EVENT_DATA_CONNECTION_ATTACHED, null));
-        waitForMs(200);
+        sendInitializationEvents();
 
         ArgumentCaptor<DataProfile> dpCaptor = ArgumentCaptor.forClass(DataProfile.class);
         verify(mSimulatedCommandsVerifier, times(2)).setupDataCall(
@@ -1613,12 +1521,14 @@
 
         logd("Sending EVENT_NETWORK_STATUS_CHANGED");
         mDct.sendMessage(mDct.obtainMessage(DctConstants.EVENT_NETWORK_STATUS_CHANGED,
-                NetworkAgent.VALID_NETWORK, 0, null));
-        waitForMs(200);
+                NetworkAgent.VALID_NETWORK, 1, null));
+        waitForLastHandlerAction(mDcTrackerTestHandler.getThreadHandler());
 
         logd("Sending EVENT_NETWORK_STATUS_CHANGED");
         mDct.sendMessage(mDct.obtainMessage(DctConstants.EVENT_NETWORK_STATUS_CHANGED,
-                NetworkAgent.INVALID_NETWORK, 0, null));
+                NetworkAgent.INVALID_NETWORK, 1, null));
+        waitForLastHandlerAction(mDcTrackerTestHandler.getThreadHandler());
+
         waitForMs(200);
 
         // Verify that its no-op when the new data stall detection feature is disabled
@@ -1639,30 +1549,28 @@
         mDct.enableApn(ApnSetting.TYPE_IMS, DcTracker.REQUEST_TYPE_NORMAL, null);
         mDct.enableApn(ApnSetting.TYPE_DEFAULT, DcTracker.REQUEST_TYPE_NORMAL, null);
 
-        logd("Sending EVENT_RECORDS_LOADED");
-        mDct.sendMessage(mDct.obtainMessage(DctConstants.EVENT_RECORDS_LOADED, null));
-        waitForMs(200);
-
-        logd("Sending EVENT_DATA_CONNECTION_ATTACHED");
-        mDct.sendMessage(mDct.obtainMessage(DctConstants.EVENT_DATA_CONNECTION_ATTACHED, null));
+        sendInitializationEvents();
 
         ArgumentCaptor<DataProfile> dpCaptor = ArgumentCaptor.forClass(DataProfile.class);
         verify(mSimulatedCommandsVerifier, timeout(TEST_TIMEOUT).times(2)).setupDataCall(
                 eq(AccessNetworkType.EUTRAN), dpCaptor.capture(),
                 eq(false), eq(false), eq(DataService.REQUEST_REASON_NORMAL), any(),
                 any(Message.class));
+        waitForLastHandlerAction(mDcTrackerTestHandler.getThreadHandler());
         verifyDataProfile(dpCaptor.getValue(), FAKE_APN1, 0, 21, 1, NETWORK_TYPE_LTE_BITMASK);
 
         logd("Sending EVENT_NETWORK_STATUS_CHANGED");
         mDct.sendMessage(mDct.obtainMessage(DctConstants.EVENT_NETWORK_STATUS_CHANGED,
-                NetworkAgent.VALID_NETWORK, 0, null));
-        waitForMs(200);
+                NetworkAgent.VALID_NETWORK, 1, null));
+        waitForLastHandlerAction(mDcTrackerTestHandler.getThreadHandler());
 
         logd("Sending EVENT_NETWORK_STATUS_CHANGED");
         mDct.sendMessage(mDct.obtainMessage(DctConstants.EVENT_NETWORK_STATUS_CHANGED,
-                NetworkAgent.INVALID_NETWORK, 0, null));
-        waitForMs(200);
+                NetworkAgent.INVALID_NETWORK, 1, null));
+        waitForLastHandlerAction(mDcTrackerTestHandler.getThreadHandler());
 
+        // Data connection is running on a different thread. Have to wait.
+        waitForMs(200);
         verify(mSimulatedCommandsVerifier, times(1)).getDataCallList(any(Message.class));
     }
 
@@ -1682,12 +1590,7 @@
         mDct.enableApn(ApnSetting.TYPE_IMS, DcTracker.REQUEST_TYPE_NORMAL, null);
         mDct.enableApn(ApnSetting.TYPE_DEFAULT, DcTracker.REQUEST_TYPE_NORMAL, null);
 
-        logd("Sending EVENT_RECORDS_LOADED");
-        mDct.sendMessage(mDct.obtainMessage(DctConstants.EVENT_RECORDS_LOADED, null));
-        waitForMs(200);
-
-        logd("Sending EVENT_DATA_CONNECTION_ATTACHED");
-        mDct.sendMessage(mDct.obtainMessage(DctConstants.EVENT_DATA_CONNECTION_ATTACHED, null));
+        sendInitializationEvents();
 
         ArgumentCaptor<DataProfile> dpCaptor = ArgumentCaptor.forClass(DataProfile.class);
         verify(mSimulatedCommandsVerifier, timeout(TEST_TIMEOUT).times(2)).setupDataCall(
@@ -1698,7 +1601,9 @@
 
         logd("Sending EVENT_NETWORK_STATUS_CHANGED false");
         mDct.sendMessage(mDct.obtainMessage(DctConstants.EVENT_NETWORK_STATUS_CHANGED,
-                NetworkAgent.INVALID_NETWORK, 0, null));
+                NetworkAgent.INVALID_NETWORK, 1, null));
+        waitForLastHandlerAction(mDcTrackerTestHandler.getThreadHandler());
+
         waitForMs(200);
 
         // expected tear down all DataConnections
@@ -1722,13 +1627,7 @@
                 new String[]{PhoneConstants.APN_TYPE_DEFAULT, PhoneConstants.APN_TYPE_MMS});
         mDct.enableApn(ApnSetting.TYPE_DEFAULT, DcTracker.REQUEST_TYPE_NORMAL, null);
 
-        logd("Sending EVENT_RECORDS_LOADED");
-        mDct.sendMessage(mDct.obtainMessage(DctConstants.EVENT_RECORDS_LOADED, null));
-        waitForMs(200);
-
-        logd("Sending EVENT_DATA_CONNECTION_ATTACHED");
-        mDct.sendMessage(mDct.obtainMessage(DctConstants.EVENT_DATA_CONNECTION_ATTACHED, null));
-        waitForMs(200);
+        sendInitializationEvents();
 
         ArgumentCaptor<DataProfile> dpCaptor = ArgumentCaptor.forClass(DataProfile.class);
         verify(mSimulatedCommandsVerifier, times(1)).setupDataCall(
@@ -1739,8 +1638,8 @@
 
         logd("Sending EVENT_NETWORK_STATUS_CHANGED false");
         mDct.sendMessage(mDct.obtainMessage(DctConstants.EVENT_NETWORK_STATUS_CHANGED,
-                NetworkAgent.INVALID_NETWORK, 0, null));
-        waitForMs(200);
+                NetworkAgent.INVALID_NETWORK, 1, null));
+        waitForLastHandlerAction(mDcTrackerTestHandler.getThreadHandler());
 
         // expected to get preferred network type
         verify(mSST, times(1)).reRegisterNetwork(eq(null));
@@ -1760,13 +1659,7 @@
                 new String[]{PhoneConstants.APN_TYPE_DEFAULT, PhoneConstants.APN_TYPE_MMS});
         mDct.enableApn(ApnSetting.TYPE_DEFAULT, DcTracker.REQUEST_TYPE_NORMAL, null);
 
-        logd("Sending EVENT_RECORDS_LOADED");
-        mDct.sendMessage(mDct.obtainMessage(DctConstants.EVENT_RECORDS_LOADED, null));
-        waitForMs(200);
-
-        logd("Sending EVENT_DATA_CONNECTION_ATTACHED");
-        mDct.sendMessage(mDct.obtainMessage(DctConstants.EVENT_DATA_CONNECTION_ATTACHED, null));
-        waitForMs(200);
+        sendInitializationEvents();
 
         ArgumentCaptor<DataProfile> dpCaptor = ArgumentCaptor.forClass(DataProfile.class);
         verify(mSimulatedCommandsVerifier, times(1)).setupDataCall(
@@ -1777,8 +1670,8 @@
 
         logd("Sending EVENT_NETWORK_STATUS_CHANGED false");
         mDct.sendMessage(mDct.obtainMessage(DctConstants.EVENT_NETWORK_STATUS_CHANGED,
-                NetworkAgent.INVALID_NETWORK, 0, null));
-        waitForMs(200);
+                NetworkAgent.INVALID_NETWORK, 1, null));
+        waitForLastHandlerAction(mDcTrackerTestHandler.getThreadHandler());
 
         // expected to get preferred network type
         verify(mSST, times(1)).powerOffRadioSafely();
@@ -1799,7 +1692,6 @@
             plans.add(SubscriptionPlan.Builder
                     .createRecurring(ZonedDateTime.parse("2007-03-14T00:00:00.000Z"),
                             Period.ofMonths(1))
-                    .setTitle("Some NR 5G unmetered workaround plan")
                     .setDataLimit(SubscriptionPlan.BYTES_UNLIMITED,
                             SubscriptionPlan.LIMIT_BEHAVIOR_THROTTLED)
                     .setNetworkTypes(new int[] {TelephonyManager.NETWORK_TYPE_NR})
@@ -1808,7 +1700,6 @@
         plans.add(SubscriptionPlan.Builder
                 .createRecurring(ZonedDateTime.parse("2007-03-14T00:00:00.000Z"),
                         Period.ofMonths(1))
-                .setTitle("Some 5GB Plan")
                 .setDataLimit(1_000_000_000, SubscriptionPlan.LIMIT_BEHAVIOR_DISABLED)
                 .setDataUsage(500_000_000, System.currentTimeMillis())
                 .build());
@@ -1842,15 +1733,10 @@
         // Watchdog active for 10s
         mBundle.putLong(CarrierConfigManager.KEY_5G_WATCHDOG_TIME_MS_LONG, 10000);
         Intent intent = new Intent(CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED);
+        intent.putExtra(CarrierConfigManager.EXTRA_SLOT_INDEX, mPhone.getPhoneId());
         intent.putExtra(SubscriptionManager.EXTRA_SUBSCRIPTION_INDEX, mPhone.getSubId());
         mContext.sendBroadcast(intent);
-        waitForHandlerAction(mDcTrackerTestHandler.getThreadHandler(), 200);
-    }
-
-    private boolean getHysteresisStatus() throws Exception {
-        Field field = DcTracker.class.getDeclaredField(("mHysteresis"));
-        field.setAccessible(true);
-        return (boolean) field.get(mDct);
+        waitForLastHandlerAction(mDcTrackerTestHandler.getThreadHandler());
     }
 
     private boolean getWatchdogStatus() throws Exception {
@@ -1899,42 +1785,54 @@
         int id = setUpDataConnection();
         setUpSubscriptionPlans(false);
         setUpWatchdogTimer();
-        doReturn(NetworkRegistrationInfo.NR_STATE_CONNECTED).when(mServiceState).getNrState();
+        doReturn(new TelephonyDisplayInfo(TelephonyManager.NETWORK_TYPE_LTE,
+                TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_NSA))
+                .when(mDisplayInfoController).getTelephonyDisplayInfo();
+        doReturn(1).when(mPhone).getSubId();
 
         // NetCapability should be metered when connected to 5G with no unmetered plan or frequency
-        mDct.sendMessage(mDct.obtainMessage(DctConstants.EVENT_SERVICE_STATE_CHANGED));
-        waitForHandlerAction(mDcTrackerTestHandler.getThreadHandler(), 200);
+        mDct.sendMessage(mDct.obtainMessage(DctConstants.EVENT_TELEPHONY_DISPLAY_INFO_CHANGED));
+        waitForLastHandlerAction(mDcTrackerTestHandler.getThreadHandler());
         verify(mDataConnection, times(1)).onMeterednessChanged(false);
 
         // Set MMWAVE frequency to unmetered
         mBundle.putBoolean(CarrierConfigManager.KEY_UNMETERED_NR_NSA_MMWAVE_BOOL, true);
         Intent intent = new Intent(CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED);
+        intent.putExtra(CarrierConfigManager.EXTRA_SLOT_INDEX, mPhone.getPhoneId());
         intent.putExtra(SubscriptionManager.EXTRA_SUBSCRIPTION_INDEX, mPhone.getSubId());
         mContext.sendBroadcast(intent);
-        waitForHandlerAction(mDcTrackerTestHandler.getThreadHandler(), 200);
+        waitForLastHandlerAction(mDcTrackerTestHandler.getThreadHandler());
 
         // NetCapability should switch to unmetered when fr=MMWAVE and MMWAVE unmetered
-        doReturn(ServiceState.FREQUENCY_RANGE_MMWAVE).when(mServiceState).getNrFrequencyRange();
-        mDct.sendMessage(mDct.obtainMessage(DctConstants.EVENT_SERVICE_STATE_CHANGED));
-        waitForHandlerAction(mDcTrackerTestHandler.getThreadHandler(), 200);
+        doReturn(new TelephonyDisplayInfo(TelephonyManager.NETWORK_TYPE_LTE,
+                TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_NSA_MMWAVE))
+                .when(mDisplayInfoController).getTelephonyDisplayInfo();
+        mDct.sendMessage(mDct.obtainMessage(DctConstants.EVENT_TELEPHONY_DISPLAY_INFO_CHANGED));
+        waitForLastHandlerAction(mDcTrackerTestHandler.getThreadHandler());
         verify(mDataConnection, times(1)).onMeterednessChanged(true);
 
         // NetCapability should switch to metered when fr=SUB6 and MMWAVE unmetered
-        doReturn(ServiceState.FREQUENCY_RANGE_HIGH).when(mServiceState).getNrFrequencyRange();
-        mDct.sendMessage(mDct.obtainMessage(DctConstants.EVENT_SERVICE_STATE_CHANGED));
-        waitForHandlerAction(mDcTrackerTestHandler.getThreadHandler(), 200);
+        doReturn(new TelephonyDisplayInfo(TelephonyManager.NETWORK_TYPE_LTE,
+                TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_NSA))
+                .when(mDisplayInfoController).getTelephonyDisplayInfo();
+        mDct.sendMessage(mDct.obtainMessage(DctConstants.EVENT_TELEPHONY_DISPLAY_INFO_CHANGED));
+        waitForLastHandlerAction(mDcTrackerTestHandler.getThreadHandler());
         verify(mDataConnection, times(2)).onMeterednessChanged(false);
 
         // Set SUB6 frequency to unmetered
+        doReturn(2).when(mPhone).getSubId();
         mBundle.putBoolean(CarrierConfigManager.KEY_UNMETERED_NR_NSA_SUB6_BOOL, true);
         intent = new Intent(CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED);
+        intent.putExtra(CarrierConfigManager.EXTRA_SLOT_INDEX, mPhone.getPhoneId());
         intent.putExtra(SubscriptionManager.EXTRA_SUBSCRIPTION_INDEX, mPhone.getSubId());
         mContext.sendBroadcast(intent);
-        waitForHandlerAction(mDcTrackerTestHandler.getThreadHandler(), 200);
+        waitForLastHandlerAction(mDcTrackerTestHandler.getThreadHandler());
 
         // NetCapability should switch to unmetered when fr=SUB6 and SUB6 unmetered
-        mDct.sendMessage(mDct.obtainMessage(DctConstants.EVENT_SERVICE_STATE_CHANGED));
-        waitForHandlerAction(mDcTrackerTestHandler.getThreadHandler(), 200);
+        mDct.sendMessage(mDct.obtainMessage(DctConstants.EVENT_TELEPHONY_DISPLAY_INFO_CHANGED));
+        waitForLastHandlerAction(mDcTrackerTestHandler.getThreadHandler());
+        // Data connection is running on a different thread. Have to wait.
+        waitForMs(200);
         verify(mDataConnection, times(2)).onMeterednessChanged(true);
 
         resetDataConnection(id);
@@ -1948,120 +1846,189 @@
         setUpWatchdogTimer();
 
         // NetCapability should be unmetered when connected to 5G
-        doReturn(NetworkRegistrationInfo.NR_STATE_CONNECTED).when(mServiceState).getNrState();
-        mDct.sendMessage(mDct.obtainMessage(DctConstants.EVENT_SERVICE_STATE_CHANGED));
-        waitForHandlerAction(mDcTrackerTestHandler.getThreadHandler(), 200);
+        doReturn(new TelephonyDisplayInfo(TelephonyManager.NETWORK_TYPE_LTE,
+                TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_NSA))
+                .when(mDisplayInfoController).getTelephonyDisplayInfo();
+        mDct.sendMessage(mDct.obtainMessage(DctConstants.EVENT_TELEPHONY_DISPLAY_INFO_CHANGED));
+        waitForLastHandlerAction(mDcTrackerTestHandler.getThreadHandler());
         verify(mDataConnection, times(1)).onMeterednessChanged(true);
 
         // NetCapability should be metered when disconnected from 5G
-        doReturn(NetworkRegistrationInfo.NR_STATE_NONE).when(mServiceState).getNrState();
-        mDct.sendMessage(mDct.obtainMessage(DctConstants.EVENT_SERVICE_STATE_CHANGED));
-        waitForHandlerAction(mDcTrackerTestHandler.getThreadHandler(), 200);
+        doReturn(new TelephonyDisplayInfo(TelephonyManager.NETWORK_TYPE_LTE,
+                TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NONE))
+                .when(mDisplayInfoController).getTelephonyDisplayInfo();
+        mDct.sendMessage(mDct.obtainMessage(DctConstants.EVENT_TELEPHONY_DISPLAY_INFO_CHANGED));
+        waitForLastHandlerAction(mDcTrackerTestHandler.getThreadHandler());
+        // Data connection is running on a different thread. Have to wait.
+        waitForMs(200);
         verify(mDataConnection, times(1)).onMeterednessChanged(false);
 
         resetDataConnection(id);
     }
 
     @Test
-    public void testReevaluateUnmeteredConnectionsOnHysteresis() throws Exception {
-        initApns(PhoneConstants.APN_TYPE_DEFAULT, new String[]{PhoneConstants.APN_TYPE_ALL});
-        int id = setUpDataConnection();
-        setUpSubscriptionPlans(true);
-        setUpWatchdogTimer();
-
-        // Hysteresis active for 10s
-        mBundle.putInt(CarrierConfigManager.KEY_5G_ICON_DISPLAY_GRACE_PERIOD_SEC_INT, 10000);
-        Intent intent = new Intent(CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED);
-        intent.putExtra(SubscriptionManager.EXTRA_SUBSCRIPTION_INDEX, mPhone.getSubId());
-        mContext.sendBroadcast(intent);
-        waitForHandlerAction(mDcTrackerTestHandler.getThreadHandler(), 200);
-
-        // Hysteresis inactive when unmetered and never connected to 5G
-        doReturn(NetworkRegistrationInfo.NR_STATE_NONE).when(mServiceState).getNrState();
-        mDct.sendMessage(mDct.obtainMessage(DctConstants.EVENT_5G_TIMER_HYSTERESIS));
-        waitForHandlerAction(mDcTrackerTestHandler.getThreadHandler(), 200);
-        assertFalse(getHysteresisStatus());
-
-        // Hysteresis inactive when unmetered and connected to 5G
-        doReturn(NetworkRegistrationInfo.NR_STATE_CONNECTED).when(mServiceState).getNrState();
-        mDct.sendMessage(mDct.obtainMessage(DctConstants.EVENT_SERVICE_STATE_CHANGED));
-        waitForHandlerAction(mDcTrackerTestHandler.getThreadHandler(), 200);
-        assertFalse(getHysteresisStatus());
-
-        // Hysteresis active when unmetered and disconnected after connected to 5G
-        doReturn(NetworkRegistrationInfo.NR_STATE_NONE).when(mServiceState).getNrState();
-        mDct.sendMessage(mDct.obtainMessage(DctConstants.EVENT_SERVICE_STATE_CHANGED));
-        waitForHandlerAction(mDcTrackerTestHandler.getThreadHandler(), 200);
-        assertTrue(getHysteresisStatus());
-
-        // NetCapability metered when hysteresis timer goes off
-        mDct.sendMessage(mDct.obtainMessage(DctConstants.EVENT_5G_TIMER_HYSTERESIS));
-        waitForHandlerAction(mDcTrackerTestHandler.getThreadHandler(), 200);
-        assertFalse(getHysteresisStatus());
-        verify(mDataConnection, times(1)).onMeterednessChanged(true);
-
-        // Hysteresis inactive when reconnected after timer goes off
-        doReturn(NetworkRegistrationInfo.NR_STATE_CONNECTED).when(mServiceState).getNrState();
-        mDct.sendMessage(mDct.obtainMessage(DctConstants.EVENT_SERVICE_STATE_CHANGED));
-        waitForHandlerAction(mDcTrackerTestHandler.getThreadHandler(), 200);
-        assertFalse(getHysteresisStatus());
-
-        // Hysteresis disabled
-        mBundle.putInt(CarrierConfigManager.KEY_5G_ICON_DISPLAY_GRACE_PERIOD_SEC_INT, 0);
-        intent = new Intent(CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED);
-        intent.putExtra(SubscriptionManager.EXTRA_SUBSCRIPTION_INDEX, mPhone.getSubId());
-        mContext.sendBroadcast(intent);
-        waitForHandlerAction(mDcTrackerTestHandler.getThreadHandler(), 200);
-
-        // Hysteresis inactive when CarrierConfig is set to 0
-        doReturn(NetworkRegistrationInfo.NR_STATE_NONE).when(mServiceState).getNrState();
-        mDct.sendMessage(mDct.obtainMessage(DctConstants.EVENT_SERVICE_STATE_CHANGED));
-        waitForHandlerAction(mDcTrackerTestHandler.getThreadHandler(), 200);
-        assertFalse(getHysteresisStatus());
-
-        resetDataConnection(id);
-    }
-
-    @Test
     public void testReevaluateUnmeteredConnectionsOnWatchdog() throws Exception {
         initApns(PhoneConstants.APN_TYPE_DEFAULT, new String[]{PhoneConstants.APN_TYPE_ALL});
         int id = setUpDataConnection();
         setUpSubscriptionPlans(true);
         setUpWatchdogTimer();
 
-        // Watchdog inactive when unmetered and never connected to 5G
-        doReturn(NetworkRegistrationInfo.NR_STATE_NONE).when(mServiceState).getNrState();
-        mDct.sendMessage(mDct.obtainMessage(DctConstants.EVENT_5G_TIMER_WATCHDOG));
-        waitForHandlerAction(mDcTrackerTestHandler.getThreadHandler(), 200);
+        // Watchdog inactive when unmetered and not connected to 5G
+        doReturn(new TelephonyDisplayInfo(TelephonyManager.NETWORK_TYPE_LTE,
+                TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NONE))
+                .when(mDisplayInfoController).getTelephonyDisplayInfo();
+        mDct.sendMessage(mDct.obtainMessage(DctConstants.EVENT_NR_TIMER_WATCHDOG));
+        waitForLastHandlerAction(mDcTrackerTestHandler.getThreadHandler());
         assertFalse(getWatchdogStatus());
 
-        // Hysteresis active for 10s
-        mBundle.putInt(CarrierConfigManager.KEY_5G_ICON_DISPLAY_GRACE_PERIOD_SEC_INT, 10000);
-        Intent intent = new Intent(CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED);
-        intent.putExtra(SubscriptionManager.EXTRA_SUBSCRIPTION_INDEX, mPhone.getSubId());
-        mContext.sendBroadcast(intent);
-        waitForHandlerAction(mDcTrackerTestHandler.getThreadHandler(), 200);
-
         // Watchdog active when unmetered and connected to 5G
-        doReturn(NetworkRegistrationInfo.NR_STATE_CONNECTED).when(mServiceState).getNrState();
-        mDct.sendMessage(mDct.obtainMessage(DctConstants.EVENT_SERVICE_STATE_CHANGED));
-        waitForHandlerAction(mDcTrackerTestHandler.getThreadHandler(), 200);
-        assertTrue(getWatchdogStatus());
-        assertFalse(getHysteresisStatus());
-
-        // Watchdog active during hysteresis
-        doReturn(NetworkRegistrationInfo.NR_STATE_NONE).when(mServiceState).getNrState();
-        mDct.sendMessage(mDct.obtainMessage(DctConstants.EVENT_SERVICE_STATE_CHANGED));
-        waitForHandlerAction(mDcTrackerTestHandler.getThreadHandler(), 200);
-        assertTrue(getHysteresisStatus());
+        doReturn(new TelephonyDisplayInfo(TelephonyManager.NETWORK_TYPE_LTE,
+                TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_NSA))
+                .when(mDisplayInfoController).getTelephonyDisplayInfo();
+        mDct.sendMessage(mDct.obtainMessage(DctConstants.EVENT_TELEPHONY_DISPLAY_INFO_CHANGED));
+        waitForLastHandlerAction(mDcTrackerTestHandler.getThreadHandler());
         assertTrue(getWatchdogStatus());
 
         // Watchdog inactive when metered
         setUpSubscriptionPlans(false);
-        mDct.sendMessage(mDct.obtainMessage(DctConstants.EVENT_SERVICE_STATE_CHANGED));
-        waitForHandlerAction(mDcTrackerTestHandler.getThreadHandler(), 200);
+        mDct.sendMessage(mDct.obtainMessage(DctConstants.EVENT_TELEPHONY_DISPLAY_INFO_CHANGED));
+        waitForLastHandlerAction(mDcTrackerTestHandler.getThreadHandler());
         assertFalse(getWatchdogStatus());
 
         resetDataConnection(id);
     }
+
+    /**
+     * Test if this is a path prefix match against the given Uri. Verifies that
+     * scheme, authority, and atomic path segments match.
+     *
+     * Copied from frameworks/base/core/java/android/net/Uri.java
+     */
+    private boolean isPathPrefixMatch(Uri uriA, Uri uriB) {
+        if (!Objects.equals(uriA.getScheme(), uriB.getScheme())) return false;
+        if (!Objects.equals(uriA.getAuthority(), uriB.getAuthority())) return false;
+
+        List<String> segA = uriA.getPathSegments();
+        List<String> segB = uriB.getPathSegments();
+
+        final int size = segB.size();
+        if (segA.size() < size) return false;
+
+        for (int i = 0; i < size; i++) {
+            if (!Objects.equals(segA.get(i), segB.get(i))) {
+                return false;
+            }
+        }
+
+        return true;
+    }
+
+    @Test
+    public void testNoApnContextsWhenDataIsDisabled() throws java.lang.InterruptedException {
+        //Check that apn contexts are loaded.
+        assertTrue(mDct.getApnContexts().size() > 0);
+
+        //Do work normally done in teardown.
+        mDct.removeCallbacksAndMessages(null);
+        mDcTrackerTestHandler.quit();
+        mDcTrackerTestHandler.join();
+
+        //Set isDataCapable to false for the new DcTracker being created in DcTrackerTestHandler.
+        doReturn(false).when(mTelephonyManager).isDataCapable();
+        mDcTrackerTestHandler = new DcTrackerTestHandler(getClass().getSimpleName());
+        setReady(false);
+
+        mDcTrackerTestHandler.start();
+        waitUntilReady();
+        assertEquals(0, mDct.getApnContexts().size());
+
+        //No need to clean up handler because that work is done in teardown.
+    }
+
+    @Test
+    public void testRatChanged() throws Exception {
+        mSimulatedCommands.setDataCallResult(true, createSetupDataCallResult());
+
+        DataConnectionReasons dataConnectionReasons = new DataConnectionReasons();
+        boolean allowed = isDataAllowed(dataConnectionReasons);
+        assertFalse(dataConnectionReasons.toString(), allowed);
+
+        logd("Sending EVENT_ENABLE_APN");
+        // APN id 0 is APN_TYPE_DEFAULT
+        mDct.enableApn(ApnSetting.TYPE_DEFAULT, DcTracker.REQUEST_TYPE_NORMAL, null);
+
+        sendInitializationEvents();
+
+        dataConnectionReasons = new DataConnectionReasons();
+        allowed = isDataAllowed(dataConnectionReasons);
+        assertTrue(dataConnectionReasons.toString(), allowed);
+
+        ArgumentCaptor<DataProfile> dpCaptor = ArgumentCaptor.forClass(DataProfile.class);
+        // Verify if RIL command was sent properly.
+        verify(mSimulatedCommandsVerifier, times(1)).setupDataCall(
+                eq(AccessNetworkType.EUTRAN), dpCaptor.capture(),
+                eq(false), eq(false), eq(DataService.REQUEST_REASON_NORMAL), any(),
+                any(Message.class));
+        verifyDataProfile(dpCaptor.getValue(), FAKE_APN1, 0, 21, 1, NETWORK_TYPE_LTE_BITMASK);
+
+        verifyDataConnected(FAKE_APN1);
+
+        doReturn(ServiceState.RIL_RADIO_TECHNOLOGY_UMTS).when(mServiceState)
+                .getRilDataRadioTechnology();
+
+        logd("Sending EVENT_DATA_RAT_CHANGED");
+        mNetworkRegistrationInfo = new NetworkRegistrationInfo.Builder()
+                .setAccessNetworkTechnology(TelephonyManager.NETWORK_TYPE_UMTS)
+                .setRegistrationState(NetworkRegistrationInfo.REGISTRATION_STATE_HOME)
+                .build();
+        doReturn(mNetworkRegistrationInfo).when(mServiceState).getNetworkRegistrationInfo(
+                anyInt(), anyInt());
+        mDct.sendMessage(mDct.obtainMessage(DctConstants.EVENT_DATA_RAT_CHANGED, null));
+        waitForLastHandlerAction(mDcTrackerTestHandler.getThreadHandler());
+
+        // Data connection is running on a different thread. Have to wait.
+        waitForMs(200);
+        // expected tear down all metered DataConnections
+        verify(mSimulatedCommandsVerifier).deactivateDataCall(
+                eq(DataService.REQUEST_REASON_NORMAL), anyInt(),
+                any(Message.class));
+    }
+
+    @Test
+    public void testApnConfigRepositoryUpdatedOnCarrierConfigChange() throws Exception {
+        assertPriority(ApnSetting.TYPE_CBS_STRING, 2);
+        assertPriority(ApnSetting.TYPE_MMS_STRING, 2);
+
+        mBundle.putStringArray(CarrierConfigManager.KEY_APN_PRIORITY_STRING_ARRAY,
+                new String[] {
+                        ApnSetting.TYPE_CBS_STRING + ":11",
+                        ApnSetting.TYPE_MMS_STRING + ":19",
+                });
+
+        sendInitializationEvents();
+
+        Intent intent = new Intent(CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED);
+        intent.putExtra(CarrierConfigManager.EXTRA_SLOT_INDEX, mPhone.getPhoneId());
+        intent.putExtra(SubscriptionManager.EXTRA_SUBSCRIPTION_INDEX, mPhone.getSubId());
+        mContext.sendBroadcast(intent);
+        waitForLastHandlerAction(mDcTrackerTestHandler.getThreadHandler());
+
+        assertPriority(ApnSetting.TYPE_CBS_STRING, 11);
+        assertPriority(ApnSetting.TYPE_MMS_STRING, 19);
+
+        //Ensure apns are in sorted order.
+        ApnContext lastApnContext = null;
+        for (ApnContext apnContext : mDct.getApnContexts()) {
+            if (lastApnContext != null) {
+                assertTrue(apnContext.getPriority() <= lastApnContext.getPriority());
+            }
+            lastApnContext = apnContext;
+        }
+    }
+
+    private void assertPriority(String type, int priority) {
+        assertEquals(priority, mDct.getApnContexts().stream()
+                .filter(x -> x.getApnType().equals(type))
+                .findFirst().get().getPriority());
+    }
 }
diff --git a/tests/telephonytests/src/com/android/internal/telephony/dataconnection/RetryManagerTest.java b/tests/telephonytests/src/com/android/internal/telephony/dataconnection/RetryManagerTest.java
index 2f452f6..334917f 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/dataconnection/RetryManagerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/dataconnection/RetryManagerTest.java
@@ -1026,4 +1026,31 @@
         delay = rm.getDelayForNextApn(false);
         assertEquals(4000, delay);
     }
+
+    /**
+     * Test the scenario where reset happens before modem suggests retry.
+     */
+    @Test
+    @SmallTest
+    public void testRetryManagerResetBeforeModemSuggestedRetry() throws Exception {
+
+        mBundle.putStringArray(CarrierConfigManager.KEY_CARRIER_DATA_CALL_RETRY_CONFIG_STRINGS,
+                new String[]{"others:1000,4000,7000,9000"});
+
+        ArrayList<ApnSetting> waitingApns = new ArrayList<ApnSetting>();
+        ApnSetting myApn1 = ApnSetting.makeApnSetting(mApn1);
+        ApnSetting myApn2 = ApnSetting.makeApnSetting(mApn2);
+        waitingApns.add(myApn1);
+        waitingApns.add(myApn2);
+
+        RetryManager rm = new RetryManager(mPhone, "mms");
+        rm.setWaitingApns(waitingApns);
+
+        rm.setModemSuggestedDelay(10);
+
+        ApnSetting nextApn = rm.getNextApnSetting();
+        assertTrue(nextApn.equals(mApn1));
+        long delay = rm.getDelayForNextApn(false);
+        assertEquals(20000, delay);
+    }
 }
diff --git a/tests/telephonytests/src/com/android/internal/telephony/dataconnection/TelephonyNetworkFactoryTest.java b/tests/telephonytests/src/com/android/internal/telephony/dataconnection/TelephonyNetworkFactoryTest.java
index 1b67149..0aa2a4d 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/dataconnection/TelephonyNetworkFactoryTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/dataconnection/TelephonyNetworkFactoryTest.java
@@ -16,75 +16,71 @@
 
 package com.android.internal.telephony.dataconnection;
 
-import static com.android.internal.telephony.TelephonyTestUtils.waitForMs;
+import static com.android.internal.telephony.NetworkFactory.CMD_CANCEL_REQUEST;
+import static com.android.internal.telephony.NetworkFactory.CMD_REQUEST_NETWORK;
+import static com.android.internal.telephony.dataconnection.TelephonyNetworkFactory.EVENT_ACTIVE_PHONE_SWITCH;
 
 import static org.junit.Assert.assertEquals;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
 
-import android.content.Context;
-import android.net.ConnectivityManager;
-import android.net.IConnectivityManager;
 import android.net.NetworkCapabilities;
 import android.net.NetworkRequest;
-import android.net.StringNetworkSpecifier;
+import android.net.TelephonyNetworkSpecifier;
 import android.os.AsyncResult;
-import android.os.Binder;
 import android.os.Handler;
-import android.os.HandlerThread;
 import android.os.Looper;
-import android.os.Messenger;
 import android.telephony.AccessNetworkConstants;
-import android.telephony.Rlog;
 import android.telephony.data.ApnSetting;
 import android.test.suitebuilder.annotation.SmallTest;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
 
 import androidx.test.filters.FlakyTest;
 
 import com.android.internal.telephony.PhoneSwitcher;
 import com.android.internal.telephony.RadioConfig;
-import com.android.internal.telephony.SubscriptionController;
 import com.android.internal.telephony.TelephonyTest;
 import com.android.internal.telephony.dataconnection.TransportManager.HandoverParams;
 import com.android.internal.telephony.dataconnection.TransportManager.HandoverParams.HandoverCallback;
-import com.android.internal.telephony.mocks.ConnectivityServiceMock;
-import com.android.internal.telephony.mocks.PhoneSwitcherMock;
-import com.android.internal.telephony.mocks.SubscriptionControllerMock;
-import com.android.internal.telephony.mocks.SubscriptionMonitorMock;
-import com.android.internal.telephony.mocks.TelephonyRegistryMock;
+import com.android.telephony.Rlog;
 
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
+import org.junit.runner.RunWith;
 import org.mockito.Mock;
+import org.mockito.Mockito;
 
 import java.lang.reflect.Field;
 import java.util.ArrayList;
 
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper
 public class TelephonyNetworkFactoryTest extends TelephonyTest {
     private final static String LOG_TAG = "TelephonyNetworkFactoryTest";
 
     @Mock
-    private RadioConfig mMockRadioConfig;
+    PhoneSwitcher mPhoneSwitcher;
     @Mock
-    private IConnectivityManager mIConnectivityManager;
+    private RadioConfig mMockRadioConfig;
+
+    @Mock
+    private DataConnection mDataConnection;
 
     private String mTestName = "";
 
-    private TelephonyRegistryMock mTelephonyRegistryMock;
-    private PhoneSwitcherMock mPhoneSwitcherMock;
-    private SubscriptionControllerMock mSubscriptionControllerMock;
-    private SubscriptionMonitorMock mSubscriptionMonitorMock;
-    private HandlerThread mHandlerThread;
-    private ConnectivityServiceMock mConnectivityServiceMock;
-    private Looper mLooper;
     private final ArrayList<NetworkRequest> mNetworkRequestList = new ArrayList<>();
 
     private TelephonyNetworkFactory mTelephonyNetworkFactoryUT;
+    private int mRequestId = 0;
 
     private void log(String str) {
         Rlog.d(LOG_TAG + " " + mTestName, str);
@@ -95,16 +91,54 @@
                 addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET).
                 addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED).
                 addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR);
-        netCap.setNetworkSpecifier(new StringNetworkSpecifier(Integer.toString(subId)));
-        return mConnectivityServiceMock.requestNetwork(netCap, null, 0, new Binder(), -1);
+        netCap.setNetworkSpecifier(new TelephonyNetworkSpecifier.Builder()
+                .setSubscriptionId(subId).build());
+        NetworkRequest networkRequest = new NetworkRequest(netCap, -1,
+                mRequestId++, NetworkRequest.Type.REQUEST);
+        mTelephonyNetworkFactoryUT.obtainMessage(CMD_REQUEST_NETWORK, 0, 0, networkRequest)
+                .sendToTarget();
+        return networkRequest;
     }
+
+    private NetworkRequest makeDefaultInternetRequest() {
+        NetworkCapabilities netCap = (new NetworkCapabilities())
+                .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
+                .addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED)
+                .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR);
+        NetworkRequest networkRequest = new NetworkRequest(netCap, -1,
+                mRequestId++, NetworkRequest.Type.REQUEST);
+        mTelephonyNetworkFactoryUT.obtainMessage(CMD_REQUEST_NETWORK, 0, 0, networkRequest)
+                .sendToTarget();
+        return networkRequest;
+    }
+
     private NetworkRequest makeSubSpecificMmsRequest(int subId) {
         NetworkCapabilities netCap = (new NetworkCapabilities()).
                 addCapability(NetworkCapabilities.NET_CAPABILITY_MMS).
                 addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED).
                 addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR);
-        netCap.setNetworkSpecifier(new StringNetworkSpecifier(Integer.toString(subId)));
-        return mConnectivityServiceMock.requestNetwork(netCap, null, 0, new Binder(), -1);
+        netCap.setNetworkSpecifier(new TelephonyNetworkSpecifier.Builder()
+                .setSubscriptionId(subId).build());
+        NetworkRequest networkRequest = new NetworkRequest(netCap, -1,
+                mRequestId++, NetworkRequest.Type.REQUEST);
+        mTelephonyNetworkFactoryUT.obtainMessage(CMD_REQUEST_NETWORK, 0, 0, networkRequest)
+                .sendToTarget();
+        return networkRequest;
+    }
+
+    private void releaseNetworkRequest(NetworkRequest networkRequest) {
+        mTelephonyNetworkFactoryUT.obtainMessage(CMD_CANCEL_REQUEST, 0, 0, networkRequest)
+                .sendToTarget();
+    }
+
+    private void activatePhoneInPhoneSwitcher(int phoneId, boolean active) {
+        doReturn(active).when(mPhoneSwitcher).shouldApplyNetworkRequest(any(), eq(phoneId));
+        mTelephonyNetworkFactoryUT.mInternalHandler.sendEmptyMessage(EVENT_ACTIVE_PHONE_SWITCH);
+    }
+
+    private void activatePhoneInPhoneSwitcher(int phoneId, NetworkRequest nr, boolean active) {
+        doReturn(active).when(mPhoneSwitcher).shouldApplyNetworkRequest(eq(nr), eq(phoneId));
+        mTelephonyNetworkFactoryUT.mInternalHandler.sendEmptyMessage(EVENT_ACTIVE_PHONE_SWITCH);
     }
 
     @Before
@@ -112,10 +146,6 @@
         super.setUp(getClass().getSimpleName());
         replaceInstance(RadioConfig.class, "sRadioConfig", null, mMockRadioConfig);
 
-        mHandlerThread = new HandlerThread("TelephonyNetworkFactoryTest");
-        mHandlerThread.start();
-        mLooper = mHandlerThread.getLooper();
-
         mContextFixture.putStringArrayResource(com.android.internal.R.array.networkAttributes,
                 new String[]{"wifi,1,1,1,-1,true", "mobile,0,0,0,-1,true",
                         "mobile_mms,2,0,2,60000,true", "mobile_supl,3,0,2,60000,true",
@@ -133,47 +163,19 @@
             mNetworkRequestList.remove((NetworkRequest) invocation.getArguments()[0]);
             return null;
         }).when(mDcTracker).releaseNetwork(any(), anyInt());
-
-        doAnswer(invocation -> {
-            mConnectivityServiceMock.registerNetworkFactory(
-                    (Messenger) invocation.getArguments()[0],
-                    (String) invocation.getArguments()[1]);
-            return null;
-        }).when(mIConnectivityManager).registerNetworkFactory(any(), anyString());
-
-        doAnswer(invocation -> {
-            mConnectivityServiceMock.unregisterNetworkFactory(
-                    (Messenger) invocation.getArguments()[0]);
-            return null;
-        }).when(mIConnectivityManager).unregisterNetworkFactory(any());
     }
 
     @After
     public void tearDown() throws Exception {
-        mConnectivityServiceMock.die();
-        mLooper.quit();
-        mHandlerThread.quit();
         super.tearDown();
     }
 
-    private void createMockedTelephonyComponents(int numberOfPhones) throws Exception {
-        mConnectivityServiceMock = new ConnectivityServiceMock(mContext);
-        mContextFixture.setSystemService(Context.CONNECTIVITY_SERVICE,
-                new ConnectivityManager(mContext, mIConnectivityManager));
-        mTelephonyRegistryMock = new TelephonyRegistryMock();
-        mSubscriptionControllerMock = new SubscriptionControllerMock(mContext,
-                mTelephonyRegistryMock, numberOfPhones);
-        mSubscriptionMonitorMock = new SubscriptionMonitorMock(numberOfPhones);
-        mPhoneSwitcherMock = new PhoneSwitcherMock(
-                numberOfPhones, mLooper, mSubscriptionControllerMock);
-        mSubscriptionMonitorMock = new SubscriptionMonitorMock(numberOfPhones);
+    private void createMockedTelephonyComponents() throws Exception {
+        replaceInstance(PhoneSwitcher.class, "sPhoneSwitcher", null, mPhoneSwitcher);
 
-        replaceInstance(SubscriptionController.class, "sInstance", null,
-                mSubscriptionControllerMock);
-        replaceInstance(PhoneSwitcher.class, "sPhoneSwitcher", null, mPhoneSwitcherMock);
-
-        mTelephonyNetworkFactoryUT = new TelephonyNetworkFactory(mSubscriptionMonitorMock, mLooper,
-                mPhone);
+        mTelephonyNetworkFactoryUT = new TelephonyNetworkFactory(Looper.myLooper(), mPhone);
+        verify(mConnectivityManager).registerNetworkProvider(any());
+        verify(mPhoneSwitcher).registerForActivePhoneSwitch(any(), anyInt(), any());
     }
 
     /**
@@ -184,65 +186,66 @@
     @SmallTest
     public void testActive() throws Exception {
         mTestName = "testActive";
-        final int numberOfPhones = 1;
         final int phoneId = 0;
         final int subId = 0;
 
-        createMockedTelephonyComponents(numberOfPhones);
+        createMockedTelephonyComponents();
 
-        mPhoneSwitcherMock.setPreferredDataPhoneId(phoneId);
-        mSubscriptionControllerMock.setDefaultDataSubId(subId);
-        mSubscriptionControllerMock.setSlotSubId(phoneId, subId);
-        mSubscriptionMonitorMock.notifySubscriptionChanged(phoneId);
+        doReturn(false).when(mPhoneSwitcher).shouldApplyNetworkRequest(any(), anyInt());
+        doReturn(subId).when(mSubscriptionController).getSubIdUsingPhoneId(phoneId);
+        // fake onSubscriptionChangedListener being triggered.
+        mTelephonyNetworkFactoryUT.mInternalHandler.sendEmptyMessage(
+                TelephonyNetworkFactory.EVENT_SUBSCRIPTION_CHANGED);
 
         log("addDefaultRequest");
-        mConnectivityServiceMock.addDefaultRequest();
-        waitForMs(250);
+        makeDefaultInternetRequest();
+        processAllMessages();
         assertEquals(0, mNetworkRequestList.size());
 
         log("setPhoneActive true: phoneId = " + phoneId);
-        mPhoneSwitcherMock.setPhoneActive(phoneId, true);
-        waitForMs(250);
+
+        activatePhoneInPhoneSwitcher(phoneId, true);
+        processAllMessages();
         assertEquals(1, mNetworkRequestList.size());
 
         log("makeSubSpecificInternetRequest: subId = " + subId);
         NetworkRequest subSpecificDefault = makeSubSpecificInternetRequest(subId);
-        waitForMs(250);
+        processAllMessages();
         assertEquals(2, mNetworkRequestList.size());
 
         log("setPhoneActive false: phoneId = " + phoneId);
-        mPhoneSwitcherMock.setPhoneActive(phoneId, false);
-        waitForMs(250);
+        activatePhoneInPhoneSwitcher(phoneId, false);
+        processAllMessages();
         assertEquals(0, mNetworkRequestList.size());
 
         log("makeSubSpecificInternetRequest: subId = " + subId);
         NetworkRequest subSpecificMms = makeSubSpecificMmsRequest(subId);
-        waitForMs(250);
+        processAllMessages();
         assertEquals(0, mNetworkRequestList.size());
 
         log("setPhoneActive true: phoneId = " + phoneId);
-        mPhoneSwitcherMock.setPhoneActive(phoneId, true);
-        waitForMs(250);
+        activatePhoneInPhoneSwitcher(phoneId, true);
+        processAllMessages();
         assertEquals(3, mNetworkRequestList.size());
 
         log("releaseNetworkRequest: subSpecificDefault = " + subSpecificDefault);
-        mConnectivityServiceMock.releaseNetworkRequest(subSpecificDefault);
-        waitForMs(250);
+        releaseNetworkRequest(subSpecificDefault);
+        processAllMessages();
         assertEquals(2, mNetworkRequestList.size());
 
         log("setPhoneActive false: phoneId = " + phoneId);
-        mPhoneSwitcherMock.setPhoneActive(phoneId, false);
-        waitForMs(250);
+        activatePhoneInPhoneSwitcher(phoneId, false);
+        processAllMessages();
         assertEquals(0, mNetworkRequestList.size());
 
         log("releaseNetworkRequest: subSpecificMms = " + subSpecificMms);
-        mConnectivityServiceMock.releaseNetworkRequest(subSpecificMms);
-        waitForMs(250);
+        releaseNetworkRequest(subSpecificMms);
+        processAllMessages();
         assertEquals(0, mNetworkRequestList.size());
 
         log("setPhoneActive true: phoneId = " + phoneId);
-        mPhoneSwitcherMock.setPhoneActive(phoneId, true);
-        waitForMs(250);
+        activatePhoneInPhoneSwitcher(phoneId, true);
+        processAllMessages();
         assertEquals(1, mNetworkRequestList.size());
     }
 
@@ -260,56 +263,53 @@
         final int altSubId = 1;
         final int unusedSubId = 2;
 
-        createMockedTelephonyComponents(numberOfPhones);
+        createMockedTelephonyComponents();
 
-        mPhoneSwitcherMock.setPreferredDataPhoneId(phoneId);
-        mSubscriptionControllerMock.setDefaultDataSubId(subId);
-        mSubscriptionControllerMock.setSlotSubId(phoneId, subId);
-        mSubscriptionMonitorMock.notifySubscriptionChanged(phoneId);
-        waitForMs(250);
-
+        doReturn(subId).when(mSubscriptionController).getSubIdUsingPhoneId(phoneId);
+        mTelephonyNetworkFactoryUT.mInternalHandler.sendEmptyMessage(
+                TelephonyNetworkFactory.EVENT_SUBSCRIPTION_CHANGED);
+        processAllMessages();
         assertEquals(0, mNetworkRequestList.size());
 
-        mPhoneSwitcherMock.setPhoneActive(phoneId, true);
-        waitForMs(250);
+        activatePhoneInPhoneSwitcher(phoneId, true);
+        processAllMessages();
         assertEquals(0, mNetworkRequestList.size());
 
-        mConnectivityServiceMock.addDefaultRequest();
-        waitForMs(250);
+        makeDefaultInternetRequest();
+        processAllMessages();
         assertEquals(1, mNetworkRequestList.size());
 
-        mSubscriptionControllerMock.setSlotSubId(altPhoneId, altSubId);
-        waitForMs(250);
+        doReturn(altSubId).when(mSubscriptionController).getSubIdUsingPhoneId(altPhoneId);
+        processAllMessages();
         assertEquals(1, mNetworkRequestList.size());
 
-        mPhoneSwitcherMock.setPreferredDataPhoneId(altPhoneId);
-        mSubscriptionControllerMock.setDefaultDataSubId(altSubId);
-        mPhoneSwitcherMock.notifyActivePhoneChange(phoneId);
-
-        waitForMs(250);
+        activatePhoneInPhoneSwitcher(phoneId, false);
+        mTelephonyNetworkFactoryUT.mInternalHandler.sendEmptyMessage(EVENT_ACTIVE_PHONE_SWITCH);
+        processAllMessages();
         assertEquals(0, mNetworkRequestList.size());
 
-        makeSubSpecificMmsRequest(subId);
-        waitForMs(250);
+        NetworkRequest subSpecificMmsRequest = makeSubSpecificMmsRequest(subId);
+        activatePhoneInPhoneSwitcher(phoneId, subSpecificMmsRequest, true);
+        processAllMessages();
         assertEquals(1, mNetworkRequestList.size());
 
-        mSubscriptionControllerMock.setSlotSubId(phoneId, unusedSubId);
-        mSubscriptionMonitorMock.notifySubscriptionChanged(phoneId);
-        waitForMs(250);
+        doReturn(unusedSubId).when(mSubscriptionController).getSubIdUsingPhoneId(phoneId);
+        mTelephonyNetworkFactoryUT.mInternalHandler.sendEmptyMessage(
+                TelephonyNetworkFactory.EVENT_SUBSCRIPTION_CHANGED);
+        processAllMessages();
         assertEquals(0, mNetworkRequestList.size());
 
         makeSubSpecificInternetRequest(subId);
-        waitForMs(250);
+        processAllMessages();
         assertEquals(0, mNetworkRequestList.size());
 
-        mSubscriptionControllerMock.setSlotSubId(phoneId, subId);
-        mSubscriptionMonitorMock.notifySubscriptionChanged(phoneId);
-        waitForMs(250);
+        doReturn(subId).when(mSubscriptionController).getSubIdUsingPhoneId(phoneId);
+        mTelephonyNetworkFactoryUT.mInternalHandler.sendEmptyMessage(
+                TelephonyNetworkFactory.EVENT_SUBSCRIPTION_CHANGED);
+        processAllMessages();
 
-        mSubscriptionControllerMock.setDefaultDataSubId(subId);
-        mPhoneSwitcherMock.setPreferredDataPhoneId(phoneId);
-        mPhoneSwitcherMock.notifyActivePhoneChange(phoneId);
-        waitForMs(250);
+        activatePhoneInPhoneSwitcher(phoneId, true);
+        processAllMessages();
         assertEquals(3, mNetworkRequestList.size());
     }
 
@@ -319,18 +319,16 @@
     @Test
     @SmallTest
     public void testHandoverNoLiveData() throws Exception {
-        createMockedTelephonyComponents(1);
-        mPhoneSwitcherMock.setPreferredDataPhoneId(0);
-        mSubscriptionControllerMock.setDefaultDataSubId(0);
-        mSubscriptionControllerMock.setSlotSubId(0, 0);
-        mSubscriptionMonitorMock.notifySubscriptionChanged(0);
+        createMockedTelephonyComponents();
+        doReturn(0).when(mSubscriptionController).getSubIdUsingPhoneId(0);
+        mTelephonyNetworkFactoryUT.mInternalHandler.sendEmptyMessage(
+                TelephonyNetworkFactory.EVENT_SUBSCRIPTION_CHANGED);
 
-        mPhoneSwitcherMock.setPhoneActive(0, true);
-        mConnectivityServiceMock.addDefaultRequest();
+        activatePhoneInPhoneSwitcher(0, true);
+        makeDefaultInternetRequest();
 
         makeSubSpecificMmsRequest(0);
-
-        waitForMs(100);
+        processAllMessages();
 
         Field f = TelephonyNetworkFactory.class.getDeclaredField("mInternalHandler");
         f.setAccessible(true);
@@ -342,7 +340,7 @@
                 AccessNetworkConstants.TRANSPORT_TYPE_WLAN, handoverCallback);
         AsyncResult ar = new AsyncResult(null, hp, null);
         h.sendMessage(h.obtainMessage(5, ar));
-        waitForMs(100);
+        processAllMessages();
 
         doReturn(AccessNetworkConstants.TRANSPORT_TYPE_WLAN).when(mTransportManager)
                 .getCurrentTransport(anyInt());
@@ -351,6 +349,43 @@
                 handoverCallback);
         ar = new AsyncResult(null, hp, null);
         h.sendMessage(h.obtainMessage(5, ar));
-        waitForMs(100);
+        processAllMessages();
     }
+
+    /**
+     * Test handover when the data connection is being connected.
+     */
+    @Test
+    @SmallTest
+    public void testHandoverActivatingData() throws Exception {
+        createMockedTelephonyComponents();
+        doReturn(0).when(mSubscriptionController).getSubIdUsingPhoneId(0);
+        mTelephonyNetworkFactoryUT.mInternalHandler.sendEmptyMessage(
+                TelephonyNetworkFactory.EVENT_SUBSCRIPTION_CHANGED);
+
+        activatePhoneInPhoneSwitcher(0, true);
+        makeDefaultInternetRequest();
+
+        makeSubSpecificMmsRequest(0);
+        processAllMessages();
+
+        Field f = TelephonyNetworkFactory.class.getDeclaredField("mInternalHandler");
+        f.setAccessible(true);
+        Handler h = (Handler) f.get(mTelephonyNetworkFactoryUT);
+
+        HandoverCallback handoverCallback = mock(HandoverCallback.class);
+        Mockito.reset(mDcTracker);
+        doReturn(mDataConnection).when(mDcTracker).getDataConnectionByApnType(anyString());
+        doReturn(false).when(mDataConnection).isActive();
+
+        HandoverParams hp = new HandoverParams(ApnSetting.TYPE_MMS,
+                AccessNetworkConstants.TRANSPORT_TYPE_WLAN, handoverCallback);
+        AsyncResult ar = new AsyncResult(null, hp, null);
+        h.sendMessage(h.obtainMessage(5, ar));
+        processAllMessages();
+
+        verify(mDcTracker, times(1)).releaseNetwork(any(), eq(1));
+        verify(mDcTracker, times(1)).requestNetwork(any(), eq(1), any());
+    }
+
 }
diff --git a/tests/telephonytests/src/com/android/internal/telephony/dataconnection/TransportManagerTest.java b/tests/telephonytests/src/com/android/internal/telephony/dataconnection/TransportManagerTest.java
index fc80e76..12c7203 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/dataconnection/TransportManagerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/dataconnection/TransportManagerTest.java
@@ -16,8 +16,6 @@
 
 package com.android.internal.telephony.dataconnection;
 
-import static com.android.internal.telephony.TelephonyTestUtils.waitForMs;
-
 import static org.junit.Assert.assertEquals;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.Matchers.anyLong;
@@ -27,12 +25,13 @@
 
 import android.os.AsyncResult;
 import android.os.Handler;
-import android.os.HandlerThread;
 import android.os.Message;
 import android.telephony.AccessNetworkConstants;
 import android.telephony.AccessNetworkConstants.AccessNetworkType;
 import android.telephony.data.ApnSetting;
 import android.test.suitebuilder.annotation.SmallTest;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
 
 import com.android.internal.telephony.TelephonyTest;
 import com.android.internal.telephony.dataconnection.AccessNetworksManager.QualifiedNetworks;
@@ -41,6 +40,7 @@
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
+import org.junit.runner.RunWith;
 import org.mockito.ArgumentCaptor;
 import org.mockito.Mock;
 
@@ -50,6 +50,8 @@
 import java.util.LinkedList;
 import java.util.List;
 
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper
 public class TransportManagerTest extends TelephonyTest {
     private static final int EVENT_HANDOVER_NEEDED = 1;
 
@@ -58,33 +60,15 @@
 
     private TransportManager mTransportManager;
 
-    private TransportManagerTestHandler mTransportManagerTestHandler;
-
-    private class TransportManagerTestHandler extends HandlerThread {
-
-        private TransportManagerTestHandler(String name) {
-            super(name);
-        }
-
-        @Override
-        public void onLooperPrepared() {
-            mTransportManager = new TransportManager(mPhone);
-            setReady(true);
-        }
-    }
-
     @Before
     public void setUp() throws Exception {
         super.setUp(getClass().getSimpleName());
-        mTransportManagerTestHandler = new TransportManagerTestHandler(TAG);
-        mTransportManagerTestHandler.start();
-        waitUntilReady();
+        mTransportManager = new TransportManager(mPhone);
+        processAllMessages();
     }
 
     @After
     public void tearDown() throws Exception {
-        mTransportManagerTestHandler.quit();
-        mTransportManagerTestHandler.join();
         super.tearDown();
     }
 
@@ -100,7 +84,7 @@
                                 AccessNetworkType.IWLAN})));
         mTransportManager.obtainMessage(1 /* EVENT_QUALIFIED_NETWORKS_CHANGED */,
                 new AsyncResult(null, networkList, null)).sendToTarget();
-        waitForMs(100);
+        processAllMessages();
         // Verify handover needed event was not sent
         verify(mTestHandler, never()).sendMessageAtTime(any(Message.class), anyLong());
 
@@ -114,7 +98,7 @@
                                 AccessNetworkType.EUTRAN})));
         mTransportManager.obtainMessage(1 /* EVENT_QUALIFIED_NETWORKS_CHANGED */,
                 new AsyncResult(null, networkList, null)).sendToTarget();
-        waitForMs(100);
+        processAllMessages();
 
         ArgumentCaptor<Message> messageArgumentCaptor = ArgumentCaptor.forClass(Message.class);
 
@@ -140,7 +124,7 @@
                                 AccessNetworkType.IWLAN})));
         mTransportManager.obtainMessage(1 /* EVENT_QUALIFIED_NETWORKS_CHANGED */,
                 new AsyncResult(null, networkList, null)).sendToTarget();
-        waitForMs(100);
+        processAllMessages();
 
         messageArgumentCaptor = ArgumentCaptor.forClass(Message.class);
 
@@ -175,7 +159,7 @@
                                 AccessNetworkType.IWLAN})));
         mTransportManager.obtainMessage(1 /* EVENT_QUALIFIED_NETWORKS_CHANGED */,
                 new AsyncResult(null, networkList, null)).sendToTarget();
-        waitForMs(100);
+        processAllMessages();
         // Verify handover needed event was not sent
         verify(mTestHandler, never()).sendMessageAtTime(any(Message.class), anyLong());
 
@@ -186,7 +170,7 @@
                                 AccessNetworkType.IWLAN})));
         mTransportManager.obtainMessage(1 /* EVENT_QUALIFIED_NETWORKS_CHANGED */,
                 new AsyncResult(null, networkList, null)).sendToTarget();
-        waitForMs(100);
+        processAllMessages();
         // Verify handover needed event was not sent
         verify(mTestHandler, never()).sendMessageAtTime(any(Message.class), anyLong());
     }
@@ -209,7 +193,7 @@
                                 AccessNetworkType.IWLAN})));
         mTransportManager.obtainMessage(1 /* EVENT_QUALIFIED_NETWORKS_CHANGED */,
                 new AsyncResult(null, networkList, null)).sendToTarget();
-        waitForMs(100);
+        processAllMessages();
         // Verify handover needed event was not sent
         verify(mTestHandler, never()).sendMessageAtTime(any(Message.class), anyLong());
 
@@ -223,7 +207,7 @@
                                 AccessNetworkType.EUTRAN})));
         mTransportManager.obtainMessage(1 /* EVENT_QUALIFIED_NETWORKS_CHANGED */,
                 new AsyncResult(null, networkList, null)).sendToTarget();
-        waitForMs(100);
+        processAllMessages();
 
         ArgumentCaptor<Message> messageArgumentCaptor = ArgumentCaptor.forClass(Message.class);
 
@@ -245,7 +229,7 @@
                                 AccessNetworkType.IWLAN})));
         mTransportManager.obtainMessage(1 /* EVENT_QUALIFIED_NETWORKS_CHANGED */,
                 new AsyncResult(null, networkList, null)).sendToTarget();
-        waitForMs(100);
+        processAllMessages();
 
         // Verify handover needed event was sent only once (for the previous change)
         verify(mTestHandler, times(1)).sendMessageAtTime(messageArgumentCaptor.capture(),
@@ -259,7 +243,7 @@
         params.callback.onCompleted(true, false);
         assertEquals(AccessNetworkConstants.TRANSPORT_TYPE_WLAN,
                 mTransportManager.getCurrentTransport(ApnSetting.TYPE_IMS));
-        waitForMs(100);
+        processAllMessages();
 
         listQueue = getAvailableNetworksList();
         // Verify the queue is empty.
diff --git a/tests/telephonytests/src/com/android/internal/telephony/emergency/EmergencyNumberTrackerTest.java b/tests/telephonytests/src/com/android/internal/telephony/emergency/EmergencyNumberTrackerTest.java
index 290c535..7a637cf 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/emergency/EmergencyNumberTrackerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/emergency/EmergencyNumberTrackerTest.java
@@ -17,13 +17,18 @@
 package com.android.internal.telephony.emergency;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.Mockito.doReturn;
 
 import android.os.AsyncResult;
-import android.os.HandlerThread;
 import android.telephony.emergency.EmergencyNumber;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
 
+import androidx.test.InstrumentationRegistry;
+
+import com.android.internal.telephony.HalVersion;
 import com.android.internal.telephony.Phone;
 import com.android.internal.telephony.PhoneFactory;
 import com.android.internal.telephony.TelephonyTest;
@@ -31,6 +36,7 @@
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
+import org.junit.runner.RunWith;
 import org.mockito.Mock;
 
 import java.util.ArrayList;
@@ -40,6 +46,8 @@
 /**
  * Tests for EmergencyNumberTracker.java
  */
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper
 public class EmergencyNumberTrackerTest extends TelephonyTest {
 
     @Mock
@@ -53,28 +61,13 @@
     private List<EmergencyNumber> mEmergencyNumberListTestSample = new ArrayList<>();
     private EmergencyNumber mUsEmergencyNumber;
     private String[] mEmergencyNumberPrefixTestSample = {"123", "456"};
-    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);
-            mEmergencyNumberTrackerMock2 = new EmergencyNumberTracker(mPhone2, mSimulatedCommands);
-            doReturn(mEmergencyNumberTrackerMock2).when(mPhone2).getEmergencyNumberTracker();
-            mEmergencyNumberTrackerMock.DBG = true;
-            setReady(true);
-        }
-    }
-
-    private EmergencyNumberTrackerTestHandler mHandlerThread;
 
     @Before
     public void setUp() throws Exception {
         logd("EmergencyNumberTrackerTest +Setup!");
         super.setUp("EmergencyNumberTrackerTest");
+        mContext = InstrumentationRegistry.getTargetContext();
+
         doReturn(mContext).when(mPhone).getContext();
         doReturn(0).when(mPhone).getPhoneId();
 
@@ -82,9 +75,11 @@
         doReturn(1).when(mPhone2).getPhoneId();
 
         initializeEmergencyNumberListTestSamples();
-        mHandlerThread = new EmergencyNumberTrackerTestHandler("EmergencyNumberTrackerTestHandler");
-        mHandlerThread.start();
-        waitUntilReady();
+        mEmergencyNumberTrackerMock = new EmergencyNumberTracker(mPhone, mSimulatedCommands);
+        mEmergencyNumberTrackerMock2 = new EmergencyNumberTracker(mPhone2, mSimulatedCommands);
+        doReturn(mEmergencyNumberTrackerMock2).when(mPhone2).getEmergencyNumberTracker();
+        mEmergencyNumberTrackerMock.DBG = true;
+        processAllMessages();
         logd("EmergencyNumberTrackerTest -Setup!");
     }
 
@@ -92,9 +87,6 @@
     public void tearDown() throws Exception {
         // Set back to single sim mode
         setSinglePhone();
-
-        mHandlerThread.quit();
-        mHandlerThread.join();
         super.tearDown();
     }
 
@@ -118,19 +110,19 @@
                 mEmergencyNumberTrackerMock.obtainMessage(
                         1 /* EVENT_UNSOL_EMERGENCY_NUMBER_LIST */,
                         new AsyncResult(null, mEmergencyNumberListTestSample, null)));
-        waitForHandlerAction(mEmergencyNumberTrackerMock, TIMEOUT_MS);
+        processAllMessages();
     }
 
     private void cacheEmergencyNumberListFromDatabaseByCountry(String countryIso) {
         mEmergencyNumberTrackerMock.updateEmergencyNumberDatabaseCountryChange(countryIso);
-        waitForHandlerAction(mEmergencyNumberTrackerMock, TIMEOUT_MS);
+        processAllMessages();
     }
 
     private void sendEmergencyNumberPrefix(EmergencyNumberTracker emergencyNumberTrackerMock) {
         emergencyNumberTrackerMock.obtainMessage(
         	4 /* EVENT_UPDATE_EMERGENCY_NUMBER_PREFIX */,
                 mEmergencyNumberPrefixTestSample).sendToTarget();
-        waitForHandlerAction(emergencyNumberTrackerMock, TIMEOUT_MS);
+        processAllMessages();
     }
 
     private void setDsdsPhones() throws Exception {
@@ -154,7 +146,7 @@
     public void testUpdateEmergencyCountryIso() throws Exception {
         sendEmergencyNumberPrefix(mEmergencyNumberTrackerMock);
         mEmergencyNumberTrackerMock.updateEmergencyNumberDatabaseCountryChange("us");
-        waitForHandlerAction(mEmergencyNumberTrackerMock, TIMEOUT_MS);
+        processAllMessages();
 
         assertTrue(mEmergencyNumberTrackerMock.getEmergencyCountryIso().equals("us"));
     }
@@ -165,8 +157,7 @@
         sendEmergencyNumberPrefix(mEmergencyNumberTrackerMock);
         sendEmergencyNumberPrefix(mEmergencyNumberTrackerMock2);
         mEmergencyNumberTrackerMock.updateEmergencyCountryIsoAllPhones("jp");
-        waitForHandlerAction(mEmergencyNumberTrackerMock, TIMEOUT_MS);
-        waitForHandlerAction(mEmergencyNumberTrackerMock2, TIMEOUT_MS);
+        processAllMessages();
 
         assertTrue(mEmergencyNumberTrackerMock.getEmergencyCountryIso().equals("jp"));
         assertTrue(mEmergencyNumberTrackerMock2.getEmergencyCountryIso().equals("jp"));
@@ -181,16 +172,14 @@
         // First, both slots have empty country iso, trigger a country change to "jp".
         // We should expect both sims have "jp" country iso.
         mEmergencyNumberTrackerMock.updateEmergencyCountryIsoAllPhones("jp");
-        waitForHandlerAction(mEmergencyNumberTrackerMock, TIMEOUT_MS);
-        waitForHandlerAction(mEmergencyNumberTrackerMock2, TIMEOUT_MS);
+        processAllMessages();
         assertTrue(mEmergencyNumberTrackerMock.getEmergencyCountryIso().equals("jp"));
         assertTrue(mEmergencyNumberTrackerMock2.getEmergencyCountryIso().equals("jp"));
 
         // Second, both slots now have "jp" country iso, trigger a country change to "us".
         // We should expect both sims have "us" country iso.
         mEmergencyNumberTrackerMock.updateEmergencyCountryIsoAllPhones("us");
-        waitForHandlerAction(mEmergencyNumberTrackerMock, TIMEOUT_MS);
-        waitForHandlerAction(mEmergencyNumberTrackerMock2, TIMEOUT_MS);
+        processAllMessages();
         assertTrue(mEmergencyNumberTrackerMock.getEmergencyCountryIso().equals("us"));
         assertTrue(mEmergencyNumberTrackerMock2.getEmergencyCountryIso().equals("us"));
 
@@ -201,12 +190,53 @@
         // to "ca".
         mEmergencyNumberTrackerMock2.mIsCountrySetByAnotherSub = false;
         mEmergencyNumberTrackerMock.updateEmergencyCountryIsoAllPhones("ca");
-        waitForHandlerAction(mEmergencyNumberTrackerMock, TIMEOUT_MS);
-        waitForHandlerAction(mEmergencyNumberTrackerMock2, TIMEOUT_MS);
+        processAllMessages();
         assertTrue(mEmergencyNumberTrackerMock.getEmergencyCountryIso().equals("ca"));
         assertTrue(mEmergencyNumberTrackerMock2.getEmergencyCountryIso().equals("us"));
     }
 
+    /**
+     * In 1.3 or less HAL. we should not use database number.
+     */
+    @Test
+    public void testUsingEmergencyNumberDatabaseWheneverHal_1_3() {
+        doReturn(new HalVersion(1, 3)).when(mPhone).getHalVersion();
+
+        sendEmergencyNumberPrefix(mEmergencyNumberTrackerMock);
+        mEmergencyNumberTrackerMock.updateEmergencyCountryIsoAllPhones("us");
+        processAllMessages();
+
+        boolean hasDatabaseNumber = false;
+        for (EmergencyNumber number : mEmergencyNumberTrackerMock.getEmergencyNumberList()) {
+            if (number.isFromSources(EmergencyNumber.EMERGENCY_NUMBER_SOURCE_DATABASE)) {
+                hasDatabaseNumber = true;
+                break;
+            }
+        }
+        assertFalse(hasDatabaseNumber);
+    }
+
+    /**
+     * In 1.4 or above HAL, we should use database number.
+     */
+    @Test
+    public void testUsingEmergencyNumberDatabaseWheneverHal_1_4() {
+        doReturn(new HalVersion(1, 4)).when(mPhone).getHalVersion();
+
+        sendEmergencyNumberPrefix(mEmergencyNumberTrackerMock);
+        mEmergencyNumberTrackerMock.updateEmergencyCountryIsoAllPhones("us");
+        processAllMessages();
+
+        boolean hasDatabaseNumber = false;
+        for (EmergencyNumber number : mEmergencyNumberTrackerMock.getEmergencyNumberList()) {
+            if (number.isFromSources(EmergencyNumber.EMERGENCY_NUMBER_SOURCE_DATABASE)) {
+                hasDatabaseNumber = true;
+                break;
+            }
+        }
+        assertTrue(hasDatabaseNumber);
+    }
+
     @Test
     public void testEmergencyNumberListPrefix() throws Exception {
         sendEmergencyNumberListFromRadio();
diff --git a/tests/telephonytests/src/com/android/internal/telephony/euicc/EuiccCardControllerTest.java b/tests/telephonytests/src/com/android/internal/telephony/euicc/EuiccCardControllerTest.java
index f4efcb4..770f34a 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/euicc/EuiccCardControllerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/euicc/EuiccCardControllerTest.java
@@ -30,8 +30,8 @@
 import android.provider.Settings;
 import android.telephony.TelephonyManager;
 import android.telephony.euicc.EuiccManager;
-
-import androidx.test.runner.AndroidJUnit4;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
 
 import com.android.internal.telephony.TelephonyTest;
 import com.android.internal.telephony.uicc.IccCardStatus.CardState;
@@ -48,9 +48,9 @@
 import org.mockito.stubbing.Answer;
 
 import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
 
-@RunWith(AndroidJUnit4.class)
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper
 public class EuiccCardControllerTest extends TelephonyTest {
     private static final String KEY_LAST_BOOT_COUNT = "last_boot_count";
 
@@ -203,14 +203,12 @@
         mSp.edit().remove(KEY_LAST_BOOT_COUNT);
         Settings.Global.putInt(mContext.getContentResolver(), Settings.Global.BOOT_COUNT, 0);
         when(mUiccController.getUiccSlots()).thenReturn(new UiccSlot[] {mActivatedEsimSlot});
-
         mEuiccCardController =
                 new EuiccCardController(mContext, null, mEuiccController, mUiccController);
+
         mContext.sendBroadcast(new Intent(TelephonyManager.ACTION_SIM_SLOT_STATUS_CHANGED));
-        try {
-            mOtaLatch.await(5000, TimeUnit.MILLISECONDS);
-            assertTrue(mOtaStarted);
-        } catch (InterruptedException ignore) { }
+        processAllMessages();
+        assertTrue(mOtaStarted);
     }
 
     @Test
@@ -219,14 +217,12 @@
         mSp.edit().remove(KEY_LAST_BOOT_COUNT);
         Settings.Global.putInt(mContext.getContentResolver(), Settings.Global.BOOT_COUNT, 0);
         when(mUiccController.getUiccSlots()).thenReturn(new UiccSlot[] {mNotPresentEsimSlot});
-
         mEuiccCardController =
             new EuiccCardController(mContext, null, mEuiccController, mUiccController);
+
         mContext.sendBroadcast(new Intent(TelephonyManager.ACTION_SIM_SLOT_STATUS_CHANGED));
-        try {
-            mOtaLatch.await(5000, TimeUnit.MILLISECONDS);
-            assertFalse(mOtaStarted);
-        } catch (InterruptedException ignore) { }
+        processAllMessages();
+        assertFalse(mOtaStarted);
     }
 
     @Test
@@ -239,6 +235,7 @@
                 new EuiccCardController(mContext, null, mEuiccController, mUiccController);
 
         mContext.sendBroadcast(new Intent(TelephonyManager.ACTION_SIM_SLOT_STATUS_CHANGED));
+        processAllMessages();
         assertFalse(mOtaStarted);
     }
 
@@ -249,10 +246,11 @@
         Settings.Global.putInt(mContext.getContentResolver(), Settings.Global.BOOT_COUNT, 0);
         when(mUiccController.getUiccSlots())
                 .thenReturn(new UiccSlot[] {mActivatedRemovableSlot, mInactivatedEsimSlot});
-
         mEuiccCardController =
                 new EuiccCardController(mContext, null, mEuiccController, mUiccController);
+
         mContext.sendBroadcast(new Intent(TelephonyManager.ACTION_SIM_SLOT_STATUS_CHANGED));
+        processAllMessages();
         assertFalse(mOtaStarted);
     }
 }
diff --git a/tests/telephonytests/src/com/android/internal/telephony/euicc/EuiccControllerTest.java b/tests/telephonytests/src/com/android/internal/telephony/euicc/EuiccControllerTest.java
index 71399c5..7c33f34 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/euicc/EuiccControllerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/euicc/EuiccControllerTest.java
@@ -15,6 +15,7 @@
  */
 package com.android.internal.telephony.euicc;
 
+import static android.telephony.euicc.EuiccCardManager.RESET_OPTION_DELETE_OPERATIONAL_PROFILES;
 import static android.telephony.euicc.EuiccManager.EUICC_OTA_STATUS_UNAVAILABLE;
 
 import static org.junit.Assert.assertEquals;
@@ -984,8 +985,7 @@
     public void testEraseSubscriptions_error() throws Exception {
         setHasWriteEmbeddedPermission(true);
         callEraseSubscriptions(true /* complete */, 42 /* result */);
-        verifyIntentSent(EuiccManager.EMBEDDED_SUBSCRIPTION_RESULT_ERROR,
-                42 /* detailedCode */);
+        verifyIntentSent(EuiccManager.EMBEDDED_SUBSCRIPTION_RESULT_ERROR, 42 /* detailedCode */);
     }
 
     @Test
@@ -997,6 +997,36 @@
     }
 
     @Test(expected = SecurityException.class)
+    public void testEraseSubscriptionsWithOptions_noPrivileges() throws Exception {
+        setHasWriteEmbeddedPermission(false);
+        callEraseSubscriptionsWithOptions(false /* complete */, 0 /* result */);
+    }
+
+    @Test
+    public void testEraseSubscriptionsWithOptions_serviceUnavailable() throws Exception {
+        setHasWriteEmbeddedPermission(true);
+        callEraseSubscriptionsWithOptions(false /* complete */, 0 /* result */);
+        verifyIntentSent(EuiccManager.EMBEDDED_SUBSCRIPTION_RESULT_ERROR,
+                0 /* detailedCode */);
+        verify(mMockConnector).eraseSubscriptionsWithOptions(anyInt(), anyInt(), any());
+    }
+
+    @Test
+    public void testEraseSubscriptionsWithOptions_error() throws Exception {
+        setHasWriteEmbeddedPermission(true);
+        callEraseSubscriptionsWithOptions(true /* complete */, 42 /* result */);
+        verifyIntentSent(EuiccManager.EMBEDDED_SUBSCRIPTION_RESULT_ERROR, 42 /* detailedCode */);
+    }
+
+    @Test
+    public void testEraseSubscriptionsWithOptions_success() throws Exception {
+        setHasWriteEmbeddedPermission(true);
+        callEraseSubscriptionsWithOptions(true /* complete */, EuiccService.RESULT_OK);
+        verifyIntentSent(EuiccManager.EMBEDDED_SUBSCRIPTION_RESULT_OK, 0 /* detailedCode */);
+        assertTrue(mController.mCalledRefreshSubscriptionsAndSendResult);
+    }
+
+    @Test(expected = SecurityException.class)
     public void testRetainSubscriptionsForFactoryReset_noPrivileges() throws Exception {
         setHasMasterClearPermission(false);
         callRetainSubscriptionsForFactoryReset(false /* complete */, 0 /* result */);
@@ -1024,6 +1054,78 @@
         verifyIntentSent(EuiccManager.EMBEDDED_SUBSCRIPTION_RESULT_OK, 0 /* detailedCode */);
     }
 
+    @Test
+    public void testAddExtrasToResultIntent_withSmdxOperationCode_normal_case() {
+        // Same setup as testGetDownloadableSubscriptionMetadata_error
+        setHasWriteEmbeddedPermission(true);
+        GetDownloadableSubscriptionMetadataResult result =
+                new GetDownloadableSubscriptionMetadataResult(0xA8b1051 /* result */,
+                        null /* subscription */);
+        callGetDownloadableSubscriptionMetadata(SUBSCRIPTION, true /* complete */, result);
+
+        assertEquals(mController.mExtrasIntent.getIntExtra(
+                EuiccManager.EXTRA_EMBEDDED_SUBSCRIPTION_OPERATION_CODE, -1),
+                EuiccManager.OPERATION_SMDX_SUBJECT_REASON_CODE);
+        assertEquals(mController.mExtrasIntent.getStringExtra(
+                EuiccManager.EXTRA_EMBEDDED_SUBSCRIPTION_SMDX_SUBJECT_CODE), "8.11.1");
+        assertEquals(mController.mExtrasIntent.getStringExtra(
+                EuiccManager.EXTRA_EMBEDDED_SUBSCRIPTION_SMDX_REASON_CODE), "5.1");
+
+    }
+
+    @Test
+    public void testAddExtrasToResultIntent_withSmdxOperationCode_general_case() {
+        // Same setup as testGetDownloadableSubscriptionMetadata_error
+        setHasWriteEmbeddedPermission(true);
+        GetDownloadableSubscriptionMetadataResult result =
+                new GetDownloadableSubscriptionMetadataResult(0xA123456 /* result */,
+                        null /* subscription */);
+        callGetDownloadableSubscriptionMetadata(SUBSCRIPTION, true /* complete */, result);
+
+        assertEquals(mController.mExtrasIntent.getIntExtra(
+                EuiccManager.EXTRA_EMBEDDED_SUBSCRIPTION_OPERATION_CODE, -1),
+                EuiccManager.OPERATION_SMDX_SUBJECT_REASON_CODE);
+        assertEquals(mController.mExtrasIntent.getStringExtra(
+                EuiccManager.EXTRA_EMBEDDED_SUBSCRIPTION_SMDX_SUBJECT_CODE), "1.2.3");
+        assertEquals(mController.mExtrasIntent.getStringExtra(
+                EuiccManager.EXTRA_EMBEDDED_SUBSCRIPTION_SMDX_REASON_CODE), "4.5.6");
+
+    }
+
+    @Test
+    public void testAddExtrasToResultIntent_withSmdxOperationCode_and_padding() {
+        // Same setup as testGetDownloadableSubscriptionMetadata_error
+        setHasWriteEmbeddedPermission(true);
+        GetDownloadableSubscriptionMetadataResult result =
+                new GetDownloadableSubscriptionMetadataResult(0xA003006 /* result */,
+                        null /* subscription */);
+        callGetDownloadableSubscriptionMetadata(SUBSCRIPTION, true /* complete */, result);
+
+        assertEquals(mController.mExtrasIntent.getIntExtra(
+                EuiccManager.EXTRA_EMBEDDED_SUBSCRIPTION_OPERATION_CODE, -1),
+                EuiccManager.OPERATION_SMDX_SUBJECT_REASON_CODE);
+        assertEquals(mController.mExtrasIntent.getStringExtra(
+                EuiccManager.EXTRA_EMBEDDED_SUBSCRIPTION_SMDX_SUBJECT_CODE), "3");
+        assertEquals(mController.mExtrasIntent.getStringExtra(
+                EuiccManager.EXTRA_EMBEDDED_SUBSCRIPTION_SMDX_REASON_CODE), "6");
+    }
+
+    @Test
+    public void testAddExtrasToResultIntent_withOperationCode() {
+        // Same setup as testGetDownloadableSubscriptionMetadata_error
+        setHasWriteEmbeddedPermission(true);
+        GetDownloadableSubscriptionMetadataResult result =
+                new GetDownloadableSubscriptionMetadataResult(0x12345678 /* result */,
+                        null /* subscription */);
+        callGetDownloadableSubscriptionMetadata(SUBSCRIPTION, true /* complete */, result);
+
+        assertEquals(mController.mExtrasIntent.getIntExtra(
+                EuiccManager.EXTRA_EMBEDDED_SUBSCRIPTION_OPERATION_CODE, -1),
+                0x12);
+        assertEquals(mController.mExtrasIntent.getIntExtra(
+                EuiccManager.EXTRA_EMBEDDED_SUBSCRIPTION_ERROR_CODE, -1), 0x345678);
+    }
+
     private void setGetEidPermissions(
             boolean hasPhoneStatePrivileged, boolean hasCarrierPrivileges) throws Exception {
         doReturn(hasPhoneStatePrivileged
@@ -1052,7 +1154,7 @@
         SubscriptionInfo subInfo = new SubscriptionInfo(
                 0, "", 0, "", "", 0, 0, "", 0, null, "", "", "", true /* isEmbedded */,
                 hasPrivileges ? new UiccAccessRule[] { ACCESS_RULE } : null, "", CARD_ID,
-                false, null, false, 0, 0, 0, null, null);
+                false, null, false, 0, 0, 0, null, null, true);
         when(mSubscriptionManager.canManageSubscription(subInfo, PACKAGE_NAME)).thenReturn(
                 hasPrivileges);
         when(mSubscriptionManager.getActiveSubscriptionInfoList(anyBoolean())).thenReturn(
@@ -1073,11 +1175,11 @@
         SubscriptionInfo subInfo1 = new SubscriptionInfo(
                 0, "", 0, "", "", 0, 0, "", 0, null, "", "", "", true /* isEmbedded */,
                 hasPrivileges ? new UiccAccessRule[] { ACCESS_RULE } : null, "", CARD_ID,
-                false, null, false, 0, 0, 0, null, null);
+                false, null, false, 0, 0, 0, null, null, true);
         SubscriptionInfo subInfo2 = new SubscriptionInfo(
                 0, "", 0, "", "", 0, 0, "", 0, null, "", "", "", true /* isEmbedded */,
                 hasPrivileges ? new UiccAccessRule[] { ACCESS_RULE } : null, "",
-                1 /* cardId */, false, null, false, 0, 0, 0, null, null);
+                1 /* cardId */, false, null, false, 0, 0, 0, null, null, true);
         when(mSubscriptionManager.canManageSubscription(subInfo1, PACKAGE_NAME)).thenReturn(
                 hasPrivileges);
         when(mSubscriptionManager.canManageSubscription(subInfo2, PACKAGE_NAME)).thenReturn(
@@ -1318,6 +1420,25 @@
         mController.eraseSubscriptions(CARD_ID, resultCallback);
     }
 
+    private void callEraseSubscriptionsWithOptions(final boolean complete, final int result) {
+        PendingIntent resultCallback = PendingIntent.getBroadcast(mContext, 0, new Intent(), 0);
+        doAnswer(new Answer<Void>() {
+            @Override
+            public Void answer(InvocationOnMock invocation) throws Exception {
+                EuiccConnector.EraseCommandCallback cb = invocation
+                        .getArgument(2 /* resultCallback */);
+                if (complete) {
+                    cb.onEraseComplete(result);
+                } else {
+                    cb.onEuiccServiceUnavailable();
+                }
+                return null;
+            }
+        }).when(mMockConnector).eraseSubscriptionsWithOptions(anyInt(), anyInt(), any());
+        mController.eraseSubscriptionsWithOptions(CARD_ID,
+                RESET_OPTION_DELETE_OPERATIONAL_PROFILES, resultCallback);
+    }
+
     private void callRetainSubscriptionsForFactoryReset(final boolean complete, final int result) {
         PendingIntent resultCallback = PendingIntent.getBroadcast(mContext, 0, new Intent(), 0);
         doAnswer(new Answer<Void>() {
diff --git a/tests/telephonytests/src/com/android/internal/telephony/gsm/GSMTestHandler.java.broken b/tests/telephonytests/src/com/android/internal/telephony/gsm/GSMTestHandler.java.broken
index aa94e0e..16861fa 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/gsm/GSMTestHandler.java.broken
+++ b/tests/telephonytests/src/com/android/internal/telephony/gsm/GSMTestHandler.java.broken
@@ -22,7 +22,7 @@
 import android.os.HandlerThread;
 import android.os.Looper;
 import android.os.Message;
-import android.telephony.Rlog;
+import com.android.telephony.Rlog;
 
 import com.android.internal.telephony.gsm.GSMPhone;
 import com.android.internal.telephony.test.SimulatedCommands;
diff --git a/tests/telephonytests/src/com/android/internal/telephony/gsm/GsmCellBroadcastHandlerTest.java b/tests/telephonytests/src/com/android/internal/telephony/gsm/GsmCellBroadcastHandlerTest.java
deleted file mode 100644
index 9fce6d3..0000000
--- a/tests/telephonytests/src/com/android/internal/telephony/gsm/GsmCellBroadcastHandlerTest.java
+++ /dev/null
@@ -1,134 +0,0 @@
-/*
- * Copyright (C) 2016 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.gsm;
-
-import static android.provider.Settings.Secure.CMAS_ADDITIONAL_BROADCAST_PKG;
-
-import static org.junit.Assert.assertEquals;
-import static org.mockito.Matchers.nullable;
-import static org.mockito.Mockito.any;
-import static org.mockito.Mockito.anyBoolean;
-import static org.mockito.Mockito.anyInt;
-import static org.mockito.Mockito.doReturn;
-import static org.mockito.Mockito.eq;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-
-import android.Manifest;
-import android.app.Activity;
-import android.app.AppOpsManager;
-import android.content.BroadcastReceiver;
-import android.content.Intent;
-import android.os.Handler;
-import android.os.HandlerThread;
-import android.os.UserHandle;
-import android.provider.Settings;
-import android.provider.Telephony;
-
-import com.android.internal.telephony.SmsStorageMonitor;
-import com.android.internal.telephony.TelephonyTest;
-import com.android.internal.telephony.TelephonyTestUtils;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Ignore;
-import org.mockito.ArgumentCaptor;
-import org.mockito.Mock;
-
-import java.util.List;
-
-public class GsmCellBroadcastHandlerTest extends TelephonyTest {
-    @Mock
-    private SmsStorageMonitor mSmsStorageMonitor;
-    @Mock
-    private android.telephony.SmsMessage mSmsMessage;
-    @Mock
-    private SmsMessage mGsmSmsMessage;
-
-    private GsmCellBroadcastHandler mGsmCellBroadcastHandler;
-    private GsmCellBroadcastHandlerTestHandler mGsmCellBroadcastHandlerTestHandler;
-
-    private class GsmCellBroadcastHandlerTestHandler extends HandlerThread {
-
-        private GsmCellBroadcastHandlerTestHandler(String name) {
-            super(name);
-        }
-
-        @Override
-        public void onLooperPrepared() {
-            mGsmCellBroadcastHandler = GsmCellBroadcastHandler.makeGsmCellBroadcastHandler(
-                    mContextFixture.getTestDouble(), mPhone);
-            setReady(true);
-        }
-    }
-
-    @Before
-    public void setUp() throws Exception {
-
-        super.setUp(getClass().getSimpleName());
-
-        doReturn(true).when(mTelephonyManager).getSmsReceiveCapableForPhone(anyInt(), anyBoolean());
-        doReturn(true).when(mSmsStorageMonitor).isStorageAvailable();
-
-        mGsmCellBroadcastHandlerTestHandler =
-                new GsmCellBroadcastHandlerTestHandler(getClass().getSimpleName());
-        mGsmCellBroadcastHandlerTestHandler.start();
-        waitUntilReady();
-    }
-
-    @After
-    public void tearDown() throws Exception {
-        mGsmCellBroadcastHandler = null;
-        mGsmCellBroadcastHandlerTestHandler.quit();
-        super.tearDown();
-    }
-
-    @Ignore
-    public void testBroadcastSms() {
-        mContextFixture.putStringArrayResource(
-                com.android.internal.R.array.config_defaultCellBroadcastReceiverPkgs,
-                new String[]{"fake.cellbroadcastreceiver"});
-
-        Settings.Secure.putString(mContext.getContentResolver(),
-                CMAS_ADDITIONAL_BROADCAST_PKG, "another.fake.pkg");
-        mSimulatedCommands.notifyGsmBroadcastSms(new byte[] {
-                (byte)0xc0, //geographical scope
-                (byte)0x01, //serial number
-                (byte)0x01, //serial number
-                (byte)0x01, //message identifier
-                (byte)0x01, //message identifier
-                (byte)0x01
-        });
-        TelephonyTestUtils.waitForMs(100);
-        ArgumentCaptor<Intent> intentArgumentCaptor = ArgumentCaptor.forClass(Intent.class);
-        verify(mContextFixture.getTestDouble(), times(2)).sendOrderedBroadcastAsUser(
-                intentArgumentCaptor.capture(), eq(UserHandle.ALL),
-                eq(Manifest.permission.RECEIVE_SMS), eq(AppOpsManager.OP_RECEIVE_SMS),
-                nullable(BroadcastReceiver.class), any(Handler.class), eq(Activity.RESULT_OK),
-                eq(null), eq(null));
-
-        List<Intent> intentList = intentArgumentCaptor.getAllValues();
-
-        assertEquals(Telephony.Sms.Intents.SMS_CB_RECEIVED_ACTION,
-                intentList.get(0).getAction());
-        assertEquals("another.fake.pkg", intentList.get(0).getPackage());
-
-        assertEquals(Telephony.Sms.Intents.SMS_CB_RECEIVED_ACTION,
-                intentList.get(1).getAction());
-        assertEquals("fake.cellbroadcastreceiver", intentList.get(1).getPackage());
-    }
-}
diff --git a/tests/telephonytests/src/com/android/internal/telephony/gsm/GsmInboundSmsHandlerTest.java b/tests/telephonytests/src/com/android/internal/telephony/gsm/GsmInboundSmsHandlerTest.java
index 028e764..552b548 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/gsm/GsmInboundSmsHandlerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/gsm/GsmInboundSmsHandlerTest.java
@@ -20,6 +20,7 @@
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 import static org.mockito.Matchers.any;
@@ -27,9 +28,10 @@
 import static org.mockito.Matchers.anyInt;
 import static org.mockito.Matchers.anyLong;
 import static org.mockito.Matchers.nullable;
+import static org.mockito.Mockito.atLeastOnce;
 import static org.mockito.Mockito.doReturn;
-import static org.mockito.Mockito.eq;
 import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 
@@ -42,14 +44,13 @@
 import android.net.Uri;
 import android.os.AsyncResult;
 import android.os.Bundle;
-import android.os.Handler;
-import android.os.HandlerThread;
-import android.os.RemoteException;
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.provider.Telephony;
 import android.telephony.SubscriptionManager;
 import android.test.mock.MockContentResolver;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
 
 import androidx.test.filters.FlakyTest;
 import androidx.test.filters.MediumTest;
@@ -59,6 +60,7 @@
 import com.android.internal.telephony.InboundSmsTracker;
 import com.android.internal.telephony.PhoneConstants;
 import com.android.internal.telephony.SmsBroadcastUndelivered;
+import com.android.internal.telephony.SmsConstants;
 import com.android.internal.telephony.SmsHeader;
 import com.android.internal.telephony.SmsStorageMonitor;
 import com.android.internal.telephony.TelephonyTest;
@@ -70,11 +72,16 @@
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
+import org.junit.runner.RunWith;
 import org.mockito.ArgumentCaptor;
 import org.mockito.Mock;
 
 import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.List;
 
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper
 public class GsmInboundSmsHandlerTest extends TelephonyTest {
     @Mock
     private SmsStorageMonitor mSmsStorageMonitor;
@@ -97,13 +104,11 @@
     private CdmaInboundSmsHandler mCdmaInboundSmsHandler;
 
     private GsmInboundSmsHandler mGsmInboundSmsHandler;
-    private GsmInboundSmsHandlerTestHandler mGsmInboundSmsHandlerTestHandler;
 
     private FakeSmsContentProvider mContentProvider;
     private static final String RAW_TABLE_NAME = "raw";
     private static final Uri sRawUri = Uri.withAppendedPath(Telephony.Sms.CONTENT_URI,
             RAW_TABLE_NAME);
-    private static final int TEST_TIMEOUT = 5000;
 
     private String mMessageBody = "This is the message body of a single-part message";
     private String mMessageBodyPart1 = "This is the first part of a multi-part message";
@@ -113,20 +118,6 @@
 
     byte[] mSmsPdu = new byte[]{(byte)0xFF, (byte)0xFF, (byte)0xFF};
 
-    private class GsmInboundSmsHandlerTestHandler extends HandlerThread {
-
-        private GsmInboundSmsHandlerTestHandler(String name) {
-            super(name);
-        }
-
-        @Override
-        public void onLooperPrepared() {
-            mGsmInboundSmsHandler = GsmInboundSmsHandler.makeInboundSmsHandler(mContext,
-                    mSmsStorageMonitor, mPhone);
-            setReady(true);
-        }
-    }
-
     private IState getCurrentState() {
         try {
             Method method = StateMachine.class.getDeclaredMethod("getCurrentState");
@@ -165,6 +156,7 @@
         doReturn(mMessageBody).when(mMockInboundSmsTracker).getMessageBody();
         doReturn(mSmsPdu).when(mMockInboundSmsTracker).getPdu();
         doReturn(mInboundSmsTrackerCV.get("date")).when(mMockInboundSmsTracker).getTimestamp();
+        doReturn(SmsConstants.FORMAT_3GPP).when(mMockInboundSmsTracker).getFormat();
         doReturn(mInboundSmsTrackerCV).when(mMockInboundSmsTracker).getContentValues();
         doReturn(mSubId0).when(mMockInboundSmsTracker).getSubId();
 
@@ -190,12 +182,14 @@
         doReturn(mSmsPdu).when(mMockInboundSmsTrackerSub1).getPdu();
         doReturn(mInboundSmsTrackerCVSub1.get("date")).when(mMockInboundSmsTrackerSub1)
                 .getTimestamp();
+        doReturn(SmsConstants.FORMAT_3GPP).when(mMockInboundSmsTrackerSub1).getFormat();
         doReturn(mInboundSmsTrackerCVSub1).when(mMockInboundSmsTrackerSub1).getContentValues();
         doReturn(mSubId1).when(mMockInboundSmsTrackerSub1).getSubId();
 
         doReturn(mMockInboundSmsTracker).doReturn(mMockInboundSmsTrackerSub1)
                 .when(mTelephonyComponentFactory)
-                .makeInboundSmsTracker(nullable(Cursor.class), anyBoolean());
+                .makeInboundSmsTracker(any(Context.class), nullable(Cursor.class),
+                        anyBoolean());
     }
 
     @Before
@@ -207,16 +201,16 @@
 
         UserManager userManager = (UserManager)mContext.getSystemService(Context.USER_SERVICE);
         doReturn(true).when(userManager).isUserUnlocked();
+        doReturn(true).when(userManager).isUserRunning(any(UserHandle.class));
 
-        try {
-            doReturn(new int[]{UserHandle.USER_SYSTEM}).when(mIActivityManager).getRunningUserIds();
-        } catch (RemoteException re) {
-            fail("Unexpected RemoteException: " + re.getStackTrace());
-        }
+        List<UserHandle> userHandles = new ArrayList();
+        userHandles.add(UserHandle.SYSTEM);
+        doReturn(userHandles).when(userManager).getUserHandles(anyBoolean());
 
         mSmsMessage.mWrappedSmsMessage = mGsmSmsMessage;
 
         mInboundSmsTracker = new InboundSmsTracker(
+                mContext,
                 mSmsPdu, /* pdu */
                 System.currentTimeMillis(), /* timestamp */
                 -1, /* destPort */
@@ -228,7 +222,8 @@
                 false, /* isClass0 */
                 mSubId0);
         doReturn(mInboundSmsTracker).when(mTelephonyComponentFactory)
-                .makeInboundSmsTracker(nullable(byte[].class), anyLong(), anyInt(), anyBoolean(),
+                .makeInboundSmsTracker(any(Context.class), nullable(byte[].class), anyLong(),
+                anyInt(), anyBoolean(),
                 anyBoolean(), nullable(String.class), nullable(String.class),
                 nullable(String.class), anyBoolean(), anyInt());
 
@@ -238,9 +233,10 @@
         ((MockContentResolver)mContext.getContentResolver()).addProvider(
                 Telephony.Sms.CONTENT_URI.getAuthority(), mContentProvider);
 
-        mGsmInboundSmsHandlerTestHandler = new GsmInboundSmsHandlerTestHandler(TAG);
-        mGsmInboundSmsHandlerTestHandler.start();
-        waitUntilReady();
+        mGsmInboundSmsHandler = GsmInboundSmsHandler.makeInboundSmsHandler(mContext,
+                mSmsStorageMonitor, mPhone);
+        monitorTestableLooper(new TestableLooper(mGsmInboundSmsHandler.getHandler().getLooper()));
+        processAllMessages();
     }
 
     @After
@@ -249,13 +245,13 @@
         int i = 0;
         while (mGsmInboundSmsHandler.getWakeLock().isHeld() && i < 100) {
             waitForMs(100);
+            processAllMessages();
             i++;
         }
         assertFalse(mGsmInboundSmsHandler.getWakeLock().isHeld());
+        mGsmInboundSmsHandler.quit();
         mGsmInboundSmsHandler = null;
         mContentProvider.shutdown();
-        mGsmInboundSmsHandlerTestHandler.quit();
-        mGsmInboundSmsHandlerTestHandler.join();
         super.tearDown();
     }
 
@@ -265,7 +261,7 @@
 
         // trigger transition to IdleState
         mGsmInboundSmsHandler.sendMessage(InboundSmsHandler.EVENT_START_ACCEPTING_SMS);
-        waitForHandlerAction(mGsmInboundSmsHandler.getHandler(), TEST_TIMEOUT);
+        processAllMessages();
 
         assertEquals("IdleState", getCurrentState().getName());
     }
@@ -316,14 +312,9 @@
         assertEquals("WaitingState", getCurrentState().getName());
 
         mContextFixture.sendBroadcastToOrderedBroadcastReceivers();
-        // handle broadcast complete msg
-        waitForHandlerAction(mGsmInboundSmsHandler.getHandler(), TEST_TIMEOUT);
-        // transition from waiting state to delivering state
-        waitForHandlerAction(mGsmInboundSmsHandler.getHandler(), TEST_TIMEOUT);
+        processAllMessages();
         if (!moreMessages) {
-            // transition from delivering state to idle state; if moreMessages are pending will
-            // transition to WaitingState instead of IdleState
-            waitForHandlerAction(mGsmInboundSmsHandler.getHandler(), TEST_TIMEOUT);
+            processAllMessages();
             assertEquals("IdleState", getCurrentState().getName());
         }
     }
@@ -331,10 +322,8 @@
     private void sendNewSms() {
         mGsmInboundSmsHandler.sendMessage(InboundSmsHandler.EVENT_NEW_SMS,
                 new AsyncResult(null, mSmsMessage, null));
-        // handle EVENT_NEW_SMS
-        waitForHandlerAction(mGsmInboundSmsHandler.getHandler(), TEST_TIMEOUT);
-        // handle EVENT_BROADCAST_SMS
-        waitForHandlerAction(mGsmInboundSmsHandler.getHandler(), TEST_TIMEOUT);
+        // handle EVENT_NEW_SMS, EVENT_BROADCAST_SMS
+        processAllMessages();
     }
 
     @FlakyTest
@@ -375,15 +364,13 @@
                 intentArgumentCaptor.capture());
         assertEquals(Telephony.Sms.Intents.DATA_SMS_RECEIVED_ACTION,
                 intentArgumentCaptor.getAllValues().get(numPastBroadcasts).getAction());
+        assertNotEquals(0L,
+                intentArgumentCaptor.getAllValues().get(numPastBroadcasts)
+                        .getLongExtra("messageId", 0L));
         assertEquals("WaitingState", getCurrentState().getName());
 
         mContextFixture.sendBroadcastToOrderedBroadcastReceivers();
-        // handle broadcast complete msg
-        waitForHandlerAction(mGsmInboundSmsHandler.getHandler(), TEST_TIMEOUT);
-        // transition from waiting state to delivering state
-        waitForHandlerAction(mGsmInboundSmsHandler.getHandler(), TEST_TIMEOUT);
-        // transition from delivering state to idle state
-        waitForHandlerAction(mGsmInboundSmsHandler.getHandler(), TEST_TIMEOUT);
+        processAllMessages();
 
         assertEquals("IdleState", getCurrentState().getName());
     }
@@ -394,6 +381,7 @@
         transitionFromStartupToIdle();
 
         mInboundSmsTracker = new InboundSmsTracker(
+                mContext,
                 mSmsPdu, /* pdu */
                 System.currentTimeMillis(), /* timestamp */
                 -1, /* destPort */
@@ -405,13 +393,13 @@
                 true, /* isClass0 */
                 mSubId0);
         doReturn(mInboundSmsTracker).when(mTelephonyComponentFactory)
-                .makeInboundSmsTracker(nullable(byte[].class), anyLong(), anyInt(), anyBoolean(),
+                .makeInboundSmsTracker(any(Context.class), nullable(byte[].class), anyLong(),
+                        anyInt(), anyBoolean(),
                         anyBoolean(), nullable(String.class), nullable(String.class),
                         nullable(String.class), anyBoolean(), anyInt());
         mGsmInboundSmsHandler.sendMessage(InboundSmsHandler.EVENT_BROADCAST_SMS,
                 mInboundSmsTracker);
-        waitForHandlerAction(mGsmInboundSmsHandler.getHandler(), TEST_TIMEOUT);
-        waitForHandlerAction(mGsmInboundSmsHandler.getHandler(), TEST_TIMEOUT);
+        processAllMessages();
 
         verifySmsIntentBroadcasts(0, true /* allowBgActivityStarts */);
     }
@@ -421,7 +409,8 @@
     public void testBroadcastSms() {
         transitionFromStartupToIdle();
 
-        mInboundSmsTracker = new InboundSmsTracker(
+        mInboundSmsTracker = spy(new InboundSmsTracker(
+                mContext,
                 mSmsPdu, /* pdu */
                 System.currentTimeMillis(), /* timestamp */
                 0, /* destPort */
@@ -431,23 +420,23 @@
                 "1234567890", /* displayAddress */
                 mMessageBody, /* messageBody */
                 false, /* isClass0 */
-                mSubId0);
+                mSubId0));
         doReturn(mInboundSmsTracker).when(mTelephonyComponentFactory)
-                .makeInboundSmsTracker(nullable(byte[].class), anyLong(), anyInt(), anyBoolean(),
-                anyBoolean(), nullable(String.class), nullable(String.class),
-                nullable(String.class), anyBoolean(), anyInt());
+                .makeInboundSmsTracker(any(Context.class), nullable(byte[].class), anyLong(),
+                        anyInt(), anyBoolean(),
+                        anyBoolean(), nullable(String.class), nullable(String.class),
+                        nullable(String.class), anyBoolean(), anyInt());
+        doReturn(2131L).when(mInboundSmsTracker).getMessageId();
         mGsmInboundSmsHandler.sendMessage(InboundSmsHandler.EVENT_BROADCAST_SMS,
                 mInboundSmsTracker);
-        waitForHandlerAction(mGsmInboundSmsHandler.getHandler(), TEST_TIMEOUT);
-        waitForHandlerAction(mGsmInboundSmsHandler.getHandler(), TEST_TIMEOUT);
+        processAllMessages();
 
         verifyDataSmsIntentBroadcasts(0);
 
         // send same data sms again, and since it's not text sms it should be broadcast again
         mGsmInboundSmsHandler.sendMessage(InboundSmsHandler.EVENT_BROADCAST_SMS,
                 mInboundSmsTracker);
-        waitForHandlerAction(mGsmInboundSmsHandler.getHandler(), TEST_TIMEOUT);
-        waitForHandlerAction(mGsmInboundSmsHandler.getHandler(), TEST_TIMEOUT);
+        processAllMessages();
 
         verifyDataSmsIntentBroadcasts(1);
     }
@@ -460,16 +449,14 @@
 
         mGsmInboundSmsHandler.sendMessage(InboundSmsHandler.EVENT_INJECT_SMS, new AsyncResult(null,
                 mSmsMessage, null));
-        waitForHandlerAction(mGsmInboundSmsHandler.getHandler(), TEST_TIMEOUT);
-        waitForHandlerAction(mGsmInboundSmsHandler.getHandler(), TEST_TIMEOUT);
+        processAllMessages();
 
         verifySmsIntentBroadcasts(0);
 
         // inject same SMS again, verify no broadcasts are sent
         mGsmInboundSmsHandler.sendMessage(InboundSmsHandler.EVENT_INJECT_SMS, new AsyncResult(null,
                 mSmsMessage, null));
-        waitForHandlerAction(mGsmInboundSmsHandler.getHandler(), TEST_TIMEOUT);
-        waitForHandlerAction(mGsmInboundSmsHandler.getHandler(), TEST_TIMEOUT);
+        processAllMessages();
 
         verify(mContext, times(2)).sendBroadcast(any(Intent.class));
         assertEquals("IdleState", getCurrentState().getName());
@@ -478,6 +465,7 @@
     private void prepareMultiPartSms(boolean is3gpp2WapPush) {
         // Part 1
         mInboundSmsTrackerPart1 = new InboundSmsTracker(
+                mContext,
                 mSmsPdu, /* pdu */
                 System.currentTimeMillis(), /* timestamp */
                 -1, /* destPort */
@@ -494,6 +482,7 @@
 
         // Part 2
         mInboundSmsTrackerPart2 = new InboundSmsTracker(
+                mContext,
                 mSmsPdu, /* pdu */
                 System.currentTimeMillis(), /* timestamp */
                 -1, /* destPort */
@@ -527,7 +516,8 @@
 
         // part 2 of non-3gpp2wap arrives first
         doReturn(mInboundSmsTrackerPart2).when(mTelephonyComponentFactory)
-                .makeInboundSmsTracker(nullable(byte[].class), anyLong(), anyInt(), anyBoolean(),
+                .makeInboundSmsTracker(any(Context.class), nullable(byte[].class), anyLong(),
+                        anyInt(), anyBoolean(),
                         nullable(String.class), nullable(String.class), anyInt(), anyInt(),
                         anyInt(), anyBoolean(), nullable(String.class), anyBoolean(), anyInt());
         sendNewSms();
@@ -538,7 +528,8 @@
         // mock a 3gpp2wap push
         prepareMultiPartSms(true);
         doReturn(mInboundSmsTrackerPart2).when(mTelephonyComponentFactory)
-                .makeInboundSmsTracker(nullable(byte[].class), anyLong(), anyInt(), anyBoolean(),
+                .makeInboundSmsTracker(any(Context.class), nullable(byte[].class), anyLong(),
+                        anyInt(), anyBoolean(),
                         nullable(String.class), nullable(String.class), anyInt(), anyInt(),
                         anyInt(), anyBoolean(), nullable(String.class), anyBoolean(), anyInt());
         sendNewSms();
@@ -552,7 +543,8 @@
         // additional copy of part 1 of non-3gpp2wap
         prepareMultiPartSms(false);
         doReturn(mInboundSmsTrackerPart1).when(mTelephonyComponentFactory)
-                .makeInboundSmsTracker(nullable(byte[].class), anyLong(), anyInt(), anyBoolean(),
+                .makeInboundSmsTracker(any(Context.class), nullable(byte[].class), anyLong(),
+                        anyInt(), anyBoolean(),
                         nullable(String.class), nullable(String.class), anyInt(), anyInt(),
                         anyInt(), anyBoolean(), nullable(String.class), anyBoolean(), anyInt());
         sendNewSms();
@@ -578,7 +570,8 @@
         doReturn(mSmsHeader).when(mGsmSmsMessage).getUserDataHeader();
 
         doReturn(mInboundSmsTrackerPart1).when(mTelephonyComponentFactory)
-                .makeInboundSmsTracker(nullable(byte[].class), anyLong(), anyInt(), anyBoolean(),
+                .makeInboundSmsTracker(any(Context.class), nullable(byte[].class), anyLong(),
+                        anyInt(), anyBoolean(),
                         nullable(String.class), nullable(String.class), anyInt(), anyInt(),
                         anyInt(), anyBoolean(), nullable(String.class), anyBoolean(), anyInt());
         sendNewSms();
@@ -587,7 +580,8 @@
         assertEquals("IdleState", getCurrentState().getName());
 
         doReturn(mInboundSmsTrackerPart2).when(mTelephonyComponentFactory)
-                .makeInboundSmsTracker(nullable(byte[].class), anyLong(), anyInt(), anyBoolean(),
+                .makeInboundSmsTracker(any(Context.class), nullable(byte[].class), anyLong(),
+                        anyInt(), anyBoolean(),
                         nullable(String.class), nullable(String.class), anyInt(), anyInt(),
                         anyInt(), anyBoolean(), nullable(String.class), anyBoolean(), anyInt());
         sendNewSms();
@@ -601,7 +595,8 @@
 
         // additional copy of part 2 of message
         doReturn(mInboundSmsTrackerPart2).when(mTelephonyComponentFactory)
-                .makeInboundSmsTracker(nullable(byte[].class), anyLong(), anyInt(), anyBoolean(),
+                .makeInboundSmsTracker(any(Context.class), nullable(byte[].class), anyLong(),
+                        anyInt(), anyBoolean(),
                         nullable(String.class), nullable(String.class), anyInt(), anyInt(),
                         anyInt(), anyBoolean(), nullable(String.class), anyBoolean(), anyInt());
         sendNewSms();
@@ -617,7 +612,8 @@
 
         // part 1 of new sms
         doReturn(mInboundSmsTrackerPart1).when(mTelephonyComponentFactory)
-                .makeInboundSmsTracker(nullable(byte[].class), anyLong(), anyInt(), anyBoolean(),
+                .makeInboundSmsTracker(any(Context.class), nullable(byte[].class), anyLong(),
+                        anyInt(), anyBoolean(),
                         nullable(String.class), nullable(String.class), anyInt(), anyInt(),
                         anyInt(), anyBoolean(), nullable(String.class), anyBoolean(), anyInt());
         sendNewSms();
@@ -641,6 +637,7 @@
         prepareMultiPartSms(false);
         // change seqNumber in part 2 to 1
         mInboundSmsTrackerPart2 = new InboundSmsTracker(
+                mContext,
                 mSmsPdu, /* pdu */
                 System.currentTimeMillis(), /* timestamp */
                 -1, /* destPort */
@@ -659,7 +656,8 @@
         doReturn(mSmsHeader).when(mGsmSmsMessage).getUserDataHeader();
 
         doReturn(mInboundSmsTrackerPart1).when(mTelephonyComponentFactory)
-                .makeInboundSmsTracker(nullable(byte[].class), anyLong(), anyInt(), anyBoolean(),
+                .makeInboundSmsTracker(any(Context.class), nullable(byte[].class), anyLong(),
+                        anyInt(), anyBoolean(),
                         nullable(String.class), nullable(String.class), anyInt(), anyInt(),
                         anyInt(), anyBoolean(), nullable(String.class), anyBoolean(), anyInt());
         sendNewSms();
@@ -668,7 +666,8 @@
         assertEquals("IdleState", getCurrentState().getName());
 
         doReturn(mInboundSmsTrackerPart2).when(mTelephonyComponentFactory)
-                .makeInboundSmsTracker(nullable(byte[].class), anyLong(), anyInt(), anyBoolean(),
+                .makeInboundSmsTracker(any(Context.class), nullable(byte[].class), anyLong(),
+                        anyInt(), anyBoolean(),
                         nullable(String.class), nullable(String.class), anyInt(), anyInt(),
                         anyInt(), anyBoolean(), nullable(String.class), anyBoolean(), anyInt());
         sendNewSms();
@@ -697,7 +696,8 @@
         doReturn(mSmsHeader).when(mGsmSmsMessage).getUserDataHeader();
 
         doReturn(mInboundSmsTrackerPart1).when(mTelephonyComponentFactory)
-                .makeInboundSmsTracker(nullable(byte[].class), anyLong(), anyInt(), anyBoolean(),
+                .makeInboundSmsTracker(any(Context.class), nullable(byte[].class), anyLong(),
+                        anyInt(), anyBoolean(),
                         nullable(String.class), nullable(String.class), anyInt(), anyInt(),
                         anyInt(), anyBoolean(), nullable(String.class), anyBoolean(), anyInt());
         sendNewSms();
@@ -711,6 +711,7 @@
         // change seqNumber in part 2 to an invalid value
         int invalidSeqNumber = -1;
         mInboundSmsTrackerPart2 = new InboundSmsTracker(
+                mContext,
                 mSmsPdu, /* pdu */
                 System.currentTimeMillis(), /* timestamp */
                 -1, /* destPort */
@@ -726,7 +727,8 @@
                 mSubId0);
 
         doReturn(mInboundSmsTrackerPart2).when(mTelephonyComponentFactory)
-                .makeInboundSmsTracker(nullable(byte[].class), anyLong(), anyInt(), anyBoolean(),
+                .makeInboundSmsTracker(any(Context.class), nullable(byte[].class), anyLong(),
+                        anyInt(), anyBoolean(),
                         nullable(String.class), nullable(String.class), anyInt(), anyInt(),
                         anyInt(), anyBoolean(), nullable(String.class), anyBoolean(), anyInt());
         sendNewSms();
@@ -750,7 +752,8 @@
         mSmsHeader.concatRef = new SmsHeader.ConcatRef();
         doReturn(mSmsHeader).when(mGsmSmsMessage).getUserDataHeader();
         doReturn(mInboundSmsTrackerPart1).when(mTelephonyComponentFactory)
-                .makeInboundSmsTracker(nullable(byte[].class), anyLong(), anyInt(), anyBoolean(),
+                .makeInboundSmsTracker(any(Context.class), nullable(byte[].class), anyLong(),
+                        anyInt(), anyBoolean(),
                         nullable(String.class), nullable(String.class), anyInt(), anyInt(),
                         anyInt(), anyBoolean(), nullable(String.class), anyBoolean(), anyInt());
 
@@ -760,7 +763,8 @@
         assertEquals("IdleState", getCurrentState().getName());
 
         doReturn(mInboundSmsTrackerPart2).when(mTelephonyComponentFactory)
-                .makeInboundSmsTracker(nullable(byte[].class), anyLong(), anyInt(), anyBoolean(),
+                .makeInboundSmsTracker(any(Context.class), nullable(byte[].class), anyLong(),
+                        anyInt(), anyBoolean(),
                         nullable(String.class), nullable(String.class), anyInt(), anyInt(),
                         anyInt(), anyBoolean(), nullable(String.class), anyBoolean(), anyInt());
         sendNewSms();
@@ -780,6 +784,7 @@
         prepareMultiPartSms(false);
         // only the first SMS is configured with the display originating email address
         mInboundSmsTrackerPart1 = new InboundSmsTracker(
+                mContext,
                 mSmsPdu, /* pdu */
                 System.currentTimeMillis(), /* timestamp */
                 -1, /* destPort */
@@ -797,7 +802,8 @@
         mSmsHeader.concatRef = new SmsHeader.ConcatRef();
         doReturn(mSmsHeader).when(mGsmSmsMessage).getUserDataHeader();
         doReturn(mInboundSmsTrackerPart1).when(mTelephonyComponentFactory)
-                .makeInboundSmsTracker(nullable(byte[].class), anyLong(), anyInt(), anyBoolean(),
+                .makeInboundSmsTracker(any(Context.class), nullable(byte[].class), anyLong(),
+                        anyInt(), anyBoolean(),
                         nullable(String.class), nullable(String.class), anyInt(), anyInt(),
                         anyInt(), anyBoolean(), nullable(String.class), anyBoolean(), anyInt());
 
@@ -807,7 +813,8 @@
         assertEquals("IdleState", getCurrentState().getName());
 
         doReturn(mInboundSmsTrackerPart2).when(mTelephonyComponentFactory)
-                .makeInboundSmsTracker(nullable(byte[].class), anyLong(), anyInt(), anyBoolean(),
+                .makeInboundSmsTracker(any(Context.class), nullable(byte[].class), anyLong(),
+                        anyInt(), anyBoolean(),
                         nullable(String.class), nullable(String.class), anyInt(), anyInt(),
                         anyInt(), anyBoolean(), nullable(String.class), anyBoolean(), anyInt());
         sendNewSms();
@@ -821,6 +828,7 @@
     public void testBroadcastUndeliveredUserLocked() throws Exception {
         replaceInstance(SmsBroadcastUndelivered.class, "instance", null, null);
         doReturn(0).when(mMockInboundSmsTracker).getDestPort();
+        doReturn(2131L).when(mMockInboundSmsTracker).getMessageId();
 
         // add a fake entry to db
         mContentProvider.insert(sRawUri, mMockInboundSmsTracker.getContentValues());
@@ -832,12 +840,14 @@
         SmsBroadcastUndelivered.initialize(mContext, mGsmInboundSmsHandler, mCdmaInboundSmsHandler);
 
         // verify that a broadcast receiver is registered for current user (user == null) based on
-        // implementation in ContextFixture
-        verify(mContext).registerReceiverAsUser(any(BroadcastReceiver.class), eq((UserHandle)null),
-                any(IntentFilter.class), eq((String)null), eq((Handler)null));
+        // implementation in ContextFixture. registerReceiver may be called more than once (for
+        // example by GsmInboundSmsHandler if TEST_MODE is true)
+        verify(mContext, atLeastOnce()).registerReceiver(any(BroadcastReceiver.class),
+                any(IntentFilter.class));
 
         // wait for ScanRawTableThread
         waitForMs(100);
+        processAllMessages();
 
         // verify no broadcasts sent because due to !isUserUnlocked
         verify(mContext, never()).sendBroadcast(any(Intent.class));
@@ -847,6 +857,7 @@
         mContext.sendBroadcast(new Intent(Intent.ACTION_USER_UNLOCKED));
         // wait for ScanRawTableThread
         waitForMs(100);
+        processAllMessages();
 
         verifyDataSmsIntentBroadcasts(1);
     }
@@ -856,6 +867,7 @@
     public void testBroadcastUndeliveredUserUnlocked() throws Exception {
         replaceInstance(SmsBroadcastUndelivered.class, "instance", null, null);
         doReturn(0).when(mMockInboundSmsTracker).getDestPort();
+        doReturn(2131L).when(mMockInboundSmsTracker).getMessageId();
 
         // add a fake entry to db
         mContentProvider.insert(sRawUri, mMockInboundSmsTracker.getContentValues());
@@ -864,6 +876,7 @@
 
         // wait for ScanRawTableThread
         waitForMs(100);
+        processAllMessages();
 
         // user is unlocked; intent should be broadcast right away
         verifyDataSmsIntentBroadcasts(0);
@@ -875,6 +888,7 @@
         replaceInstance(SmsBroadcastUndelivered.class, "instance", null, null);
         SmsBroadcastUndelivered.initialize(mContext, mGsmInboundSmsHandler, mCdmaInboundSmsHandler);
         mInboundSmsTracker = new InboundSmsTracker(
+                mContext,
                 mSmsPdu, /* pdu */
                 System.currentTimeMillis(), /* timestamp */
                 0, /* destPort */
@@ -886,9 +900,10 @@
                 false, /* isClass0 */
                 mSubId0);
         doReturn(mInboundSmsTracker).when(mTelephonyComponentFactory)
-                .makeInboundSmsTracker(nullable(byte[].class), anyLong(), anyInt(), anyBoolean(),
-                anyBoolean(), nullable(String.class), nullable(String.class),
-                nullable(String.class), anyBoolean(), anyInt());
+                .makeInboundSmsTracker(any(Context.class), nullable(byte[].class), anyLong(),
+                        anyInt(), anyBoolean(),
+                        anyBoolean(), nullable(String.class), nullable(String.class),
+                        nullable(String.class), anyBoolean(), anyInt());
 
         //add a fake entry to db
         ContentValues rawSms = new ContentValues();
@@ -899,6 +914,7 @@
         mContext.sendBroadcast(new Intent(Intent.ACTION_USER_UNLOCKED));
         // wait for ScanRawTableThread
         waitForMs(100);
+        processAllMessages();
 
         verify(mContext, times(1)).sendBroadcast(any(Intent.class));
         assertEquals("IdleState", getCurrentState().getName());
@@ -920,12 +936,13 @@
 
         //return InboundSmsTracker objects corresponding to the 2 parts
         doReturn(mInboundSmsTrackerPart1).doReturn(mInboundSmsTrackerPart2).
-                when(mTelephonyComponentFactory).makeInboundSmsTracker(any(Cursor.class),
-                anyBoolean());
+                when(mTelephonyComponentFactory).makeInboundSmsTracker(any(Context.class),
+                any(Cursor.class), anyBoolean());
 
         SmsBroadcastUndelivered.initialize(mContext, mGsmInboundSmsHandler, mCdmaInboundSmsHandler);
         // wait for ScanRawTableThread
-        waitForMs(200);
+        waitForMs(100);
+        processAllMessages();
 
         verifySmsIntentBroadcasts(0);
     }
@@ -941,7 +958,8 @@
 
         SmsBroadcastUndelivered.initialize(mContext, mGsmInboundSmsHandler, mCdmaInboundSmsHandler);
         // wait for ScanRawTableThread
-        waitForMs(200);
+        waitForMs(100);
+        processAllMessages();
 
         verifySmsIntentBroadcasts(0, mSubId0, true);
         verifySmsIntentBroadcasts(2, mSubId1, false);
diff --git a/tests/telephonytests/src/com/android/internal/telephony/gsm/GsmSmsCbTest.java b/tests/telephonytests/src/com/android/internal/telephony/gsm/GsmSmsCbTest.java
deleted file mode 100644
index a4b7ae7..0000000
--- a/tests/telephonytests/src/com/android/internal/telephony/gsm/GsmSmsCbTest.java
+++ /dev/null
@@ -1,789 +0,0 @@
-/*
- * Copyright (C) 2010 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.gsm;
-
-import android.telephony.Rlog;
-import android.telephony.SmsCbEtwsInfo;
-import android.telephony.SmsCbLocation;
-import android.telephony.SmsCbMessage;
-import android.test.AndroidTestCase;
-import android.test.suitebuilder.annotation.SmallTest;
-
-import com.android.internal.telephony.uicc.IccUtils;
-
-import org.junit.Test;
-
-import java.util.Random;
-
-/**
- * Test cases for basic SmsCbMessage operations
- */
-public class GsmSmsCbTest extends AndroidTestCase {
-
-    private static final String TAG = "GsmSmsCbTest";
-
-    private static final SmsCbLocation sTestLocation = new SmsCbLocation("94040", 1234, 5678);
-
-    private SmsCbMessage createFromPdu(byte[] pdu) {
-        try {
-            SmsCbHeader header = new SmsCbHeader(pdu);
-            byte[][] pdus = new byte[1][];
-            pdus[0] = pdu;
-            return GsmSmsCbMessage.createSmsCbMessage(getContext(), header, sTestLocation, pdus);
-        } catch (IllegalArgumentException e) {
-            return null;
-        }
-    }
-
-    private void doTestGeographicalScopeValue(byte[] pdu, byte b, int expectedGs) {
-        pdu[0] = b;
-        SmsCbMessage msg = createFromPdu(pdu);
-
-        assertEquals("Unexpected geographical scope decoded", expectedGs, msg
-                .getGeographicalScope());
-    }
-
-    @Test @SmallTest
-    public void testCreateNullPdu() {
-        SmsCbMessage msg = createFromPdu(null);
-        assertNull("createFromPdu(byte[] with null pdu should return null", msg);
-    }
-
-    @Test @SmallTest
-    public void testCreateTooShortPdu() {
-        byte[] pdu = new byte[4];
-        SmsCbMessage msg = createFromPdu(pdu);
-
-        assertNull("createFromPdu(byte[] with too short pdu should return null", msg);
-    }
-
-    @Test @SmallTest
-    public void testGetGeographicalScope() {
-        byte[] pdu = {
-                (byte)0xC0, (byte)0x00, (byte)0x00, (byte)0x32, (byte)0x40, (byte)0x11, (byte)0x41,
-                (byte)0xD0, (byte)0x71, (byte)0xDA, (byte)0x04, (byte)0x91, (byte)0xCB, (byte)0xE6,
-                (byte)0x70, (byte)0x9D, (byte)0x4D, (byte)0x07, (byte)0x85, (byte)0xD9, (byte)0x70,
-                (byte)0x74, (byte)0x58, (byte)0x5C, (byte)0xA6, (byte)0x83, (byte)0xDA, (byte)0xE5,
-                (byte)0xF9, (byte)0x3C, (byte)0x7C, (byte)0x2E, (byte)0x83, (byte)0xEE, (byte)0x69,
-                (byte)0x3A, (byte)0x1A, (byte)0x34, (byte)0x0E, (byte)0xCB, (byte)0xE5, (byte)0xE9,
-                (byte)0xF0, (byte)0xB9, (byte)0x0C, (byte)0x92, (byte)0x97, (byte)0xE9, (byte)0x75,
-                (byte)0xB9, (byte)0x1B, (byte)0x04, (byte)0x0F, (byte)0x93, (byte)0xC9, (byte)0x69,
-                (byte)0xF7, (byte)0xB9, (byte)0xD1, (byte)0x68, (byte)0x34, (byte)0x1A, (byte)0x8D,
-                (byte)0x46, (byte)0xA3, (byte)0xD1, (byte)0x68, (byte)0x34, (byte)0x1A, (byte)0x8D,
-                (byte)0x46, (byte)0xA3, (byte)0xD1, (byte)0x68, (byte)0x34, (byte)0x1A, (byte)0x8D,
-                (byte)0x46, (byte)0xA3, (byte)0xD1, (byte)0x68, (byte)0x34, (byte)0x1A, (byte)0x8D,
-                (byte)0x46, (byte)0xA3, (byte)0xD1, (byte)0x00
-        };
-
-        doTestGeographicalScopeValue(pdu, (byte)0x00,
-                SmsCbMessage.GEOGRAPHICAL_SCOPE_CELL_WIDE_IMMEDIATE);
-        doTestGeographicalScopeValue(pdu, (byte)0x40, SmsCbMessage.GEOGRAPHICAL_SCOPE_PLMN_WIDE);
-        doTestGeographicalScopeValue(pdu, (byte)0x80, SmsCbMessage.GEOGRAPHICAL_SCOPE_LA_WIDE);
-        doTestGeographicalScopeValue(pdu, (byte)0xC0, SmsCbMessage.GEOGRAPHICAL_SCOPE_CELL_WIDE);
-    }
-
-    @Test @SmallTest
-    public void testGetGeographicalScopeUmts() {
-        byte[] pdu = {
-                (byte)0x01, (byte)0x00, (byte)0x32, (byte)0xC0, (byte)0x00, (byte)0x40,
-
-                (byte)0x01,
-
-                (byte)0x41, (byte)0xD0, (byte)0x71, (byte)0xDA, (byte)0x04, (byte)0x91,
-                (byte)0xCB, (byte)0xE6, (byte)0x70, (byte)0x9D, (byte)0x4D, (byte)0x07,
-                (byte)0x85, (byte)0xD9, (byte)0x70, (byte)0x74, (byte)0x58, (byte)0x5C,
-                (byte)0xA6, (byte)0x83, (byte)0xDA, (byte)0xE5, (byte)0xF9, (byte)0x3C,
-                (byte)0x7C, (byte)0x2E, (byte)0x83, (byte)0xEE, (byte)0x69, (byte)0x3A,
-                (byte)0x1A, (byte)0x34, (byte)0x0E, (byte)0xCB, (byte)0xE5, (byte)0xE9,
-                (byte)0xF0, (byte)0xB9, (byte)0x0C, (byte)0x92, (byte)0x97, (byte)0xE9,
-                (byte)0x75, (byte)0xB9, (byte)0x1B, (byte)0x04, (byte)0x0F, (byte)0x93,
-                (byte)0xC9, (byte)0x69, (byte)0xF7, (byte)0xB9, (byte)0xD1, (byte)0x68,
-                (byte)0x34, (byte)0x1A, (byte)0x8D, (byte)0x46, (byte)0xA3, (byte)0xD1,
-                (byte)0x68, (byte)0x34, (byte)0x1A, (byte)0x8D, (byte)0x46, (byte)0xA3,
-                (byte)0xD1, (byte)0x68, (byte)0x34, (byte)0x1A, (byte)0x8D, (byte)0x46,
-                (byte)0xA3, (byte)0xD1, (byte)0x68, (byte)0x34, (byte)0x1A, (byte)0x8D,
-                (byte)0x46, (byte)0xA3, (byte)0xD1, (byte)0x00,
-
-                (byte)0x34
-        };
-
-        SmsCbMessage msg = createFromPdu(pdu);
-
-        assertEquals("Unexpected geographical scope decoded",
-                SmsCbMessage.GEOGRAPHICAL_SCOPE_CELL_WIDE, msg.getGeographicalScope());
-    }
-
-    @Test @SmallTest
-    public void testGetMessageBody7Bit() {
-        byte[] pdu = {
-                (byte)0xC0, (byte)0x00, (byte)0x00, (byte)0x32, (byte)0x40, (byte)0x11, (byte)0x41,
-                (byte)0xD0, (byte)0x71, (byte)0xDA, (byte)0x04, (byte)0x91, (byte)0xCB, (byte)0xE6,
-                (byte)0x70, (byte)0x9D, (byte)0x4D, (byte)0x07, (byte)0x85, (byte)0xD9, (byte)0x70,
-                (byte)0x74, (byte)0x58, (byte)0x5C, (byte)0xA6, (byte)0x83, (byte)0xDA, (byte)0xE5,
-                (byte)0xF9, (byte)0x3C, (byte)0x7C, (byte)0x2E, (byte)0x83, (byte)0xEE, (byte)0x69,
-                (byte)0x3A, (byte)0x1A, (byte)0x34, (byte)0x0E, (byte)0xCB, (byte)0xE5, (byte)0xE9,
-                (byte)0xF0, (byte)0xB9, (byte)0x0C, (byte)0x92, (byte)0x97, (byte)0xE9, (byte)0x75,
-                (byte)0xB9, (byte)0x1B, (byte)0x04, (byte)0x0F, (byte)0x93, (byte)0xC9, (byte)0x69,
-                (byte)0xF7, (byte)0xB9, (byte)0xD1, (byte)0x68, (byte)0x34, (byte)0x1A, (byte)0x8D,
-                (byte)0x46, (byte)0xA3, (byte)0xD1, (byte)0x68, (byte)0x34, (byte)0x1A, (byte)0x8D,
-                (byte)0x46, (byte)0xA3, (byte)0xD1, (byte)0x68, (byte)0x34, (byte)0x1A, (byte)0x8D,
-                (byte)0x46, (byte)0xA3, (byte)0xD1, (byte)0x68, (byte)0x34, (byte)0x1A, (byte)0x8D,
-                (byte)0x46, (byte)0xA3, (byte)0xD1, (byte)0x00
-        };
-        SmsCbMessage msg = createFromPdu(pdu);
-
-        assertEquals("Unexpected 7-bit string decoded",
-                "A GSM default alphabet message with carriage return padding",
-                msg.getMessageBody());
-    }
-
-    @Test @SmallTest
-    public void testGetMessageBody7BitUmts() {
-        byte[] pdu = {
-                (byte)0x01, (byte)0x00, (byte)0x32, (byte)0xC0, (byte)0x00, (byte)0x40,
-
-                (byte)0x01,
-
-                (byte)0x41, (byte)0xD0, (byte)0x71, (byte)0xDA, (byte)0x04, (byte)0x91,
-                (byte)0xCB, (byte)0xE6, (byte)0x70, (byte)0x9D, (byte)0x4D, (byte)0x07,
-                (byte)0x85, (byte)0xD9, (byte)0x70, (byte)0x74, (byte)0x58, (byte)0x5C,
-                (byte)0xA6, (byte)0x83, (byte)0xDA, (byte)0xE5, (byte)0xF9, (byte)0x3C,
-                (byte)0x7C, (byte)0x2E, (byte)0x83, (byte)0xEE, (byte)0x69, (byte)0x3A,
-                (byte)0x1A, (byte)0x34, (byte)0x0E, (byte)0xCB, (byte)0xE5, (byte)0xE9,
-                (byte)0xF0, (byte)0xB9, (byte)0x0C, (byte)0x92, (byte)0x97, (byte)0xE9,
-                (byte)0x75, (byte)0xB9, (byte)0x1B, (byte)0x04, (byte)0x0F, (byte)0x93,
-                (byte)0xC9, (byte)0x69, (byte)0xF7, (byte)0xB9, (byte)0xD1, (byte)0x68,
-                (byte)0x34, (byte)0x1A, (byte)0x8D, (byte)0x46, (byte)0xA3, (byte)0xD1,
-                (byte)0x68, (byte)0x34, (byte)0x1A, (byte)0x8D, (byte)0x46, (byte)0xA3,
-                (byte)0xD1, (byte)0x68, (byte)0x34, (byte)0x1A, (byte)0x8D, (byte)0x46,
-                (byte)0xA3, (byte)0xD1, (byte)0x68, (byte)0x34, (byte)0x1A, (byte)0x8D,
-                (byte)0x46, (byte)0xA3, (byte)0xD1, (byte)0x00,
-
-                (byte)0x34
-        };
-        SmsCbMessage msg = createFromPdu(pdu);
-
-        assertEquals("Unexpected 7-bit string decoded",
-                "A GSM default alphabet message with carriage return padding",
-                msg.getMessageBody());
-    }
-
-    @Test @SmallTest
-    public void testGetMessageBody7BitMultipageUmts() {
-        byte[] pdu = {
-                (byte)0x01, (byte)0x00, (byte)0x01, (byte)0xC0, (byte)0x00, (byte)0x40,
-
-                (byte)0x02,
-
-                (byte)0xC6, (byte)0xB4, (byte)0x7C, (byte)0x4E, (byte)0x07, (byte)0xC1,
-                (byte)0xC3, (byte)0xE7, (byte)0xF2, (byte)0xAA, (byte)0xD1, (byte)0x68,
-                (byte)0x34, (byte)0x1A, (byte)0x8D, (byte)0x46, (byte)0xA3, (byte)0xD1,
-                (byte)0x68, (byte)0x34, (byte)0x1A, (byte)0x8D, (byte)0x46, (byte)0xA3,
-                (byte)0xD1, (byte)0x68, (byte)0x34, (byte)0x1A, (byte)0x8D, (byte)0x46,
-                (byte)0xA3, (byte)0xD1, (byte)0x68, (byte)0x34, (byte)0x1A, (byte)0x8D,
-                (byte)0x46, (byte)0xA3, (byte)0xD1, (byte)0x68, (byte)0x34, (byte)0x1A,
-                (byte)0x8D, (byte)0x46, (byte)0xA3, (byte)0xD1, (byte)0x68, (byte)0x34,
-                (byte)0x1A, (byte)0x8D, (byte)0x46, (byte)0xA3, (byte)0xD1, (byte)0x68,
-                (byte)0x34, (byte)0x1A, (byte)0x8D, (byte)0x46, (byte)0xA3, (byte)0xD1,
-                (byte)0x68, (byte)0x34, (byte)0x1A, (byte)0x8D, (byte)0x46, (byte)0xA3,
-                (byte)0xD1, (byte)0x68, (byte)0x34, (byte)0x1A, (byte)0x8D, (byte)0x46,
-                (byte)0xA3, (byte)0xD1, (byte)0x68, (byte)0x34, (byte)0x1A, (byte)0x8D,
-                (byte)0x46, (byte)0xA3, (byte)0xD1, (byte)0x00,
-
-                (byte)0x0A,
-
-                (byte)0xD3, (byte)0xF2, (byte)0xF8, (byte)0xED, (byte)0x26, (byte)0x83,
-                (byte)0xE0, (byte)0xE1, (byte)0x73, (byte)0xB9, (byte)0xD1, (byte)0x68,
-                (byte)0x34, (byte)0x1A, (byte)0x8D, (byte)0x46, (byte)0xA3, (byte)0xD1,
-                (byte)0x68, (byte)0x34, (byte)0x1A, (byte)0x8D, (byte)0x46, (byte)0xA3,
-                (byte)0xD1, (byte)0x68, (byte)0x34, (byte)0x1A, (byte)0x8D, (byte)0x46,
-                (byte)0xA3, (byte)0xD1, (byte)0x68, (byte)0x34, (byte)0x1A, (byte)0x8D,
-                (byte)0x46, (byte)0xA3, (byte)0xD1, (byte)0x68, (byte)0x34, (byte)0x1A,
-                (byte)0x8D, (byte)0x46, (byte)0xA3, (byte)0xD1, (byte)0x68, (byte)0x34,
-                (byte)0x1A, (byte)0x8D, (byte)0x46, (byte)0xA3, (byte)0xD1, (byte)0x68,
-                (byte)0x34, (byte)0x1A, (byte)0x8D, (byte)0x46, (byte)0xA3, (byte)0xD1,
-                (byte)0x68, (byte)0x34, (byte)0x1A, (byte)0x8D, (byte)0x46, (byte)0xA3,
-                (byte)0xD1, (byte)0x68, (byte)0x34, (byte)0x1A, (byte)0x8D, (byte)0x46,
-                (byte)0xA3, (byte)0xD1, (byte)0x68, (byte)0x34, (byte)0x1A, (byte)0x8D,
-                (byte)0x46, (byte)0xA3, (byte)0xD1, (byte)0x00,
-
-                (byte)0x0A
-        };
-        SmsCbMessage msg = createFromPdu(pdu);
-
-        assertEquals("Unexpected multipage 7-bit string decoded",
-                "First page+Second page",
-                msg.getMessageBody());
-    }
-
-    @Test @SmallTest
-    public void testGetMessageBody7BitFull() {
-        byte[] pdu = {
-                (byte)0xC0, (byte)0x00, (byte)0x00, (byte)0x32, (byte)0x40, (byte)0x11, (byte)0x41,
-                (byte)0xD0, (byte)0x71, (byte)0xDA, (byte)0x04, (byte)0x91, (byte)0xCB, (byte)0xE6,
-                (byte)0x70, (byte)0x9D, (byte)0x4D, (byte)0x07, (byte)0x85, (byte)0xD9, (byte)0x70,
-                (byte)0x74, (byte)0x58, (byte)0x5C, (byte)0xA6, (byte)0x83, (byte)0xDA, (byte)0xE5,
-                (byte)0xF9, (byte)0x3C, (byte)0x7C, (byte)0x2E, (byte)0x83, (byte)0xC4, (byte)0xE5,
-                (byte)0xB4, (byte)0xFB, (byte)0x0C, (byte)0x2A, (byte)0xE3, (byte)0xC3, (byte)0x63,
-                (byte)0x3A, (byte)0x3B, (byte)0x0F, (byte)0xCA, (byte)0xCD, (byte)0x40, (byte)0x63,
-                (byte)0x74, (byte)0x58, (byte)0x1E, (byte)0x1E, (byte)0xD3, (byte)0xCB, (byte)0xF2,
-                (byte)0x39, (byte)0x88, (byte)0xFD, (byte)0x76, (byte)0x9F, (byte)0x59, (byte)0xA0,
-                (byte)0x76, (byte)0x39, (byte)0xEC, (byte)0x4E, (byte)0xBB, (byte)0xCF, (byte)0x20,
-                (byte)0x3A, (byte)0xBA, (byte)0x2C, (byte)0x2F, (byte)0x83, (byte)0xD2, (byte)0x73,
-                (byte)0x90, (byte)0xFB, (byte)0x0D, (byte)0x82, (byte)0x87, (byte)0xC9, (byte)0xE4,
-                (byte)0xB4, (byte)0xFB, (byte)0x1C, (byte)0x02
-        };
-        SmsCbMessage msg = createFromPdu(pdu);
-
-        assertEquals(
-                "Unexpected 7-bit string decoded",
-                "A GSM default alphabet message being exactly 93 characters long, " +
-                "meaning there is no padding!",
-                msg.getMessageBody());
-    }
-
-    @Test @SmallTest
-    public void testGetMessageBody7BitFullUmts() {
-        byte[] pdu = {
-                (byte)0x01, (byte)0x00, (byte)0x32, (byte)0xC0, (byte)0x00, (byte)0x40,
-
-                (byte)0x01,
-
-                (byte)0x41, (byte)0xD0, (byte)0x71, (byte)0xDA, (byte)0x04, (byte)0x91,
-                (byte)0xCB, (byte)0xE6, (byte)0x70, (byte)0x9D, (byte)0x4D, (byte)0x07,
-                (byte)0x85, (byte)0xD9, (byte)0x70, (byte)0x74, (byte)0x58, (byte)0x5C,
-                (byte)0xA6, (byte)0x83, (byte)0xDA, (byte)0xE5, (byte)0xF9, (byte)0x3C,
-                (byte)0x7C, (byte)0x2E, (byte)0x83, (byte)0xC4, (byte)0xE5, (byte)0xB4,
-                (byte)0xFB, (byte)0x0C, (byte)0x2A, (byte)0xE3, (byte)0xC3, (byte)0x63,
-                (byte)0x3A, (byte)0x3B, (byte)0x0F, (byte)0xCA, (byte)0xCD, (byte)0x40,
-                (byte)0x63, (byte)0x74, (byte)0x58, (byte)0x1E, (byte)0x1E, (byte)0xD3,
-                (byte)0xCB, (byte)0xF2, (byte)0x39, (byte)0x88, (byte)0xFD, (byte)0x76,
-                (byte)0x9F, (byte)0x59, (byte)0xA0, (byte)0x76, (byte)0x39, (byte)0xEC,
-                (byte)0x4E, (byte)0xBB, (byte)0xCF, (byte)0x20, (byte)0x3A, (byte)0xBA,
-                (byte)0x2C, (byte)0x2F, (byte)0x83, (byte)0xD2, (byte)0x73, (byte)0x90,
-                (byte)0xFB, (byte)0x0D, (byte)0x82, (byte)0x87, (byte)0xC9, (byte)0xE4,
-                (byte)0xB4, (byte)0xFB, (byte)0x1C, (byte)0x02,
-
-                (byte)0x52
-        };
-        SmsCbMessage msg = createFromPdu(pdu);
-
-        assertEquals(
-                "Unexpected 7-bit string decoded",
-                "A GSM default alphabet message being exactly 93 characters long, " +
-                "meaning there is no padding!",
-                msg.getMessageBody());
-    }
-
-    @Test @SmallTest
-    public void testGetMessageBody7BitWithLanguage() {
-        byte[] pdu = {
-                (byte)0xC0, (byte)0x00, (byte)0x00, (byte)0x32, (byte)0x04, (byte)0x11, (byte)0x41,
-                (byte)0xD0, (byte)0x71, (byte)0xDA, (byte)0x04, (byte)0x91, (byte)0xCB, (byte)0xE6,
-                (byte)0x70, (byte)0x9D, (byte)0x4D, (byte)0x07, (byte)0x85, (byte)0xD9, (byte)0x70,
-                (byte)0x74, (byte)0x58, (byte)0x5C, (byte)0xA6, (byte)0x83, (byte)0xDA, (byte)0xE5,
-                (byte)0xF9, (byte)0x3C, (byte)0x7C, (byte)0x2E, (byte)0x83, (byte)0xEE, (byte)0x69,
-                (byte)0x3A, (byte)0x1A, (byte)0x34, (byte)0x0E, (byte)0xCB, (byte)0xE5, (byte)0xE9,
-                (byte)0xF0, (byte)0xB9, (byte)0x0C, (byte)0x92, (byte)0x97, (byte)0xE9, (byte)0x75,
-                (byte)0xB9, (byte)0x1B, (byte)0x04, (byte)0x0F, (byte)0x93, (byte)0xC9, (byte)0x69,
-                (byte)0xF7, (byte)0xB9, (byte)0xD1, (byte)0x68, (byte)0x34, (byte)0x1A, (byte)0x8D,
-                (byte)0x46, (byte)0xA3, (byte)0xD1, (byte)0x68, (byte)0x34, (byte)0x1A, (byte)0x8D,
-                (byte)0x46, (byte)0xA3, (byte)0xD1, (byte)0x68, (byte)0x34, (byte)0x1A, (byte)0x8D,
-                (byte)0x46, (byte)0xA3, (byte)0xD1, (byte)0x68, (byte)0x34, (byte)0x1A, (byte)0x8D,
-                (byte)0x46, (byte)0xA3, (byte)0xD1, (byte)0x00
-        };
-        SmsCbMessage msg = createFromPdu(pdu);
-
-        assertEquals("Unexpected 7-bit string decoded",
-                "A GSM default alphabet message with carriage return padding",
-                msg.getMessageBody());
-
-        assertEquals("Unexpected language indicator decoded", "es", msg.getLanguageCode());
-    }
-
-    @Test @SmallTest
-    public void testGetMessageBody7BitWithLanguageInBody() {
-        byte[] pdu = {
-                (byte)0xC0, (byte)0x00, (byte)0x00, (byte)0x32, (byte)0x10, (byte)0x11, (byte)0x73,
-                (byte)0x7B, (byte)0x23, (byte)0x08, (byte)0x3A, (byte)0x4E, (byte)0x9B, (byte)0x20,
-                (byte)0x72, (byte)0xD9, (byte)0x1C, (byte)0xAE, (byte)0xB3, (byte)0xE9, (byte)0xA0,
-                (byte)0x30, (byte)0x1B, (byte)0x8E, (byte)0x0E, (byte)0x8B, (byte)0xCB, (byte)0x74,
-                (byte)0x50, (byte)0xBB, (byte)0x3C, (byte)0x9F, (byte)0x87, (byte)0xCF, (byte)0x65,
-                (byte)0xD0, (byte)0x3D, (byte)0x4D, (byte)0x47, (byte)0x83, (byte)0xC6, (byte)0x61,
-                (byte)0xB9, (byte)0x3C, (byte)0x1D, (byte)0x3E, (byte)0x97, (byte)0x41, (byte)0xF2,
-                (byte)0x32, (byte)0xBD, (byte)0x2E, (byte)0x77, (byte)0x83, (byte)0xE0, (byte)0x61,
-                (byte)0x32, (byte)0x39, (byte)0xED, (byte)0x3E, (byte)0x37, (byte)0x1A, (byte)0x8D,
-                (byte)0x46, (byte)0xA3, (byte)0xD1, (byte)0x68, (byte)0x34, (byte)0x1A, (byte)0x8D,
-                (byte)0x46, (byte)0xA3, (byte)0xD1, (byte)0x68, (byte)0x34, (byte)0x1A, (byte)0x8D,
-                (byte)0x46, (byte)0xA3, (byte)0xD1, (byte)0x68, (byte)0x34, (byte)0x1A, (byte)0x8D,
-                (byte)0x46, (byte)0xA3, (byte)0xD1, (byte)0x00
-        };
-        SmsCbMessage msg = createFromPdu(pdu);
-
-        assertEquals("Unexpected 7-bit string decoded",
-                "A GSM default alphabet message with carriage return padding",
-                msg.getMessageBody());
-
-        assertEquals("Unexpected language indicator decoded", "sv", msg.getLanguageCode());
-    }
-
-    @Test @SmallTest
-    public void testGetMessageBody7BitWithLanguageInBodyUmts() {
-        byte[] pdu = {
-                (byte)0x01, (byte)0x00, (byte)0x32, (byte)0xC0, (byte)0x00, (byte)0x10,
-
-                (byte)0x01,
-
-                (byte)0x73, (byte)0x7B, (byte)0x23, (byte)0x08, (byte)0x3A, (byte)0x4E,
-                (byte)0x9B, (byte)0x20, (byte)0x72, (byte)0xD9, (byte)0x1C, (byte)0xAE,
-                (byte)0xB3, (byte)0xE9, (byte)0xA0, (byte)0x30, (byte)0x1B, (byte)0x8E,
-                (byte)0x0E, (byte)0x8B, (byte)0xCB, (byte)0x74, (byte)0x50, (byte)0xBB,
-                (byte)0x3C, (byte)0x9F, (byte)0x87, (byte)0xCF, (byte)0x65, (byte)0xD0,
-                (byte)0x3D, (byte)0x4D, (byte)0x47, (byte)0x83, (byte)0xC6, (byte)0x61,
-                (byte)0xB9, (byte)0x3C, (byte)0x1D, (byte)0x3E, (byte)0x97, (byte)0x41,
-                (byte)0xF2, (byte)0x32, (byte)0xBD, (byte)0x2E, (byte)0x77, (byte)0x83,
-                (byte)0xE0, (byte)0x61, (byte)0x32, (byte)0x39, (byte)0xED, (byte)0x3E,
-                (byte)0x37, (byte)0x1A, (byte)0x8D, (byte)0x46, (byte)0xA3, (byte)0xD1,
-                (byte)0x68, (byte)0x34, (byte)0x1A, (byte)0x8D, (byte)0x46, (byte)0xA3,
-                (byte)0xD1, (byte)0x68, (byte)0x34, (byte)0x1A, (byte)0x8D, (byte)0x46,
-                (byte)0xA3, (byte)0xD1, (byte)0x68, (byte)0x34, (byte)0x1A, (byte)0x8D,
-                (byte)0x46, (byte)0xA3, (byte)0xD1, (byte)0x00,
-
-                (byte)0x37
-        };
-        SmsCbMessage msg = createFromPdu(pdu);
-
-        assertEquals("Unexpected 7-bit string decoded",
-                "A GSM default alphabet message with carriage return padding",
-                msg.getMessageBody());
-
-        assertEquals("Unexpected language indicator decoded", "sv", msg.getLanguageCode());
-    }
-
-    @Test @SmallTest
-    public void testGetMessageBody8Bit() {
-        byte[] pdu = {
-                (byte)0xC0, (byte)0x00, (byte)0x00, (byte)0x32, (byte)0x44, (byte)0x11, (byte)0x41,
-                (byte)0x42, (byte)0x43, (byte)0x44, (byte)0x45, (byte)0x46, (byte)0x47, (byte)0x41,
-                (byte)0x42, (byte)0x43, (byte)0x44, (byte)0x45, (byte)0x46, (byte)0x47, (byte)0x41,
-                (byte)0x42, (byte)0x43, (byte)0x44, (byte)0x45, (byte)0x46, (byte)0x47, (byte)0x41,
-                (byte)0x42, (byte)0x43, (byte)0x44, (byte)0x45, (byte)0x46, (byte)0x47, (byte)0x41,
-                (byte)0x42, (byte)0x43, (byte)0x44, (byte)0x45, (byte)0x46, (byte)0x47, (byte)0x41,
-                (byte)0x42, (byte)0x43, (byte)0x44, (byte)0x45, (byte)0x46, (byte)0x47, (byte)0x41,
-                (byte)0x42, (byte)0x43, (byte)0x44, (byte)0x45, (byte)0x46, (byte)0x47, (byte)0x41,
-                (byte)0x42, (byte)0x43, (byte)0x44, (byte)0x45, (byte)0x46, (byte)0x47, (byte)0x41,
-                (byte)0x42, (byte)0x43, (byte)0x44, (byte)0x45, (byte)0x46, (byte)0x47, (byte)0x41,
-                (byte)0x42, (byte)0x43, (byte)0x44, (byte)0x45, (byte)0x46, (byte)0x47, (byte)0x41,
-                (byte)0x42, (byte)0x43, (byte)0x44, (byte)0x45, (byte)0x46, (byte)0x47, (byte)0x41,
-                (byte)0x42, (byte)0x43, (byte)0x44, (byte)0x45
-        };
-        SmsCbMessage msg = createFromPdu(pdu);
-
-        assertEquals("8-bit message body should be empty", "", msg.getMessageBody());
-    }
-
-    @Test @SmallTest
-    public void testGetMessageBodyUcs2() {
-        byte[] pdu = {
-                (byte)0xC0, (byte)0x00, (byte)0x00, (byte)0x32, (byte)0x48, (byte)0x11, (byte)0x00,
-                (byte)0x41, (byte)0x00, (byte)0x20, (byte)0x00, (byte)0x55, (byte)0x00, (byte)0x43,
-                (byte)0x00, (byte)0x53, (byte)0x00, (byte)0x32, (byte)0x00, (byte)0x20, (byte)0x00,
-                (byte)0x6D, (byte)0x00, (byte)0x65, (byte)0x00, (byte)0x73, (byte)0x00, (byte)0x73,
-                (byte)0x00, (byte)0x61, (byte)0x00, (byte)0x67, (byte)0x00, (byte)0x65, (byte)0x00,
-                (byte)0x20, (byte)0x00, (byte)0x63, (byte)0x00, (byte)0x6F, (byte)0x00, (byte)0x6E,
-                (byte)0x00, (byte)0x74, (byte)0x00, (byte)0x61, (byte)0x00, (byte)0x69, (byte)0x00,
-                (byte)0x6E, (byte)0x00, (byte)0x69, (byte)0x00, (byte)0x6E, (byte)0x00, (byte)0x67,
-                (byte)0x00, (byte)0x20, (byte)0x00, (byte)0x61, (byte)0x00, (byte)0x20, (byte)0x04,
-                (byte)0x34, (byte)0x00, (byte)0x20, (byte)0x00, (byte)0x63, (byte)0x00, (byte)0x68,
-                (byte)0x00, (byte)0x61, (byte)0x00, (byte)0x72, (byte)0x00, (byte)0x61, (byte)0x00,
-                (byte)0x63, (byte)0x00, (byte)0x74, (byte)0x00, (byte)0x65, (byte)0x00, (byte)0x72,
-                (byte)0x00, (byte)0x0D, (byte)0x00, (byte)0x0D
-        };
-        SmsCbMessage msg = createFromPdu(pdu);
-
-        assertEquals("Unexpected 7-bit string decoded",
-                "A UCS2 message containing a \u0434 character", msg.getMessageBody());
-    }
-
-    @Test @SmallTest
-    public void testGetMessageBodyUcs2Umts() {
-        byte[] pdu = {
-                (byte)0x01, (byte)0x00, (byte)0x32, (byte)0xC0, (byte)0x00, (byte)0x48,
-
-                (byte)0x01,
-
-                (byte)0x00, (byte)0x41, (byte)0x00, (byte)0x20, (byte)0x00, (byte)0x55,
-                (byte)0x00, (byte)0x43, (byte)0x00, (byte)0x53, (byte)0x00, (byte)0x32,
-                (byte)0x00, (byte)0x20, (byte)0x00, (byte)0x6D, (byte)0x00, (byte)0x65,
-                (byte)0x00, (byte)0x73, (byte)0x00, (byte)0x73, (byte)0x00, (byte)0x61,
-                (byte)0x00, (byte)0x67, (byte)0x00, (byte)0x65, (byte)0x00, (byte)0x20,
-                (byte)0x00, (byte)0x63, (byte)0x00, (byte)0x6F, (byte)0x00, (byte)0x6E,
-                (byte)0x00, (byte)0x74, (byte)0x00, (byte)0x61, (byte)0x00, (byte)0x69,
-                (byte)0x00, (byte)0x6E, (byte)0x00, (byte)0x69, (byte)0x00, (byte)0x6E,
-                (byte)0x00, (byte)0x67, (byte)0x00, (byte)0x20, (byte)0x00, (byte)0x61,
-                (byte)0x00, (byte)0x20, (byte)0x04, (byte)0x34, (byte)0x00, (byte)0x20,
-                (byte)0x00, (byte)0x63, (byte)0x00, (byte)0x68, (byte)0x00, (byte)0x61,
-                (byte)0x00, (byte)0x72, (byte)0x00, (byte)0x61, (byte)0x00, (byte)0x63,
-                (byte)0x00, (byte)0x74, (byte)0x00, (byte)0x65, (byte)0x00, (byte)0x72,
-                (byte)0x00, (byte)0x0D, (byte)0x00, (byte)0x0D,
-
-                (byte)0x4E
-        };
-        SmsCbMessage msg = createFromPdu(pdu);
-
-        assertEquals("Unexpected 7-bit string decoded",
-                "A UCS2 message containing a \u0434 character", msg.getMessageBody());
-    }
-
-    @Test @SmallTest
-    public void testGetMessageBodyUcs2MultipageUmts() {
-        byte[] pdu = {
-                (byte)0x01, (byte)0x00, (byte)0x32, (byte)0xC0, (byte)0x00, (byte)0x48,
-
-                (byte)0x02,
-
-                (byte)0x00, (byte)0x41, (byte)0x00, (byte)0x41, (byte)0x00, (byte)0x41,
-                (byte)0x00, (byte)0x0D, (byte)0x0D, (byte)0x0D, (byte)0x0D, (byte)0x0D,
-                (byte)0x0D, (byte)0x0D, (byte)0x0D, (byte)0x0D, (byte)0x0D, (byte)0x0D,
-                (byte)0x0D, (byte)0x0D, (byte)0x0D, (byte)0x0D, (byte)0x0D, (byte)0x0D,
-                (byte)0x0D, (byte)0x0D, (byte)0x0D, (byte)0x0D, (byte)0x0D, (byte)0x0D,
-                (byte)0x0D, (byte)0x0D, (byte)0x0D, (byte)0x0D, (byte)0x0D, (byte)0x0D,
-                (byte)0x0D, (byte)0x0D, (byte)0x0D, (byte)0x0D, (byte)0x0D, (byte)0x0D,
-                (byte)0x0D, (byte)0x0D, (byte)0x0D, (byte)0x0D, (byte)0x0D, (byte)0x0D,
-                (byte)0x0D, (byte)0x0D, (byte)0x0D, (byte)0x0D, (byte)0x0D, (byte)0x0D,
-                (byte)0x0D, (byte)0x0D, (byte)0x0D, (byte)0x0D, (byte)0x0D, (byte)0x0D,
-                (byte)0x0D, (byte)0x0D, (byte)0x0D, (byte)0x0D, (byte)0x0D, (byte)0x0D,
-                (byte)0x0D, (byte)0x0D, (byte)0x0D, (byte)0x0D, (byte)0x0D, (byte)0x0D,
-                (byte)0x0D, (byte)0x0D, (byte)0x0D, (byte)0x0D, (byte)0x0D, (byte)0x0D,
-                (byte)0x0D, (byte)0x0D, (byte)0x0D, (byte)0x0D,
-
-                (byte)0x06,
-
-                (byte)0x00, (byte)0x42, (byte)0x00, (byte)0x42, (byte)0x00, (byte)0x42,
-                (byte)0x00, (byte)0x0D, (byte)0x0D, (byte)0x0D, (byte)0x0D, (byte)0x0D,
-                (byte)0x0D, (byte)0x0D, (byte)0x0D, (byte)0x0D, (byte)0x0D, (byte)0x0D,
-                (byte)0x0D, (byte)0x0D, (byte)0x0D, (byte)0x0D, (byte)0x0D, (byte)0x0D,
-                (byte)0x0D, (byte)0x0D, (byte)0x0D, (byte)0x0D, (byte)0x0D, (byte)0x0D,
-                (byte)0x0D, (byte)0x0D, (byte)0x0D, (byte)0x0D, (byte)0x0D, (byte)0x0D,
-                (byte)0x0D, (byte)0x0D, (byte)0x0D, (byte)0x0D, (byte)0x0D, (byte)0x0D,
-                (byte)0x0D, (byte)0x0D, (byte)0x0D, (byte)0x0D, (byte)0x0D, (byte)0x0D,
-                (byte)0x0D, (byte)0x0D, (byte)0x0D, (byte)0x0D, (byte)0x0D, (byte)0x0D,
-                (byte)0x0D, (byte)0x0D, (byte)0x0D, (byte)0x0D, (byte)0x0D, (byte)0x0D,
-                (byte)0x0D, (byte)0x0D, (byte)0x0D, (byte)0x0D, (byte)0x0D, (byte)0x0D,
-                (byte)0x0D, (byte)0x0D, (byte)0x0D, (byte)0x0D, (byte)0x0D, (byte)0x0D,
-                (byte)0x0D, (byte)0x0D, (byte)0x0D, (byte)0x0D, (byte)0x0D, (byte)0x0D,
-                (byte)0x0D, (byte)0x0D, (byte)0x0D, (byte)0x0D,
-
-                (byte)0x06
-        };
-        SmsCbMessage msg = createFromPdu(pdu);
-
-        assertEquals("Unexpected multipage UCS2 string decoded",
-                "AAABBB", msg.getMessageBody());
-    }
-
-    @Test @SmallTest
-    public void testGetMessageBodyUcs2WithLanguageInBody() {
-        byte[] pdu = {
-                (byte)0xC0, (byte)0x00, (byte)0x00, (byte)0x32, (byte)0x11, (byte)0x11, (byte)0x78,
-                (byte)0x3C, (byte)0x00, (byte)0x41, (byte)0x00, (byte)0x20, (byte)0x00, (byte)0x55,
-                (byte)0x00, (byte)0x43, (byte)0x00, (byte)0x53, (byte)0x00, (byte)0x32, (byte)0x00,
-                (byte)0x20, (byte)0x00, (byte)0x6D, (byte)0x00, (byte)0x65, (byte)0x00, (byte)0x73,
-                (byte)0x00, (byte)0x73, (byte)0x00, (byte)0x61, (byte)0x00, (byte)0x67, (byte)0x00,
-                (byte)0x65, (byte)0x00, (byte)0x20, (byte)0x00, (byte)0x63, (byte)0x00, (byte)0x6F,
-                (byte)0x00, (byte)0x6E, (byte)0x00, (byte)0x74, (byte)0x00, (byte)0x61, (byte)0x00,
-                (byte)0x69, (byte)0x00, (byte)0x6E, (byte)0x00, (byte)0x69, (byte)0x00, (byte)0x6E,
-                (byte)0x00, (byte)0x67, (byte)0x00, (byte)0x20, (byte)0x00, (byte)0x61, (byte)0x00,
-                (byte)0x20, (byte)0x04, (byte)0x34, (byte)0x00, (byte)0x20, (byte)0x00, (byte)0x63,
-                (byte)0x00, (byte)0x68, (byte)0x00, (byte)0x61, (byte)0x00, (byte)0x72, (byte)0x00,
-                (byte)0x61, (byte)0x00, (byte)0x63, (byte)0x00, (byte)0x74, (byte)0x00, (byte)0x65,
-                (byte)0x00, (byte)0x72, (byte)0x00, (byte)0x0D
-        };
-        SmsCbMessage msg = createFromPdu(pdu);
-
-        assertEquals("Unexpected 7-bit string decoded",
-                "A UCS2 message containing a \u0434 character", msg.getMessageBody());
-
-        assertEquals("Unexpected language indicator decoded", "xx", msg.getLanguageCode());
-    }
-
-    @Test @SmallTest
-    public void testGetMessageBodyUcs2WithLanguageInBodyUmts() {
-        byte[] pdu = {
-                (byte)0x01, (byte)0x00, (byte)0x32, (byte)0xC0, (byte)0x00, (byte)0x11,
-
-                (byte)0x01,
-
-                (byte)0x78, (byte)0x3C, (byte)0x00, (byte)0x41, (byte)0x00, (byte)0x20,
-                (byte)0x00, (byte)0x55, (byte)0x00, (byte)0x43, (byte)0x00, (byte)0x53,
-                (byte)0x00, (byte)0x32, (byte)0x00, (byte)0x20, (byte)0x00, (byte)0x6D,
-                (byte)0x00, (byte)0x65, (byte)0x00, (byte)0x73, (byte)0x00, (byte)0x73,
-                (byte)0x00, (byte)0x61, (byte)0x00, (byte)0x67, (byte)0x00, (byte)0x65,
-                (byte)0x00, (byte)0x20, (byte)0x00, (byte)0x63, (byte)0x00, (byte)0x6F,
-                (byte)0x00, (byte)0x6E, (byte)0x00, (byte)0x74, (byte)0x00, (byte)0x61,
-                (byte)0x00, (byte)0x69, (byte)0x00, (byte)0x6E, (byte)0x00, (byte)0x69,
-                (byte)0x00, (byte)0x6E, (byte)0x00, (byte)0x67, (byte)0x00, (byte)0x20,
-                (byte)0x00, (byte)0x61, (byte)0x00, (byte)0x20, (byte)0x04, (byte)0x34,
-                (byte)0x00, (byte)0x20, (byte)0x00, (byte)0x63, (byte)0x00, (byte)0x68,
-                (byte)0x00, (byte)0x61, (byte)0x00, (byte)0x72, (byte)0x00, (byte)0x61,
-                (byte)0x00, (byte)0x63, (byte)0x00, (byte)0x74, (byte)0x00, (byte)0x65,
-                (byte)0x00, (byte)0x72, (byte)0x00, (byte)0x0D,
-
-                (byte)0x50
-        };
-        SmsCbMessage msg = createFromPdu(pdu);
-
-        assertEquals("Unexpected 7-bit string decoded",
-                "A UCS2 message containing a \u0434 character", msg.getMessageBody());
-
-        assertEquals("Unexpected language indicator decoded", "xx", msg.getLanguageCode());
-    }
-
-    @Test @SmallTest
-    public void testGetMessageIdentifier() {
-        byte[] pdu = {
-                (byte)0xC0, (byte)0x00, (byte)0x30, (byte)0x39, (byte)0x40, (byte)0x11, (byte)0x41,
-                (byte)0xD0, (byte)0x71, (byte)0xDA, (byte)0x04, (byte)0x91, (byte)0xCB, (byte)0xE6,
-                (byte)0x70, (byte)0x9D, (byte)0x4D, (byte)0x07, (byte)0x85, (byte)0xD9, (byte)0x70,
-                (byte)0x74, (byte)0x58, (byte)0x5C, (byte)0xA6, (byte)0x83, (byte)0xDA, (byte)0xE5,
-                (byte)0xF9, (byte)0x3C, (byte)0x7C, (byte)0x2E, (byte)0x83, (byte)0xEE, (byte)0x69,
-                (byte)0x3A, (byte)0x1A, (byte)0x34, (byte)0x0E, (byte)0xCB, (byte)0xE5, (byte)0xE9,
-                (byte)0xF0, (byte)0xB9, (byte)0x0C, (byte)0x92, (byte)0x97, (byte)0xE9, (byte)0x75,
-                (byte)0xB9, (byte)0x1B, (byte)0x04, (byte)0x0F, (byte)0x93, (byte)0xC9, (byte)0x69,
-                (byte)0xF7, (byte)0xB9, (byte)0xD1, (byte)0x68, (byte)0x34, (byte)0x1A, (byte)0x8D,
-                (byte)0x46, (byte)0xA3, (byte)0xD1, (byte)0x68, (byte)0x34, (byte)0x1A, (byte)0x8D,
-                (byte)0x46, (byte)0xA3, (byte)0xD1, (byte)0x68, (byte)0x34, (byte)0x1A, (byte)0x8D,
-                (byte)0x46, (byte)0xA3, (byte)0xD1, (byte)0x68, (byte)0x34, (byte)0x1A, (byte)0x8D,
-                (byte)0x46, (byte)0xA3, (byte)0xD1, (byte)0x00
-        };
-
-        SmsCbMessage msg = createFromPdu(pdu);
-
-        assertEquals("Unexpected message identifier decoded", 12345, msg.getServiceCategory());
-    }
-
-    @Test @SmallTest
-    public void testGetMessageIdentifierUmts() {
-        byte[] pdu = {
-                (byte)0x01, (byte)0x30, (byte)0x39, (byte)0x2A, (byte)0xA5, (byte)0x40,
-
-                (byte)0x01,
-
-                (byte)0x41, (byte)0xD0, (byte)0x71, (byte)0xDA, (byte)0x04, (byte)0x91,
-                (byte)0xCB, (byte)0xE6, (byte)0x70, (byte)0x9D, (byte)0x4D, (byte)0x07,
-                (byte)0x85, (byte)0xD9, (byte)0x70, (byte)0x74, (byte)0x58, (byte)0x5C,
-                (byte)0xA6, (byte)0x83, (byte)0xDA, (byte)0xE5, (byte)0xF9, (byte)0x3C,
-                (byte)0x7C, (byte)0x2E, (byte)0x83, (byte)0xEE, (byte)0x69, (byte)0x3A,
-                (byte)0x1A, (byte)0x34, (byte)0x0E, (byte)0xCB, (byte)0xE5, (byte)0xE9,
-                (byte)0xF0, (byte)0xB9, (byte)0x0C, (byte)0x92, (byte)0x97, (byte)0xE9,
-                (byte)0x75, (byte)0xB9, (byte)0x1B, (byte)0x04, (byte)0x0F, (byte)0x93,
-                (byte)0xC9, (byte)0x69, (byte)0xF7, (byte)0xB9, (byte)0xD1, (byte)0x68,
-                (byte)0x34, (byte)0x1A, (byte)0x8D, (byte)0x46, (byte)0xA3, (byte)0xD1,
-                (byte)0x68, (byte)0x34, (byte)0x1A, (byte)0x8D, (byte)0x46, (byte)0xA3,
-                (byte)0xD1, (byte)0x68, (byte)0x34, (byte)0x1A, (byte)0x8D, (byte)0x46,
-                (byte)0xA3, (byte)0xD1, (byte)0x68, (byte)0x34, (byte)0x1A, (byte)0x8D,
-                (byte)0x46, (byte)0xA3, (byte)0xD1, (byte)0x00,
-
-                (byte)0x34
-        };
-
-        SmsCbMessage msg = createFromPdu(pdu);
-
-        assertEquals("Unexpected message identifier decoded", 12345, msg.getServiceCategory());
-    }
-
-    @Test @SmallTest
-    public void testGetMessageCode() {
-        byte[] pdu = {
-                (byte)0x2A, (byte)0xA5, (byte)0x30, (byte)0x39, (byte)0x40, (byte)0x11, (byte)0x41,
-                (byte)0xD0, (byte)0x71, (byte)0xDA, (byte)0x04, (byte)0x91, (byte)0xCB, (byte)0xE6,
-                (byte)0x70, (byte)0x9D, (byte)0x4D, (byte)0x07, (byte)0x85, (byte)0xD9, (byte)0x70,
-                (byte)0x74, (byte)0x58, (byte)0x5C, (byte)0xA6, (byte)0x83, (byte)0xDA, (byte)0xE5,
-                (byte)0xF9, (byte)0x3C, (byte)0x7C, (byte)0x2E, (byte)0x83, (byte)0xEE, (byte)0x69,
-                (byte)0x3A, (byte)0x1A, (byte)0x34, (byte)0x0E, (byte)0xCB, (byte)0xE5, (byte)0xE9,
-                (byte)0xF0, (byte)0xB9, (byte)0x0C, (byte)0x92, (byte)0x97, (byte)0xE9, (byte)0x75,
-                (byte)0xB9, (byte)0x1B, (byte)0x04, (byte)0x0F, (byte)0x93, (byte)0xC9, (byte)0x69,
-                (byte)0xF7, (byte)0xB9, (byte)0xD1, (byte)0x68, (byte)0x34, (byte)0x1A, (byte)0x8D,
-                (byte)0x46, (byte)0xA3, (byte)0xD1, (byte)0x68, (byte)0x34, (byte)0x1A, (byte)0x8D,
-                (byte)0x46, (byte)0xA3, (byte)0xD1, (byte)0x68, (byte)0x34, (byte)0x1A, (byte)0x8D,
-                (byte)0x46, (byte)0xA3, (byte)0xD1, (byte)0x68, (byte)0x34, (byte)0x1A, (byte)0x8D,
-                (byte)0x46, (byte)0xA3, (byte)0xD1, (byte)0x00
-        };
-
-        SmsCbMessage msg = createFromPdu(pdu);
-        int messageCode = (msg.getSerialNumber() & 0x3ff0) >> 4;
-
-        assertEquals("Unexpected message code decoded", 682, messageCode);
-    }
-
-    @Test @SmallTest
-    public void testGetMessageCodeUmts() {
-        byte[] pdu = {
-                (byte)0x01, (byte)0x30, (byte)0x39, (byte)0x2A, (byte)0xA5, (byte)0x40,
-
-                (byte)0x01,
-
-                (byte)0x41, (byte)0xD0, (byte)0x71, (byte)0xDA, (byte)0x04, (byte)0x91,
-                (byte)0xCB, (byte)0xE6, (byte)0x70, (byte)0x9D, (byte)0x4D, (byte)0x07,
-                (byte)0x85, (byte)0xD9, (byte)0x70, (byte)0x74, (byte)0x58, (byte)0x5C,
-                (byte)0xA6, (byte)0x83, (byte)0xDA, (byte)0xE5, (byte)0xF9, (byte)0x3C,
-                (byte)0x7C, (byte)0x2E, (byte)0x83, (byte)0xEE, (byte)0x69, (byte)0x3A,
-                (byte)0x1A, (byte)0x34, (byte)0x0E, (byte)0xCB, (byte)0xE5, (byte)0xE9,
-                (byte)0xF0, (byte)0xB9, (byte)0x0C, (byte)0x92, (byte)0x97, (byte)0xE9,
-                (byte)0x75, (byte)0xB9, (byte)0x1B, (byte)0x04, (byte)0x0F, (byte)0x93,
-                (byte)0xC9, (byte)0x69, (byte)0xF7, (byte)0xB9, (byte)0xD1, (byte)0x68,
-                (byte)0x34, (byte)0x1A, (byte)0x8D, (byte)0x46, (byte)0xA3, (byte)0xD1,
-                (byte)0x68, (byte)0x34, (byte)0x1A, (byte)0x8D, (byte)0x46, (byte)0xA3,
-                (byte)0xD1, (byte)0x68, (byte)0x34, (byte)0x1A, (byte)0x8D, (byte)0x46,
-                (byte)0xA3, (byte)0xD1, (byte)0x68, (byte)0x34, (byte)0x1A, (byte)0x8D,
-                (byte)0x46, (byte)0xA3, (byte)0xD1, (byte)0x00,
-
-                (byte)0x34
-        };
-
-        SmsCbMessage msg = createFromPdu(pdu);
-        int messageCode = (msg.getSerialNumber() & 0x3ff0) >> 4;
-
-        assertEquals("Unexpected message code decoded", 682, messageCode);
-    }
-
-    @Test @SmallTest
-    public void testGetUpdateNumber() {
-        byte[] pdu = {
-                (byte)0x2A, (byte)0xA5, (byte)0x30, (byte)0x39, (byte)0x40, (byte)0x11, (byte)0x41,
-                (byte)0xD0, (byte)0x71, (byte)0xDA, (byte)0x04, (byte)0x91, (byte)0xCB, (byte)0xE6,
-                (byte)0x70, (byte)0x9D, (byte)0x4D, (byte)0x07, (byte)0x85, (byte)0xD9, (byte)0x70,
-                (byte)0x74, (byte)0x58, (byte)0x5C, (byte)0xA6, (byte)0x83, (byte)0xDA, (byte)0xE5,
-                (byte)0xF9, (byte)0x3C, (byte)0x7C, (byte)0x2E, (byte)0x83, (byte)0xEE, (byte)0x69,
-                (byte)0x3A, (byte)0x1A, (byte)0x34, (byte)0x0E, (byte)0xCB, (byte)0xE5, (byte)0xE9,
-                (byte)0xF0, (byte)0xB9, (byte)0x0C, (byte)0x92, (byte)0x97, (byte)0xE9, (byte)0x75,
-                (byte)0xB9, (byte)0x1B, (byte)0x04, (byte)0x0F, (byte)0x93, (byte)0xC9, (byte)0x69,
-                (byte)0xF7, (byte)0xB9, (byte)0xD1, (byte)0x68, (byte)0x34, (byte)0x1A, (byte)0x8D,
-                (byte)0x46, (byte)0xA3, (byte)0xD1, (byte)0x68, (byte)0x34, (byte)0x1A, (byte)0x8D,
-                (byte)0x46, (byte)0xA3, (byte)0xD1, (byte)0x68, (byte)0x34, (byte)0x1A, (byte)0x8D,
-                (byte)0x46, (byte)0xA3, (byte)0xD1, (byte)0x68, (byte)0x34, (byte)0x1A, (byte)0x8D,
-                (byte)0x46, (byte)0xA3, (byte)0xD1, (byte)0x00
-        };
-
-        SmsCbMessage msg = createFromPdu(pdu);
-        int updateNumber = msg.getSerialNumber() & 0x000f;
-
-        assertEquals("Unexpected update number decoded", 5, updateNumber);
-    }
-
-    @Test @SmallTest
-    public void testGetUpdateNumberUmts() {
-        byte[] pdu = {
-                (byte)0x01, (byte)0x30, (byte)0x39, (byte)0x2A, (byte)0xA5, (byte)0x40,
-
-                (byte)0x01,
-
-                (byte)0x41, (byte)0xD0, (byte)0x71, (byte)0xDA, (byte)0x04, (byte)0x91,
-                (byte)0xCB, (byte)0xE6, (byte)0x70, (byte)0x9D, (byte)0x4D, (byte)0x07,
-                (byte)0x85, (byte)0xD9, (byte)0x70, (byte)0x74, (byte)0x58, (byte)0x5C,
-                (byte)0xA6, (byte)0x83, (byte)0xDA, (byte)0xE5, (byte)0xF9, (byte)0x3C,
-                (byte)0x7C, (byte)0x2E, (byte)0x83, (byte)0xEE, (byte)0x69, (byte)0x3A,
-                (byte)0x1A, (byte)0x34, (byte)0x0E, (byte)0xCB, (byte)0xE5, (byte)0xE9,
-                (byte)0xF0, (byte)0xB9, (byte)0x0C, (byte)0x92, (byte)0x97, (byte)0xE9,
-                (byte)0x75, (byte)0xB9, (byte)0x1B, (byte)0x04, (byte)0x0F, (byte)0x93,
-                (byte)0xC9, (byte)0x69, (byte)0xF7, (byte)0xB9, (byte)0xD1, (byte)0x68,
-                (byte)0x34, (byte)0x1A, (byte)0x8D, (byte)0x46, (byte)0xA3, (byte)0xD1,
-                (byte)0x68, (byte)0x34, (byte)0x1A, (byte)0x8D, (byte)0x46, (byte)0xA3,
-                (byte)0xD1, (byte)0x68, (byte)0x34, (byte)0x1A, (byte)0x8D, (byte)0x46,
-                (byte)0xA3, (byte)0xD1, (byte)0x68, (byte)0x34, (byte)0x1A, (byte)0x8D,
-                (byte)0x46, (byte)0xA3, (byte)0xD1, (byte)0x00,
-
-                (byte)0x34
-        };
-
-        SmsCbMessage msg = createFromPdu(pdu);
-        int updateNumber = msg.getSerialNumber() & 0x000f;
-
-        assertEquals("Unexpected update number decoded", 5, updateNumber);
-    }
-
-    /* ETWS Test message including header */
-    private static final byte[] etwsMessageNormal = IccUtils.hexStringToBytes("000011001101" +
-            "0D0A5BAE57CE770C531790E85C716CBF3044573065B930675730" +
-            "9707767A751F30025F37304463FA308C306B5099304830664E0B30553044FF086C178C615E81FF09" +
-            "0000000000000000000000000000");
-
-    private static final byte[] etwsMessageCancel = IccUtils.hexStringToBytes("000011001101" +
-            "0D0A5148307B3069002800310030003A0035" +
-            "00320029306E7DCA602557309707901F5831309253D66D883057307E3059FF086C178C615E81FF09" +
-            "00000000000000000000000000000000000000000000");
-
-    private static final byte[] etwsMessageTest = IccUtils.hexStringToBytes("000011031101" +
-            "0D0A5BAE57CE770C531790E85C716CBF3044" +
-            "573065B9306757309707300263FA308C306B5099304830664E0B30553044FF086C178C615E81FF09" +
-            "00000000000000000000000000000000000000000000");
-
-    // FIXME: add example of ETWS primary notification PDU
-
-    @Test @SmallTest
-    public void testEtwsMessageNormal() {
-        SmsCbMessage msg = createFromPdu(etwsMessageNormal);
-        Rlog.d(TAG, msg.toString());
-        assertEquals("GS mismatch", 0, msg.getGeographicalScope());
-        assertEquals("serial number mismatch", 0, msg.getSerialNumber());
-        assertEquals("message ID mismatch", 0x1100, msg.getServiceCategory());
-        assertEquals("warning type mismatch", SmsCbEtwsInfo.ETWS_WARNING_TYPE_EARTHQUAKE,
-                msg.getEtwsWarningInfo().getWarningType());
-    }
-
-    @Test @SmallTest
-    public void testEtwsMessageCancel() {
-        SmsCbMessage msg = createFromPdu(etwsMessageCancel);
-        Rlog.d(TAG, msg.toString());
-        assertEquals("GS mismatch", 0, msg.getGeographicalScope());
-        assertEquals("serial number mismatch", 0, msg.getSerialNumber());
-        assertEquals("message ID mismatch", 0x1100, msg.getServiceCategory());
-        assertEquals("warning type mismatch", SmsCbEtwsInfo.ETWS_WARNING_TYPE_EARTHQUAKE,
-                msg.getEtwsWarningInfo().getWarningType());
-    }
-
-    @Test @SmallTest
-    public void testEtwsMessageTest() {
-        SmsCbMessage msg = createFromPdu(etwsMessageTest);
-        Rlog.d(TAG, msg.toString());
-        assertEquals("GS mismatch", 0, msg.getGeographicalScope());
-        assertEquals("serial number mismatch", 0, msg.getSerialNumber());
-        assertEquals("message ID mismatch", 0x1103, msg.getServiceCategory());
-        assertEquals("warning type mismatch", SmsCbEtwsInfo.ETWS_WARNING_TYPE_TEST_MESSAGE,
-                msg.getEtwsWarningInfo().getWarningType());
-    }
-
-    // Make sure we don't throw an exception if we feed random data to the PDU parser.
-    @Test @SmallTest
-    public void testRandomPdus() {
-        Random r = new Random(94040);
-        for (int run = 0; run < 10000; run++) {
-            int len = r.nextInt(140);
-            byte[] data = new byte[len];
-            for (int i = 0; i < len; i++) {
-                data[i] = (byte) r.nextInt(256);
-            }
-            try {
-                // this should return a SmsCbMessage object or null for invalid data
-                SmsCbMessage msg = createFromPdu(data);
-            } catch (Exception e) {
-                Rlog.d(TAG, "exception thrown", e);
-                fail("Exception in decoder at run " + run + " length " + len + ": " + e);
-            }
-        }
-    }
-}
diff --git a/tests/telephonytests/src/com/android/internal/telephony/gsm/GsmSmsDispatcherTest.java b/tests/telephonytests/src/com/android/internal/telephony/gsm/GsmSmsDispatcherTest.java
index 198b3cb..94b8987 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/gsm/GsmSmsDispatcherTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/gsm/GsmSmsDispatcherTest.java
@@ -136,6 +136,7 @@
     public void tearDown() throws Exception {
         mGsmSmsDispatcher = null;
         mGsmSmsDispatcherTestHandler.quit();
+        mGsmSmsDispatcherTestHandler.join();
         super.tearDown();
     }
 
@@ -156,7 +157,7 @@
                 .thenReturn(new Country("US", Country.COUNTRY_SOURCE_SIM));
 
         mGsmSmsDispatcher.sendText("6501002000", "121" /*scAddr*/, "test sms",
-                null, null, null, null, false, -1, false, -1, false);
+                null, null, null, null, false, -1, false, -1, false, 0L);
 
         verify(mSimulatedCommandsVerifier).sendSMS(anyString(), anyString(), any(Message.class));
         // Blocked number provider is notified about the emergency contact asynchronously.
@@ -176,7 +177,7 @@
 
         mGsmSmsDispatcher.sendText(
                 getEmergencyNumberFromSystemPropertiesOrDefault(), "121" /*scAddr*/, "test sms",
-                null, null, null, null, false, -1, false, -1, false);
+                null, null, null, null, false, -1, false, -1, false, 0L);
 
         verify(mSimulatedCommandsVerifier).sendSMS(anyString(), anyString(), any(Message.class));
         // Blocked number provider is notified about the emergency contact asynchronously.
@@ -219,7 +220,7 @@
         // send invalid dest address: +
         mReceivedTestIntent = false;
         mGsmSmsDispatcher.sendText("+", "222" /*scAddr*/, TAG,
-                pendingIntent, null, null, null, false, -1, false, -1, false);
+                pendingIntent, null, null, null, false, -1, false, -1, false, 0L);
         waitForMs(500);
         verify(mSimulatedCommandsVerifier, times(0)).sendSMS(anyString(), anyString(),
                 any(Message.class));
@@ -250,7 +251,7 @@
         Settings.Global.putInt(mContext.getContentResolver(),
                 Settings.Global.DEVICE_PROVISIONED, 1);
 
-        mGsmSmsDispatcher.sendRawPdu(mSmsTracker);
+        mGsmSmsDispatcher.sendRawPdu(new SMSDispatcher.SmsTracker[] {mSmsTracker});
         waitForHandlerAction(mGsmSmsDispatcher, TIMEOUT_MS);
 
         verify(mSmsUsageMonitor, times(1)).checkDestination(any(), any());
@@ -287,7 +288,7 @@
         // send SMS and check sentIntent
         mReceivedTestIntent = false;
         mGsmSmsDispatcher.sendMultipartText("+123" /*destAddr*/, "222" /*scAddr*/, parts,
-                sentIntents, null, null, null, false, -1, false, -1);
+                sentIntents, null, null, null, false, -1, false, -1, 0L);
 
         waitForMs(500);
         synchronized (mLock) {
diff --git a/tests/telephonytests/src/com/android/internal/telephony/gsm/UsimDataDownloadCommands.java.broken b/tests/telephonytests/src/com/android/internal/telephony/gsm/UsimDataDownloadCommands.java.broken
index e923dfa..a9d869c 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/gsm/UsimDataDownloadCommands.java.broken
+++ b/tests/telephonytests/src/com/android/internal/telephony/gsm/UsimDataDownloadCommands.java.broken
@@ -20,7 +20,7 @@
 import android.os.AsyncResult;
 import android.os.Message;
 import android.os.SystemClock;
-import android.telephony.Rlog;
+import com.android.telephony.Rlog;
 
 import com.android.internal.telephony.BaseCommands;
 import com.android.internal.telephony.UUSInfo;
@@ -381,7 +381,7 @@
     }
 
     @Override
-    public void writeSmsToRuim(int status, String pdu, Message response) {
+    public void writeSmsToRuim(int status, byte[] pdu, Message response) {
     }
 
     @Override
diff --git a/tests/telephonytests/src/com/android/internal/telephony/gsm/UsimDataDownloadTest.java.broken b/tests/telephonytests/src/com/android/internal/telephony/gsm/UsimDataDownloadTest.java.broken
index f32e4bd..9fbb86c 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/gsm/UsimDataDownloadTest.java.broken
+++ b/tests/telephonytests/src/com/android/internal/telephony/gsm/UsimDataDownloadTest.java.broken
@@ -18,7 +18,7 @@
 
 import android.os.HandlerThread;
 import android.test.AndroidTestCase;
-import android.telephony.Rlog;
+import com.android.telephony.Rlog;
 
 import java.nio.charset.Charset;
 
diff --git a/tests/telephonytests/src/com/android/internal/telephony/ims/FeatureConnectionTest.java b/tests/telephonytests/src/com/android/internal/telephony/ims/FeatureConnectionTest.java
new file mode 100644
index 0000000..bac45ef
--- /dev/null
+++ b/tests/telephonytests/src/com/android/internal/telephony/ims/FeatureConnectionTest.java
@@ -0,0 +1,220 @@
+/*
+ * Copyright (C) 2019 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.ims;
+
+import junit.framework.AssertionFailedError;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.telephony.ims.aidl.IImsRegistration;
+import android.telephony.ims.feature.ImsFeature;
+import android.telephony.ims.stub.ImsRegistrationImplBase;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import com.android.ims.FeatureConnection;
+import com.android.ims.ImsManager;
+import com.android.ims.internal.IImsServiceFeatureCallback;
+import com.android.internal.telephony.TelephonyTest;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+
+import java.util.concurrent.Executor;
+
+public class FeatureConnectionTest extends TelephonyTest {
+
+    private Executor mSimpleExecutor = new Executor() {
+        @Override
+        public void execute(Runnable r) {
+            r.run();
+        }
+    };
+
+    private class TestFeatureConnection extends FeatureConnection {
+        private Integer mFeatureState = ImsFeature.STATE_READY;
+
+        public boolean isFeatureCreatedCalled = false;
+        public boolean isFeatureRemovedCalled = false;
+        public int mNewStatus = ImsFeature.STATE_UNAVAILABLE;
+
+        TestFeatureConnection(Context context, int slotId) {
+            super(context, slotId);
+            if (!ImsManager.isImsSupportedOnDevice(context)) {
+                sImsSupportedOnDevice = false;
+            }
+        }
+
+        @Override
+        public void checkServiceIsReady() throws RemoteException {
+            super.checkServiceIsReady();
+        }
+
+        @Override
+        protected void handleImsFeatureCreatedCallback(int slotId, int feature) {
+            isFeatureCreatedCalled = true;
+        }
+
+        @Override
+        protected void handleImsFeatureRemovedCallback(int slotId, int feature) {
+            isFeatureRemovedCalled = true;
+        }
+
+        @Override
+        protected void handleImsStatusChangedCallback(int slotId, int feature, int status) {
+            mNewStatus = status;
+        }
+
+        @Override
+        protected Integer retrieveFeatureState() {
+            return mFeatureState;
+        }
+
+        @Override
+        protected IImsRegistration getRegistrationBinder() {
+            return getTestRegistrationBinder();
+        }
+
+        public void setFeatureState(int state) {
+            mFeatureState = state;
+        }
+    };
+
+    private int mPhoneId;
+    private TestFeatureConnection mTestFeatureConnection;
+    @Mock IBinder mBinder;
+    @Mock IImsRegistration mRegistrationBinder;
+
+    @Before
+    public void setUp() throws Exception {
+        super.setUp("FeatureConnectionTest");
+        mPhoneId = mPhone.getPhoneId();
+
+        doReturn(null).when(mContext).getMainLooper();
+        doReturn(true).when(mPackageManager).hasSystemFeature(PackageManager.FEATURE_TELEPHONY_IMS);
+
+        mTestFeatureConnection = new TestFeatureConnection(mContext, mPhoneId);
+        mTestFeatureConnection.mExecutor = mSimpleExecutor;
+        mTestFeatureConnection.setBinder(mBinder);
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        super.tearDown();
+    }
+
+    /**
+     * Test service is ready when binder is alive and IMS status is ready.
+     */
+    @Test
+    @SmallTest
+    public void testServiceIsReady() {
+        when(mBinder.isBinderAlive()).thenReturn(true);
+        mTestFeatureConnection.setFeatureState(ImsFeature.STATE_READY);
+
+        try {
+            mTestFeatureConnection.checkServiceIsReady();
+        } catch (RemoteException e) {
+            throw new AssertionFailedError("Exception in testServiceIsReady: " + e);
+        }
+    }
+
+    /**
+     * Test service is not ready when binder is not alive or status is not ready.
+     */
+    @Test
+    @SmallTest
+    public void testServiceIsNotReady() {
+        // Binder is not alive
+        when(mBinder.isBinderAlive()).thenReturn(false);
+
+        try {
+            mTestFeatureConnection.checkServiceIsReady();
+            throw new AssertionFailedError("testServiceIsNotReady: binder isn't alive");
+        } catch (RemoteException e) {
+            // expected result
+        }
+
+        // IMS feature status is unavailable
+        when(mBinder.isBinderAlive()).thenReturn(true);
+        mTestFeatureConnection.setFeatureState(ImsFeature.STATE_UNAVAILABLE);
+
+        try {
+            mTestFeatureConnection.checkServiceIsReady();
+            throw new AssertionFailedError("testServiceIsNotReady: status unavailable");
+        } catch (RemoteException e) {
+            // expected result
+        }
+    }
+
+    /**
+     * Test registration tech callbacks.
+     */
+    @Test
+    @SmallTest
+    public void testRegistrationTech() throws Exception {
+        when(mRegistrationBinder.getRegistrationTechnology()).thenReturn(
+                ImsRegistrationImplBase.REGISTRATION_TECH_IWLAN);
+
+        assertEquals(ImsRegistrationImplBase.REGISTRATION_TECH_IWLAN,
+                mTestFeatureConnection.getRegistrationTech());
+
+    }
+
+    /**
+     * Test callback is called when IMS feature created/removed/changed.
+     */
+    @Test
+    @SmallTest
+    public void testListenerCallback() {
+        IImsServiceFeatureCallback featureCallback = mTestFeatureConnection.getListener();
+
+        try {
+            featureCallback.imsFeatureCreated(anyInt(), anyInt());
+            assertTrue(mTestFeatureConnection.isFeatureCreatedCalled);
+        } catch (RemoteException e) {
+            throw new AssertionFailedError("testListenerCallback(Created): " + e);
+        }
+
+        try {
+            featureCallback.imsFeatureRemoved(anyInt(), anyInt());
+            assertTrue(mTestFeatureConnection.isFeatureRemovedCalled);
+        } catch (RemoteException e) {
+            throw new AssertionFailedError("testListenerCallback(Removed): " + e);
+        }
+
+        try {
+            featureCallback.imsStatusChanged(anyInt(), anyInt(), ImsFeature.STATE_READY);
+            assertEquals(mTestFeatureConnection.mNewStatus, ImsFeature.STATE_READY);
+        } catch (RemoteException e) {
+            throw new AssertionFailedError("testListenerCallback(Changed): " + e);
+        }
+    }
+
+    private IImsRegistration getTestRegistrationBinder() {
+        return mRegistrationBinder;
+    }
+}
diff --git a/tests/telephonytests/src/com/android/internal/telephony/ims/FeatureConnectorTest.java b/tests/telephonytests/src/com/android/internal/telephony/ims/FeatureConnectorTest.java
new file mode 100644
index 0000000..a2b4de8
--- /dev/null
+++ b/tests/telephonytests/src/com/android/internal/telephony/ims/FeatureConnectorTest.java
@@ -0,0 +1,155 @@
+/*
+ * Copyright 2019 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.ims;
+
+import junit.framework.AssertionFailedError;
+
+import static org.mockito.ArgumentMatchers.anyObject;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.pm.PackageManager;
+import android.os.HandlerThread;
+import android.os.Looper;
+import android.telephony.ims.feature.ImsFeature;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import com.android.ims.FeatureConnector;
+import com.android.ims.ImsException;
+import com.android.ims.ImsManager;
+import com.android.internal.telephony.TelephonyTest;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+
+import java.util.concurrent.Executor;
+
+public class FeatureConnectorTest extends TelephonyTest {
+
+    private Executor mExecutor = new Executor() {
+        @Override
+        public void execute(Runnable r) {
+            r.run();
+        }
+    };
+
+    private HandlerThread mHandlerThread;
+    private FeatureConnector<ImsManager> mFeatureConnector;
+    @Mock
+    ImsManager mImsManager;
+    @Mock
+    FeatureConnector.Listener<ImsManager> mListener;
+    @Mock
+    FeatureConnector.RetryTimeout mRetryTimeout;
+
+    @Before
+    public void setUp() throws Exception {
+        super.setUp("FeatureConnectorTest");
+        if (Looper.myLooper() == null) {
+            Looper.prepare();
+        }
+
+        int phoneId = mPhone.getPhoneId();
+        mHandlerThread = new HandlerThread("ConnectorHandlerThread");
+        mHandlerThread.start();
+
+        mFeatureConnector = new FeatureConnector<>(mContext, phoneId,
+            mListener, mExecutor, mHandlerThread.getLooper());
+        mFeatureConnector.mListener = mListener;
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        mHandlerThread.quit();
+        super.tearDown();
+    }
+
+    @Test
+    @SmallTest
+    public void testConnect() {
+        // ImsManager is supported on device
+        setImsSupportedFeature(true);
+        when(mListener.getFeatureManager()).thenReturn(mImsManager);
+
+        mFeatureConnector.connect();
+        waitForHandlerAction(mFeatureConnector, 1000);
+
+        // Verify that mListener will retrieve feature manager
+        verify(mListener).getFeatureManager();
+
+        reset(mListener);
+
+        // ImsManager is NOT supported on device
+        setImsSupportedFeature(false);
+        when(mListener.getFeatureManager()).thenReturn(mImsManager);
+
+        mFeatureConnector.connect();
+        waitForHandlerAction(mFeatureConnector, 1000);
+
+        // Verify that mListener won't retrieve feature manager
+        verify(mListener, never()).getFeatureManager();
+    }
+
+    @Test
+    @SmallTest
+    public void testDisconnect() {
+        // Verify mListener will call connectionUnavailable if disconnect() is called.
+        mFeatureConnector.disconnect();
+        verify(mListener).connectionUnavailable();
+    }
+
+    @Test
+    @SmallTest
+    public void testNotifyStateChanged() {
+        try {
+            mFeatureConnector.mManager = mImsManager;
+            when(mImsManager.getImsServiceState()).thenReturn(ImsFeature.STATE_READY);
+            // Trigger status changed
+            mFeatureConnector.mNotifyStatusChangedCallback.notifyStateChanged();
+            // Verify NotifyReady is called
+            verify(mListener).connectionReady(anyObject());
+        } catch (ImsException e) {
+            throw new AssertionFailedError("Exception in testNotifyStateChanged: " + e);
+        }
+    }
+
+    @Test
+    @SmallTest
+    public void testRetryGetImsService() {
+        mFeatureConnector.mManager = mImsManager;
+        mFeatureConnector.mRetryTimeout = mRetryTimeout;
+
+        when(mRetryTimeout.get()).thenReturn(1);
+        when(mListener.getFeatureManager()).thenReturn(mImsManager);
+
+        mFeatureConnector.retryGetImsService();
+        waitForHandlerAction(mFeatureConnector, 2000);
+
+        // Verify removeNotifyStatusChangedCallback will be called if ImsManager is not null.
+        verify(mImsManager).removeNotifyStatusChangedCallback(anyObject());
+    }
+
+    private void setImsSupportedFeature(boolean isSupported) {
+        doReturn(isSupported).when(mPackageManager).hasSystemFeature(
+                PackageManager.FEATURE_TELEPHONY_IMS);
+    }
+}
diff --git a/tests/telephonytests/src/com/android/internal/telephony/ims/ImsManagerTest.java b/tests/telephonytests/src/com/android/internal/telephony/ims/ImsManagerTest.java
index 38218c4..7088024 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/ims/ImsManagerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/ims/ImsManagerTest.java
@@ -19,6 +19,7 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.fail;
 import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.ArgumentMatchers.nullable;
 import static org.mockito.Mockito.anyInt;
 import static org.mockito.Mockito.anyString;
 import static org.mockito.Mockito.doReturn;
@@ -95,8 +96,8 @@
         doReturn(mSubscriptionController).when(mBinder).queryLocalInterface(anyString());
         mServiceManagerMockedServices.put("isub", mBinder);
         // Stick to the CarrierConfig defaults unless explicitly overwritten.
-        doReturn("-1").when(mSubscriptionController)
-                .getSubscriptionProperty(anyInt(), anyString(), anyString());
+        doReturn("-1").when(mSubscriptionController).getSubscriptionProperty(anyInt(), anyString(),
+                anyString(), nullable(String.class));
 
 
         doReturn(true).when(mMmTelFeatureConnection).isBinderAlive();
@@ -133,6 +134,8 @@
         mBundle.putBoolean(
                 CarrierConfigManager.KEY_USE_WFC_HOME_NETWORK_MODE_IN_ROAMING_NETWORK_BOOL,
                 WFC_NOT_USE_HOME_MODE_FOR_ROAMING_VAL);
+        mBundle.putBoolean(CarrierConfigManager.KEY_CARRIER_RCS_PROVISIONING_REQUIRED_BOOL, true);
+
     }
 
     @Test @SmallTest
@@ -143,38 +146,44 @@
         verify(mSubscriptionController, times(1)).getSubscriptionProperty(
                 anyInt(),
                 eq(SubscriptionManager.WFC_IMS_ENABLED),
-                anyString());
+                anyString(),
+                nullable(String.class));
 
         assertEquals(WFC_IMS_ROAMING_ENABLE_DEFAULT_VAL, imsManager.isWfcRoamingEnabledByUser());
         verify(mSubscriptionController, times(1)).getSubscriptionProperty(
                 anyInt(),
                 eq(SubscriptionManager.WFC_IMS_ROAMING_ENABLED),
-                anyString());
+                anyString(),
+                nullable(String.class));
 
         assertEquals(ENHANCED_4G_MODE_DEFAULT_VAL,
                 imsManager.isEnhanced4gLteModeSettingEnabledByUser());
         verify(mSubscriptionController, times(1)).getSubscriptionProperty(
                 anyInt(),
                 eq(SubscriptionManager.ENHANCED_4G_MODE_ENABLED),
-                anyString());
+                anyString(),
+                nullable(String.class));
 
         assertEquals(WFC_IMS_MODE_DEFAULT_VAL, imsManager.getWfcMode(false));
         verify(mSubscriptionController, times(1)).getSubscriptionProperty(
                 anyInt(),
                 eq(SubscriptionManager.WFC_IMS_MODE),
-                anyString());
+                anyString(),
+                nullable(String.class));
 
         assertEquals(WFC_IMS_ROAMING_MODE_DEFAULT_VAL, imsManager.getWfcMode(true));
         verify(mSubscriptionController, times(1)).getSubscriptionProperty(
                 anyInt(),
                 eq(SubscriptionManager.WFC_IMS_ROAMING_MODE),
-                anyString());
+                anyString(),
+                nullable(String.class));
 
         assertEquals(VT_IMS_ENABLE_DEFAULT_VAL, imsManager.isVtEnabledByUser());
         verify(mSubscriptionController, times(1)).getSubscriptionProperty(
                 anyInt(),
                 eq(SubscriptionManager.VT_IMS_ENABLED),
-                anyString());
+                anyString(),
+                nullable(String.class));
     }
 
     @Test @SmallTest
@@ -243,6 +252,10 @@
         assertEquals(true, imsManager.isVolteProvisionedOnDevice());
         verify(mImsConfigImplBaseMock, times(1)).getConfigInt(
                 eq(ImsConfig.ConfigConstants.VLT_SETTING_ENABLED));
+
+        assertEquals(true, imsManager.isEabProvisionedOnDevice());
+        verify(mImsConfigImplBaseMock, times(1)).getConfigInt(
+                eq(ImsConfig.ConfigConstants.EAB_SETTING_ENABLED));
     }
 
     @Test
@@ -267,7 +280,30 @@
                 eq(0));
         verify(mImsConfigImplBaseMock, times(1)).getConfigInt(
                 eq(ImsConfig.ConfigConstants.VOICE_OVER_WIFI_SETTING_ENABLED));
+    }
 
+    @Test
+    public void testEabSetProvisionedValues() throws Exception {
+        ImsManager imsManager = getImsManagerAndInitProvisionedValues();
+
+        assertEquals(true, imsManager.isEabProvisionedOnDevice());
+        verify(mImsConfigImplBaseMock, times(1)).getConfigInt(
+                eq(ImsConfig.ConfigConstants.EAB_SETTING_ENABLED));
+
+        imsManager.getConfigInterface().setProvisionedValue(
+                ImsConfig.ConfigConstants.EAB_SETTING_ENABLED,
+                ImsConfig.FeatureValueConstants.OFF);
+
+        assertEquals(0, (int) mProvisionedIntVals.get(
+                ImsConfig.ConfigConstants.EAB_SETTING_ENABLED));
+
+        assertEquals(false, imsManager.isEabProvisionedOnDevice());
+
+        verify(mImsConfigImplBaseMock, times(1)).setConfig(
+                eq(ImsConfig.ConfigConstants.EAB_SETTING_ENABLED),
+                eq(0));
+        verify(mImsConfigImplBaseMock, times(1)).getConfigInt(
+                eq(ImsConfig.ConfigConstants.EAB_SETTING_ENABLED));
     }
 
     /**
@@ -285,12 +321,14 @@
                 .when(mSubscriptionController).getSubscriptionProperty(
                         anyInt(),
                         eq(SubscriptionManager.WFC_IMS_MODE),
-                        anyString());
+                        anyString(),
+                        nullable(String.class));
         doReturn(String.valueOf(ImsConfig.WfcModeFeatureValueConstants.CELLULAR_PREFERRED))
                 .when(mSubscriptionController).getSubscriptionProperty(
                         anyInt(),
                         eq(SubscriptionManager.WFC_IMS_ROAMING_MODE),
-                        anyString());
+                        anyString(),
+                        nullable(String.class));
         ImsManager imsManager = getImsManagerAndInitProvisionedValues();
 
         // Roaming
@@ -345,7 +383,8 @@
                 .when(mSubscriptionController).getSubscriptionProperty(
                 anyInt(),
                 eq(SubscriptionManager.WFC_IMS_ROAMING_ENABLED),
-                anyString());
+                anyString(),
+                nullable(String.class));
 
         ImsManager imsManager = getImsManagerAndInitProvisionedValues();
 
@@ -380,7 +419,8 @@
                 .when(mSubscriptionController).getSubscriptionProperty(
                 anyInt(),
                 eq(SubscriptionManager.WFC_IMS_ROAMING_ENABLED),
-                anyString());
+                anyString(),
+                nullable(String.class));
 
         ImsManager imsManager = getImsManagerAndInitProvisionedValues();
 
@@ -454,7 +494,8 @@
                 .when(mSubscriptionController).getSubscriptionProperty(
                 anyInt(),
                 eq(SubscriptionManager.WFC_IMS_ENABLED),
-                anyString());
+                anyString(),
+                nullable(String.class));
 
         // The device is roaming
         doReturn(true).when(mTelephonyManager).isNetworkRoaming(eq(mSubId[0]));
@@ -500,7 +541,8 @@
                 .when(mSubscriptionController).getSubscriptionProperty(
                 anyInt(),
                 eq(SubscriptionManager.WFC_IMS_ENABLED),
-                anyString());
+                anyString(),
+                nullable(String.class));
 
         // The device is roaming
         doReturn(true).when(mTelephonyManager).isNetworkRoaming(eq(mSubId[0]));
@@ -534,13 +576,15 @@
                 .when(mSubscriptionController).getSubscriptionProperty(
                 anyInt(),
                 eq(SubscriptionManager.WFC_IMS_ENABLED),
-                anyString());
+                anyString(),
+                nullable(String.class));
         // The user has enabled the "WFC while roaming" setting in the UI while WFC was enabled
         doReturn(String.valueOf(1 /*true*/))
                 .when(mSubscriptionController).getSubscriptionProperty(
                 anyInt(),
                 eq(SubscriptionManager.WFC_IMS_ROAMING_ENABLED),
-                anyString());
+                anyString(),
+                nullable(String.class));
 
         ImsManager imsManager = getImsManagerAndInitProvisionedValues();
 
@@ -574,7 +618,8 @@
                 .when(mSubscriptionController).getSubscriptionProperty(
                 anyInt(),
                 eq(SubscriptionManager.WFC_IMS_ROAMING_ENABLED),
-                anyString());
+                anyString(),
+                nullable(String.class));
 
         ImsManager imsManager = getImsManagerAndInitProvisionedValues();
 
@@ -609,12 +654,14 @@
                 .when(mSubscriptionController).getSubscriptionProperty(
                 anyInt(),
                 eq(SubscriptionManager.WFC_IMS_MODE),
-                anyString());
+                anyString(),
+                nullable(String.class));
         doReturn(String.valueOf(ImsConfig.WfcModeFeatureValueConstants.WIFI_ONLY))
                 .when(mSubscriptionController).getSubscriptionProperty(
                 anyInt(),
                 eq(SubscriptionManager.WFC_IMS_ROAMING_MODE),
-                anyString());
+                anyString(),
+                nullable(String.class));
         ImsManager imsManager = getImsManagerAndInitProvisionedValues();
 
         // Roaming
@@ -687,12 +734,14 @@
                 .when(mSubscriptionController).getSubscriptionProperty(
                 anyInt(),
                 eq(SubscriptionManager.WFC_IMS_MODE),
-                anyString());
+                anyString(),
+                nullable(String.class));
         doReturn(String.valueOf(ImsConfig.WfcModeFeatureValueConstants.CELLULAR_PREFERRED))
                 .when(mSubscriptionController).getSubscriptionProperty(
                 anyInt(),
                 eq(SubscriptionManager.WFC_IMS_ROAMING_MODE),
-                anyString());
+                anyString(),
+                nullable(String.class));
 
         ImsManager imsManager = getImsManagerAndInitProvisionedValues();
 
@@ -702,7 +751,8 @@
         verify(mSubscriptionController, times(1)).getSubscriptionProperty(
                 anyInt(),
                 eq(SubscriptionManager.WFC_IMS_ROAMING_MODE),
-                anyString());
+                anyString(),
+                nullable(String.class));
 
         // Set WFC roaming network mode to not editable.
         mBundle.putBoolean(CarrierConfigManager.KEY_EDITABLE_WFC_ROAMING_MODE_BOOL,
@@ -713,7 +763,8 @@
         verify(mSubscriptionController, times(1)).getSubscriptionProperty(
                 anyInt(),
                 eq(SubscriptionManager.WFC_IMS_ROAMING_MODE),
-                anyString());
+                anyString(),
+                nullable(String.class));
     }
 
     /**
@@ -730,12 +781,14 @@
                 .when(mSubscriptionController).getSubscriptionProperty(
                 anyInt(),
                 eq(SubscriptionManager.WFC_IMS_MODE),
-                anyString());
+                anyString(),
+                nullable(String.class));
         doReturn(String.valueOf(ImsConfig.WfcModeFeatureValueConstants.CELLULAR_PREFERRED))
                 .when(mSubscriptionController).getSubscriptionProperty(
                 anyInt(),
                 eq(SubscriptionManager.WFC_IMS_ROAMING_MODE),
-                anyString());
+                anyString(),
+                nullable(String.class));
 
         // Set to use WFC home network mode in roaming network.
         mBundle.putBoolean(
@@ -749,7 +802,8 @@
         verify(mSubscriptionController, times(1)).getSubscriptionProperty(
                 anyInt(),
                 eq(SubscriptionManager.WFC_IMS_MODE),
-                anyString());
+                anyString(),
+                nullable(String.class));
 
         // Set WFC home network mode to not editable.
         mBundle.putBoolean(CarrierConfigManager.KEY_EDITABLE_WFC_MODE_BOOL,
@@ -760,7 +814,8 @@
         verify(mSubscriptionController, times(1)).getSubscriptionProperty(
                 anyInt(),
                 eq(SubscriptionManager.WFC_IMS_MODE),
-                anyString());
+                anyString(),
+                nullable(String.class));
     }
 
     /**
@@ -818,6 +873,7 @@
             fail("failed with " + ex);
         }
 
+        ((ImsManager.ImsExecutorFactory) imsManager.mExecutorFactory).destroy();
         imsManager.mExecutorFactory = Runnable::run;
 
         return imsManager;
diff --git a/tests/telephonytests/src/com/android/internal/telephony/ims/ImsResolverTest.java b/tests/telephonytests/src/com/android/internal/telephony/ims/ImsResolverTest.java
index b7c450c..1770764 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/ims/ImsResolverTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/ims/ImsResolverTest.java
@@ -20,6 +20,7 @@
 import static junit.framework.Assert.assertNotNull;
 import static junit.framework.Assert.assertNull;
 import static junit.framework.Assert.assertTrue;
+import static junit.framework.Assert.fail;
 import static junit.framework.TestCase.assertFalse;
 
 import static org.mockito.ArgumentMatchers.argThat;
@@ -29,6 +30,7 @@
 import static org.mockito.Mockito.atLeastOnce;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
@@ -44,13 +46,19 @@
 import android.os.Bundle;
 import android.os.PersistableBundle;
 import android.os.RemoteException;
+import android.os.UserManager;
 import android.telephony.CarrierConfigManager;
+import android.telephony.TelephonyManager;
 import android.telephony.ims.ImsService;
 import android.telephony.ims.feature.ImsFeature;
 import android.telephony.ims.stub.ImsFeatureConfiguration;
 import android.test.suitebuilder.annotation.SmallTest;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+import android.util.ArrayMap;
+import android.util.ArraySet;
 
-import androidx.test.runner.AndroidJUnit4;
+import com.android.internal.telephony.PhoneConfigurationManager;
 
 import org.junit.After;
 import org.junit.Before;
@@ -58,11 +66,11 @@
 import org.junit.runner.RunWith;
 import org.mockito.ArgumentCaptor;
 import org.mockito.Mock;
-import org.mockito.Mockito;
 
 import java.util.ArrayList;
 import java.util.HashSet;
 import java.util.List;
+import java.util.Map;
 import java.util.Objects;
 import java.util.Set;
 import java.util.stream.Collectors;
@@ -70,17 +78,22 @@
 /**
  * Unit tests for ImsResolver
  */
-@RunWith(AndroidJUnit4.class)
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper
 public class ImsResolverTest extends ImsTestBase {
 
-    private static final int TEST_TIMEOUT = 200; //ms
     private static final ComponentName TEST_DEVICE_DEFAULT_NAME = new ComponentName("TestDevicePkg",
             "DeviceImsService");
+    private static final ComponentName TEST_DEVICE2_DEFAULT_NAME = new ComponentName(
+            "TestDevicePkg2", "DeviceImsService2");
     private static final ComponentName TEST_CARRIER_DEFAULT_NAME = new ComponentName(
             "TestCarrierPkg", "CarrierImsService");
     private static final ComponentName TEST_CARRIER_2_DEFAULT_NAME = new ComponentName(
             "TestCarrier2Pkg", "Carrier2ImsService");
 
+    private static final int NUM_MAX_SLOTS = 2;
+    private static final String TAG = ImsResolverTest.class.getSimpleName();
+
     @Mock
     Context mMockContext;
     @Mock
@@ -88,16 +101,22 @@
     @Mock
     ImsResolver.SubscriptionManagerProxy mTestSubscriptionManagerProxy;
     @Mock
+    ImsResolver.TelephonyManagerProxy mTestTelephonyManagerProxy;
+    @Mock
     CarrierConfigManager mMockCarrierConfigManager;
     @Mock
+    UserManager mMockUserManager;
+    @Mock
     ImsResolver.ImsDynamicQueryManagerFactory mMockQueryManagerFactory;
     @Mock
     ImsServiceFeatureQueryManager mMockQueryManager;
     private ImsResolver mTestImsResolver;
     private BroadcastReceiver mTestPackageBroadcastReceiver;
     private BroadcastReceiver mTestCarrierConfigReceiver;
+    private BroadcastReceiver mTestBootCompleteReceiver;
     private ImsServiceFeatureQueryManager.Listener mDynamicQueryListener;
     private PersistableBundle[] mCarrierConfigs;
+    private TestableLooper mLooper;
 
     @Before
     @Override
@@ -108,7 +127,10 @@
     @After
     @Override
     public void tearDown() throws Exception {
+        mTestImsResolver.destroy();
         mTestImsResolver = null;
+        mLooper.destroy();
+        mLooper = null;
         super.tearDown();
     }
 
@@ -119,7 +141,8 @@
     @Test
     @SmallTest
     public void testAddDevicePackageToCache() {
-        setupResolver(1/*numSlots*/);
+        setupResolver(1 /*numSlots*/, TEST_DEVICE_DEFAULT_NAME.getPackageName(),
+                TEST_DEVICE_DEFAULT_NAME.getPackageName());
         HashSet<String> features = new HashSet<>();
         features.add(ImsResolver.METADATA_EMERGENCY_MMTEL_FEATURE);
         features.add(ImsResolver.METADATA_MMTEL_FEATURE);
@@ -128,7 +151,7 @@
         setupController();
 
         // Complete package manager lookup and cache.
-        startBind();
+        startBindCarrierConfigAlreadySet();
 
         ImsResolver.ImsServiceInfo testCachedService =
                 mTestImsResolver.getImsServiceInfoFromCache(
@@ -144,17 +167,18 @@
     @Test
     @SmallTest
     public void testAddCarrierPackageToCache() {
-        setupResolver(1/*numSlots*/);
+        setupResolver(1 /*numSlots*/, TEST_DEVICE_DEFAULT_NAME.getPackageName(),
+                TEST_DEVICE_DEFAULT_NAME.getPackageName());
         HashSet<String> features = new HashSet<>();
         features.add(ImsResolver.METADATA_EMERGENCY_MMTEL_FEATURE);
         features.add(ImsResolver.METADATA_MMTEL_FEATURE);
         features.add(ImsResolver.METADATA_RCS_FEATURE);
-        setConfigCarrierString(0, TEST_CARRIER_DEFAULT_NAME.getPackageName());
+        setConfigCarrierStringMmTelRcs(0, TEST_CARRIER_DEFAULT_NAME.getPackageName());
         setupPackageQuery(TEST_CARRIER_DEFAULT_NAME, features, true);
         setupController();
 
         // Complete package manager lookup and cache.
-        startBind();
+        startBindCarrierConfigAlreadySet();
 
         ImsResolver.ImsServiceInfo testCachedService =
                 mTestImsResolver.getImsServiceInfoFromCache(
@@ -173,18 +197,19 @@
     @Test
     @SmallTest
     public void testCarrierPackageBind() throws RemoteException {
-        setupResolver(1/*numSlots*/);
+        setupResolver(1 /*numSlots*/, TEST_DEVICE_DEFAULT_NAME.getPackageName(),
+                TEST_DEVICE_DEFAULT_NAME.getPackageName());
         // Setup the carrier features
         HashSet<ImsFeatureConfiguration.FeatureSlotPair> features = new HashSet<>();
         features.add(new ImsFeatureConfiguration.FeatureSlotPair(0, ImsFeature.FEATURE_MMTEL));
         features.add(new ImsFeatureConfiguration.FeatureSlotPair(0, ImsFeature.FEATURE_RCS));
         // Set CarrierConfig default package name and make it available as the CarrierConfig.
-        setConfigCarrierString(0, TEST_CARRIER_DEFAULT_NAME.getPackageName());
+        setConfigCarrierStringMmTelRcs(0, TEST_CARRIER_DEFAULT_NAME.getPackageName());
         setupPackageQuery(TEST_CARRIER_DEFAULT_NAME, new HashSet<>(), true);
         ImsServiceController controller = setupController();
 
         // Start bind to carrier service
-        startBind();
+        startBindCarrierConfigAlreadySet();
         // setup features response
         setupDynamicQueryFeatures(TEST_CARRIER_DEFAULT_NAME, features, 1);
 
@@ -194,22 +219,133 @@
     }
 
     /**
+     * Set the carrier config override value to many separate services for MMTEL and RCS and ensure
+     * that ImsResolver calls .bind on those package names with the correct ImsFeatures.
+     */
+    @Test
+    @SmallTest
+    public void testDeviceCarrierPackageBindMultipleServices() throws RemoteException {
+        setupResolver(2 /*numSlots*/, TEST_DEVICE_DEFAULT_NAME.getPackageName(),
+                TEST_DEVICE_DEFAULT_NAME.getPackageName());
+        // Setup the carrier features: carrier 1 - MMTEL, slot 0; carrier 2 RCS, slot 0
+        HashSet<ImsFeatureConfiguration.FeatureSlotPair> featuresMmTel = new HashSet<>();
+        featuresMmTel.add(new ImsFeatureConfiguration.FeatureSlotPair(0, ImsFeature.FEATURE_MMTEL));
+        HashSet<ImsFeatureConfiguration.FeatureSlotPair> featuresRcs = new HashSet<>();
+        featuresRcs.add(new ImsFeatureConfiguration.FeatureSlotPair(0, ImsFeature.FEATURE_RCS));
+        HashSet<ImsFeatureConfiguration.FeatureSlotPair> featuresAll = new HashSet<>(featuresMmTel);
+        featuresAll.addAll(featuresRcs);
+        // Setup the device features: MMTEL, RCS on slot 0,1
+        HashSet<ImsFeatureConfiguration.FeatureSlotPair> featuresDevice = new HashSet<>();
+        featuresDevice.add(new ImsFeatureConfiguration.FeatureSlotPair(1,
+                ImsFeature.FEATURE_MMTEL));
+        featuresDevice.add(new ImsFeatureConfiguration.FeatureSlotPair(1, ImsFeature.FEATURE_RCS));
+        // Set CarrierConfig default package name and make it available as the CarrierConfig.
+        setConfigCarrierStringMmTel(0, TEST_CARRIER_DEFAULT_NAME.getPackageName());
+        setConfigCarrierStringRcs(0, TEST_CARRIER_2_DEFAULT_NAME.getPackageName());
+        Set<String> deviceFeatures = new ArraySet<>();
+        deviceFeatures.add(ImsResolver.METADATA_MMTEL_FEATURE);
+        deviceFeatures.add(ImsResolver.METADATA_RCS_FEATURE);
+        List<ResolveInfo> info = new ArrayList<>();
+        info.add(getResolveInfo(TEST_DEVICE_DEFAULT_NAME, deviceFeatures, true));
+        info.add(getResolveInfo(TEST_CARRIER_DEFAULT_NAME, new HashSet<>(), true));
+        info.add(getResolveInfo(TEST_CARRIER_2_DEFAULT_NAME, new HashSet<>(), true));
+        setupPackageQuery(info);
+        ImsServiceController deviceController = mock(ImsServiceController.class);
+        ImsServiceController carrierController1 = mock(ImsServiceController.class);
+        ImsServiceController carrierController2 = mock(ImsServiceController.class);
+        setImsServiceControllerFactory(deviceController, carrierController1, carrierController2);
+
+        // Start bind to carrier service
+        startBindCarrierConfigAlreadySet();
+        // setup features response
+        // emulate mMockQueryManager returning information about pending queries.
+        when(mMockQueryManager.isQueryInProgress()).thenReturn(true);
+        setupDynamicQueryFeatures(TEST_CARRIER_DEFAULT_NAME, featuresAll, 1);
+        when(mMockQueryManager.isQueryInProgress()).thenReturn(false);
+        setupDynamicQueryFeatures(TEST_CARRIER_2_DEFAULT_NAME, featuresAll, 1);
+
+        verify(deviceController).bind(featuresDevice);
+        verify(deviceController, never()).unbind();
+        verify(carrierController1).bind(featuresMmTel);
+        verify(carrierController1, never()).unbind();
+        verify(carrierController2).bind(featuresRcs);
+        verify(carrierController2, never()).unbind();
+        assertEquals(TEST_CARRIER_DEFAULT_NAME, carrierController1.getComponentName());
+        assertEquals(TEST_CARRIER_2_DEFAULT_NAME, carrierController2.getComponentName());
+    }
+
+    /**
+     * Set the carrier config override value to two separate services for MMTEL and RCS and ensure
+     * that ImsResolver calls .bind on those package names with the correct ImsFeatures.
+     */
+    @Test
+    @SmallTest
+    public void testCarrierPackageBindOneConfigTwoSupport() throws RemoteException {
+        setupResolver(2 /*numSlots*/, TEST_DEVICE_DEFAULT_NAME.getPackageName(),
+                TEST_DEVICE_DEFAULT_NAME.getPackageName());
+        // Setup the carrier features
+        HashSet<ImsFeatureConfiguration.FeatureSlotPair> featuresMmTel = new HashSet<>();
+        featuresMmTel.add(new ImsFeatureConfiguration.FeatureSlotPair(0, ImsFeature.FEATURE_MMTEL));
+        HashSet<ImsFeatureConfiguration.FeatureSlotPair> featuresRcs = new HashSet<>();
+        featuresRcs.add(new ImsFeatureConfiguration.FeatureSlotPair(0, ImsFeature.FEATURE_RCS));
+        featuresRcs.add(new ImsFeatureConfiguration.FeatureSlotPair(1, ImsFeature.FEATURE_RCS));
+        HashSet<ImsFeatureConfiguration.FeatureSlotPair> allFeatures = new HashSet<>(featuresMmTel);
+        allFeatures.addAll(featuresRcs);
+        // Set CarrierConfig default package name and make it available as the CarrierConfig.
+        setConfigCarrierStringMmTel(0, TEST_CARRIER_DEFAULT_NAME.getPackageName());
+        // TEST_CARRIER_DEFAULT_NAME isnt configured for MMTEL on slot 1.
+        featuresMmTel.remove(new ImsFeatureConfiguration.FeatureSlotPair(1,
+                ImsFeature.FEATURE_MMTEL));
+        setConfigCarrierStringRcs(0, TEST_CARRIER_2_DEFAULT_NAME.getPackageName());
+        setConfigCarrierStringRcs(1, TEST_CARRIER_2_DEFAULT_NAME.getPackageName());
+        List<ResolveInfo> info = new ArrayList<>();
+        info.add(getResolveInfo(TEST_CARRIER_DEFAULT_NAME, new HashSet<>(), true));
+        info.add(getResolveInfo(TEST_CARRIER_2_DEFAULT_NAME, new HashSet<>(), true));
+        setupPackageQuery(info);
+        ImsServiceController deviceController = mock(ImsServiceController.class);
+        ImsServiceController carrierController1 = mock(ImsServiceController.class);
+        ImsServiceController carrierController2 = mock(ImsServiceController.class);
+        setImsServiceControllerFactory(deviceController, carrierController1, carrierController2);
+
+        // Start bind to carrier service
+        startBindCarrierConfigAlreadySet();
+        // setup features response
+        // emulate mMockQueryManager returning information about pending queries.
+        when(mMockQueryManager.isQueryInProgress()).thenReturn(true);
+        setupDynamicQueryFeatures(TEST_CARRIER_DEFAULT_NAME, allFeatures, 1);
+        when(mMockQueryManager.isQueryInProgress()).thenReturn(false);
+        setupDynamicQueryFeatures(TEST_CARRIER_2_DEFAULT_NAME, allFeatures, 1);
+
+        verify(deviceController, never()).bind(any());
+        verify(deviceController, never()).unbind();
+        verify(carrierController1).bind(featuresMmTel);
+        verify(carrierController1, never()).unbind();
+        verify(carrierController2).bind(featuresRcs);
+        verify(carrierController2, never()).unbind();
+        assertEquals(TEST_CARRIER_DEFAULT_NAME, carrierController1.getComponentName());
+        assertEquals(TEST_CARRIER_2_DEFAULT_NAME, carrierController2.getComponentName());
+    }
+
+    /**
      * Creates a carrier ImsService that defines FEATURE_EMERGENCY_MMTEL and ensure that the
      * controller sets this capability.
      */
     @Test
     @SmallTest
     public void testCarrierPackageBindWithEmergencyCalling() throws RemoteException {
-        setupResolver(1/*numSlots*/);
+        setupResolver(1 /*numSlots*/, TEST_DEVICE_DEFAULT_NAME.getPackageName(),
+                TEST_DEVICE_DEFAULT_NAME.getPackageName());
         // Set CarrierConfig default package name and make it available to the package manager
-        setConfigCarrierString(0, TEST_CARRIER_DEFAULT_NAME.getPackageName());
+        setConfigCarrierStringMmTelRcs(0, TEST_CARRIER_DEFAULT_NAME.getPackageName());
         HashSet<ImsFeatureConfiguration.FeatureSlotPair> features = new HashSet<>();
+        features.add(new ImsFeatureConfiguration.FeatureSlotPair(0,
+                ImsFeature.FEATURE_EMERGENCY_MMTEL));
         features.add(new ImsFeatureConfiguration.FeatureSlotPair(0, ImsFeature.FEATURE_MMTEL));
         features.add(new ImsFeatureConfiguration.FeatureSlotPair(0, ImsFeature.FEATURE_RCS));
         setupPackageQuery(TEST_CARRIER_DEFAULT_NAME, new HashSet<>(), true);
         ImsServiceController controller = setupController();
 
-        startBind();
+        startBindCarrierConfigAlreadySet();
         setupDynamicQueryFeatures(TEST_CARRIER_DEFAULT_NAME, features, 1);
 
         verify(controller).bind(features);
@@ -218,22 +354,52 @@
     }
 
     /**
+     * Creates a carrier ImsService that defines FEATURE_EMERGENCY_MMTEL but not FEATURE_MMTEL and
+     * ensure that the controller doesn't set FEATURE_EMERGENCY_MMTEL.
+     */
+    @Test
+    @SmallTest
+    public void testCarrierPackageBindWithEmergencyButNotMmtel() throws RemoteException {
+        setupResolver(1 /*numSlots*/, TEST_DEVICE_DEFAULT_NAME.getPackageName(),
+                TEST_DEVICE_DEFAULT_NAME.getPackageName());
+        // Set CarrierConfig default package name and make it available to the package manager
+        setConfigCarrierStringMmTelRcs(0, TEST_CARRIER_DEFAULT_NAME.getPackageName());
+        HashSet<ImsFeatureConfiguration.FeatureSlotPair> features = new HashSet<>();
+        features.add(new ImsFeatureConfiguration.FeatureSlotPair(0,
+                ImsFeature.FEATURE_EMERGENCY_MMTEL));
+        features.add(new ImsFeatureConfiguration.FeatureSlotPair(0, ImsFeature.FEATURE_RCS));
+        setupPackageQuery(TEST_CARRIER_DEFAULT_NAME, new HashSet<>(), true);
+        ImsServiceController controller = setupController();
+
+        startBindCarrierConfigAlreadySet();
+        setupDynamicQueryFeatures(TEST_CARRIER_DEFAULT_NAME, features, 1);
+
+        // We will not bind with FEATURE_EMERGENCY_MMTEL
+        features.remove(new ImsFeatureConfiguration.FeatureSlotPair(0,
+                ImsFeature.FEATURE_EMERGENCY_MMTEL));
+        verify(controller).bind(features);
+        verify(controller, never()).unbind();
+        assertEquals(TEST_CARRIER_DEFAULT_NAME, controller.getComponentName());
+    }
+
+    /**
      * Creates a carrier ImsService that does not report FEATURE_EMERGENCY_MMTEL and then update the
      * ImsService to define it. Ensure that the controller sets this capability once enabled.
      */
     @Test
     @SmallTest
     public void testCarrierPackageChangeEmergencyCalling() throws RemoteException {
-        setupResolver(1/*numSlots*/);
+        setupResolver(1 /*numSlots*/, TEST_DEVICE_DEFAULT_NAME.getPackageName(),
+                TEST_DEVICE_DEFAULT_NAME.getPackageName());
         // Set CarrierConfig default package name and make it available to the package manager
-        setConfigCarrierString(0, TEST_CARRIER_DEFAULT_NAME.getPackageName());
+        setConfigCarrierStringMmTelRcs(0, TEST_CARRIER_DEFAULT_NAME.getPackageName());
         HashSet<ImsFeatureConfiguration.FeatureSlotPair> features = new HashSet<>();
         features.add(new ImsFeatureConfiguration.FeatureSlotPair(0, ImsFeature.FEATURE_MMTEL));
         setupPackageQuery(TEST_CARRIER_DEFAULT_NAME, new HashSet<>(), true);
         ImsServiceController controller = setupController();
 
         // Bind without emergency calling
-        startBind();
+        startBindCarrierConfigAlreadySet();
         setupDynamicQueryFeatures(TEST_CARRIER_DEFAULT_NAME, features, 1);
         verify(controller).bind(features);
         verify(controller, never()).unbind();
@@ -258,16 +424,17 @@
     @Test
     @SmallTest
     public void testDontBindWhenNullCarrierPackage() throws RemoteException {
-        setupResolver(1/*numSlots*/);
+        setupResolver(1 /*numSlots*/, TEST_DEVICE_DEFAULT_NAME.getPackageName(),
+                TEST_DEVICE_DEFAULT_NAME.getPackageName());
         setupPackageQuery(TEST_CARRIER_DEFAULT_NAME, new HashSet<>(), true);
         ImsServiceController controller = setupController();
 
         // Set the CarrierConfig string to null so that ImsResolver will not bind to the available
         // Services
-        setConfigCarrierString(0, null);
-        startBind();
+        setConfigCarrierStringMmTelRcs(0, null);
+        startBindCarrierConfigAlreadySet();
 
-        waitForHandlerAction(mTestImsResolver.getHandler(), TEST_TIMEOUT);
+        mLooper.processAllMessages();
         verify(mMockQueryManager, never()).startQuery(any(), any());
         verify(controller, never()).bind(any());
         verify(controller, never()).unbind();
@@ -280,7 +447,8 @@
     @Test
     @SmallTest
     public void testDevicePackageBind() throws RemoteException {
-        setupResolver(1/*numSlots*/);
+        setupResolver(1 /*numSlots*/, TEST_DEVICE_DEFAULT_NAME.getPackageName(),
+                TEST_DEVICE_DEFAULT_NAME.getPackageName());
         List<ResolveInfo> info = new ArrayList<>();
         Set<String> features = new HashSet<>();
         features.add(ImsResolver.METADATA_MMTEL_FEATURE);
@@ -292,9 +460,301 @@
         ImsServiceController controller = setupController();
 
 
-        startBind();
-        // Wait to make sure that there are no dynamic queries that are being completed.
-        waitForHandlerAction(mTestImsResolver.getHandler(), TEST_TIMEOUT);
+        startBindNoCarrierConfig(1);
+        mLooper.processAllMessages();
+
+        // There is no carrier override set, so make sure that the ImsServiceController binds
+        // to all SIMs.
+        HashSet<ImsFeatureConfiguration.FeatureSlotPair> featureSet = convertToHashSet(features, 0);
+        verify(controller).bind(featureSet);
+        verify(controller, never()).unbind();
+        verify(mMockQueryManager, never()).startQuery(any(), any());
+        assertEquals(TEST_DEVICE_DEFAULT_NAME, controller.getComponentName());
+    }
+
+    /**
+     * Test that the ImsService corresponding to the default device ImsService package name is
+     * bound and when there is a configuration change from two SIMs to one, the features are
+     * updated correctly.
+     */
+    @Test
+    @SmallTest
+    public void testDevicePackageBind_MsimToOneSim() throws RemoteException {
+        setupResolver(2 /*numSlots*/, TEST_DEVICE_DEFAULT_NAME.getPackageName(),
+                TEST_DEVICE_DEFAULT_NAME.getPackageName());
+        List<ResolveInfo> info = new ArrayList<>();
+        Set<String> features = new HashSet<>();
+        features.add(ImsResolver.METADATA_MMTEL_FEATURE);
+        features.add(ImsResolver.METADATA_RCS_FEATURE);
+        // Use device default package, which will load the ImsService that the device provides
+        info.add(getResolveInfo(TEST_DEVICE_DEFAULT_NAME, features, true));
+        info.add(getResolveInfo(TEST_CARRIER_DEFAULT_NAME, new HashSet<>(), true));
+        setupPackageQuery(info);
+        ImsServiceController controller = setupController();
+
+
+        startBindNoCarrierConfig(1);
+        mLooper.processAllMessages();
+
+        // There is no carrier override set, so make sure that the ImsServiceController binds
+        // to all SIMs.
+        HashSet<ImsFeatureConfiguration.FeatureSlotPair> featureSet = convertToHashSet(features, 0);
+        featureSet.addAll(convertToHashSet(features, 1));
+        verify(controller).bind(featureSet);
+        verify(controller, never()).unbind();
+        verify(mMockQueryManager, never()).startQuery(any(), any());
+        assertEquals(TEST_DEVICE_DEFAULT_NAME, controller.getComponentName());
+
+        // Change number of SIMs and verify the features in the ImsServiceController are changed
+        // as well
+        PhoneConfigurationManager.notifyMultiSimConfigChange(1);
+        mLooper.processAllMessages();
+        featureSet = convertToHashSet(features, 0);
+        verify(controller).changeImsServiceFeatures(featureSet);
+        verify(controller, never()).unbind();
+    }
+
+    /**
+     * Test that the ImsService corresponding to the default device ImsService package name is
+     * bound and when there is a configuration change from one to two SIMs, the features are
+     * updated correctly.
+     */
+    @Test
+    @SmallTest
+    public void testDevicePackageBind_OneSimToMsim() throws RemoteException {
+        setupResolver(1 /*numSlots*/, TEST_DEVICE_DEFAULT_NAME.getPackageName(),
+                TEST_DEVICE_DEFAULT_NAME.getPackageName());
+        List<ResolveInfo> info = new ArrayList<>();
+        Set<String> features = new HashSet<>();
+        features.add(ImsResolver.METADATA_MMTEL_FEATURE);
+        features.add(ImsResolver.METADATA_RCS_FEATURE);
+        // Use device default package, which will load the ImsService that the device provides
+        info.add(getResolveInfo(TEST_DEVICE_DEFAULT_NAME, features, true));
+        info.add(getResolveInfo(TEST_CARRIER_DEFAULT_NAME, new HashSet<>(), true));
+        setupPackageQuery(info);
+        ImsServiceController controller = setupController();
+
+
+        startBindNoCarrierConfig(1);
+        mLooper.processAllMessages();
+
+        // There is no carrier override set, so make sure that the ImsServiceController binds
+        // to all SIMs.
+        HashSet<ImsFeatureConfiguration.FeatureSlotPair> featureSet = convertToHashSet(features, 0);
+        verify(controller).bind(featureSet);
+        verify(controller, never()).unbind();
+        verify(mMockQueryManager, never()).startQuery(any(), any());
+        assertEquals(TEST_DEVICE_DEFAULT_NAME, controller.getComponentName());
+
+        // Change number of SIMs and verify the features in the ImsServiceController are changed
+        // as well
+        PhoneConfigurationManager.notifyMultiSimConfigChange(2);
+        // Carrier config changed should happen for slot 1 (independent of carrier ImsService)
+        sendCarrierConfigChanged(1, 1);
+        featureSet.addAll(convertToHashSet(features, 1));
+        verify(controller).changeImsServiceFeatures(featureSet);
+        verify(controller, never()).unbind();
+    }
+
+    /**
+     * Test that the dynamic ims services are bound in the event that the user is not yet unlocked
+     * but the carrier config changed event is fired.
+     * @throws RemoteException
+     */
+    @Test
+    @SmallTest
+    public void testDeviceDynamicQueryBindsOnCarrierConfigChanged() throws RemoteException {
+        //Set package names with no features set in metadata
+        List<ResolveInfo> info = new ArrayList<>();
+        info.add(getResolveInfo(TEST_DEVICE_DEFAULT_NAME, new HashSet<>(), true));
+        info.add(getResolveInfo(TEST_DEVICE2_DEFAULT_NAME, new HashSet<>(), true));
+        info.add(getResolveInfo(TEST_CARRIER_DEFAULT_NAME, new HashSet<>(), true));
+        setupPackageQuery(info);
+
+        //setupResolver
+        setupResolver(1 /*numSlots*/, TEST_DEVICE_DEFAULT_NAME.getPackageName(),
+                TEST_DEVICE2_DEFAULT_NAME.getPackageName());
+
+        //Set controllers
+        ImsServiceController deviceController = mock(ImsServiceController.class);
+        ImsServiceController deviceController2 = mock(ImsServiceController.class);
+        ImsServiceController carrierController = mock(ImsServiceController.class);
+
+        Map<String, ImsServiceController> controllerMap = new ArrayMap<>();
+        controllerMap.put(TEST_DEVICE_DEFAULT_NAME.getPackageName(), deviceController);
+        controllerMap.put(TEST_DEVICE2_DEFAULT_NAME.getPackageName(), deviceController2);
+        controllerMap.put(TEST_CARRIER_DEFAULT_NAME.getPackageName(), carrierController);
+        setImsServiceControllerFactory(controllerMap);
+
+        //Set features to device ims services
+        Set<ImsFeatureConfiguration.FeatureSlotPair> deviceFeatures1 =
+                convertToFeatureSlotPairs(0, ImsResolver.METADATA_EMERGENCY_MMTEL_FEATURE,
+                        ImsResolver.METADATA_MMTEL_FEATURE);
+
+        Set<ImsFeatureConfiguration.FeatureSlotPair> deviceFeatures2 =
+                convertToFeatureSlotPairs(0, ImsResolver.METADATA_RCS_FEATURE);
+
+        startBindNoCarrierConfig(1);
+        mLooper.processAllMessages();
+        // ensure that startQuery was called
+        verify(mMockQueryManager, times(1)).startQuery(eq(TEST_DEVICE_DEFAULT_NAME),
+                any(String.class));
+
+        verify(mMockQueryManager, times(1)).startQuery(eq(TEST_DEVICE2_DEFAULT_NAME),
+                any(String.class));
+
+        mDynamicQueryListener.onComplete(TEST_DEVICE_DEFAULT_NAME, deviceFeatures1);
+        mDynamicQueryListener.onComplete(TEST_DEVICE2_DEFAULT_NAME, deviceFeatures2);
+        mLooper.processAllMessages();
+
+        verify(deviceController, times(2)).bind(eq(deviceFeatures1));
+        verify(deviceController2, times(1)).bind(eq(deviceFeatures2));
+    }
+
+    /**
+     * Test that when a device and carrier override package are set, both ImsServices are bound.
+     * Verify that the carrier ImsService features are created and the device default features
+     * are created for all features that are not covered by the carrier ImsService. When the device
+     * configuration is changed from one SIM to MSIM, ensure that the capabilities are reflected.
+     */
+    @Test
+    @SmallTest
+    public void testDeviceAndCarrierPackageBind_OneSimToMsim() throws RemoteException {
+        setupResolver(1 /*numSlots*/, TEST_DEVICE_DEFAULT_NAME.getPackageName(),
+                TEST_DEVICE_DEFAULT_NAME.getPackageName());
+        List<ResolveInfo> info = new ArrayList<>();
+        // set device features to MMTEL, RCS
+        Set<String> deviceFeatures = new HashSet<>();
+        deviceFeatures.add(ImsResolver.METADATA_MMTEL_FEATURE);
+        deviceFeatures.add(ImsResolver.METADATA_RCS_FEATURE);
+        // Set the carrier override package for slot 0
+        setConfigCarrierStringMmTelRcs(0, TEST_CARRIER_DEFAULT_NAME.getPackageName());
+        HashSet<ImsFeatureConfiguration.FeatureSlotPair> carrierFeatures = new HashSet<>();
+        // Carrier service only supports RCS on slot 0
+        carrierFeatures.add(new ImsFeatureConfiguration.FeatureSlotPair(0, ImsFeature.FEATURE_RCS));
+        // Use device default package, which will load the ImsService that the device provides
+        info.add(getResolveInfo(TEST_DEVICE_DEFAULT_NAME, deviceFeatures, true));
+        info.add(getResolveInfo(TEST_CARRIER_DEFAULT_NAME, new HashSet<>(), true));
+        // Only return info if not using the compat argument
+        setupPackageQuery(info);
+        ImsServiceController deviceController = mock(ImsServiceController.class);
+        ImsServiceController carrierController = mock(ImsServiceController.class);
+        setImsServiceControllerFactory(deviceController, carrierController);
+
+        startBindCarrierConfigAlreadySet();
+        setupDynamicQueryFeatures(TEST_CARRIER_DEFAULT_NAME, carrierFeatures, 1);
+
+        // Verify that all features that have been defined for the carrier override are bound
+        verify(carrierController).bind(carrierFeatures);
+        verify(carrierController, never()).unbind();
+        assertEquals(TEST_CARRIER_DEFAULT_NAME, carrierController.getComponentName());
+        // Verify that all features that are not defined in the carrier override are bound in the
+        // device controller (including emergency voice for slot 0)
+        HashSet<ImsFeatureConfiguration.FeatureSlotPair> deviceFeatureSet =
+                convertToHashSet(deviceFeatures, 0);
+        deviceFeatureSet.removeAll(carrierFeatures);
+        verify(deviceController).bind(deviceFeatureSet);
+        verify(deviceController, never()).unbind();
+        assertEquals(TEST_DEVICE_DEFAULT_NAME, deviceController.getComponentName());
+
+        // Move to MSIM and verify the features in the ImsServiceController are changed as well
+        PhoneConfigurationManager.notifyMultiSimConfigChange(2);
+        setConfigCarrierStringMmTelRcs(1, TEST_CARRIER_DEFAULT_NAME.getPackageName());
+        carrierFeatures.add(new ImsFeatureConfiguration.FeatureSlotPair(1, ImsFeature.FEATURE_RCS));
+        // Assume that there is a CarrierConfig change that kicks off query to carrier service.
+        sendCarrierConfigChanged(1, 1);
+        setupDynamicQueryFeatures(TEST_CARRIER_DEFAULT_NAME, carrierFeatures, 2);
+        verify(carrierController).changeImsServiceFeatures(carrierFeatures);
+        deviceFeatureSet = convertToHashSet(deviceFeatures, 0);
+        deviceFeatureSet.addAll(convertToHashSet(deviceFeatures, 1));
+        deviceFeatureSet.removeAll(carrierFeatures);
+        verify(deviceController).changeImsServiceFeatures(deviceFeatureSet);
+        verify(deviceController, never()).unbind();
+    }
+
+    /**
+     * Test that when a device and carrier override package are set, both ImsServices are bound.
+     * Verify that the carrier ImsService features are created and the device default features
+     * are created for all features that are not covered by the carrier ImsService. When the device
+     * configuration is changed from one SIM to MSIM, ensure that the capabilities are reflected.
+     */
+    @Test
+    @SmallTest
+    public void testDeviceAndCarrierPackageBind_MsimToOneSim() throws RemoteException {
+        setupResolver(2 /*numSlots*/, TEST_DEVICE_DEFAULT_NAME.getPackageName(),
+                TEST_DEVICE_DEFAULT_NAME.getPackageName());
+        List<ResolveInfo> info = new ArrayList<>();
+        // set device features to MMTEL, RCS
+        Set<String> deviceFeatures = new HashSet<>();
+        deviceFeatures.add(ImsResolver.METADATA_MMTEL_FEATURE);
+        deviceFeatures.add(ImsResolver.METADATA_RCS_FEATURE);
+        // Set the carrier override package for slot 0, slot 1 as RCS
+        setConfigCarrierStringMmTelRcs(0, TEST_CARRIER_DEFAULT_NAME.getPackageName());
+        setConfigCarrierStringMmTelRcs(1, TEST_CARRIER_DEFAULT_NAME.getPackageName());
+        HashSet<ImsFeatureConfiguration.FeatureSlotPair> carrierFeatures = new HashSet<>();
+        carrierFeatures.add(new ImsFeatureConfiguration.FeatureSlotPair(0, ImsFeature.FEATURE_RCS));
+        carrierFeatures.add(new ImsFeatureConfiguration.FeatureSlotPair(1, ImsFeature.FEATURE_RCS));
+        // Use device default package, which will load the ImsService that the device provides
+        info.add(getResolveInfo(TEST_DEVICE_DEFAULT_NAME, deviceFeatures, true));
+        info.add(getResolveInfo(TEST_CARRIER_DEFAULT_NAME, new HashSet<>(), true));
+        // Only return info if not using the compat argument
+        setupPackageQuery(info);
+        ImsServiceController deviceController = mock(ImsServiceController.class);
+        ImsServiceController carrierController = mock(ImsServiceController.class);
+        setImsServiceControllerFactory(deviceController, carrierController);
+
+        startBindCarrierConfigAlreadySet();
+        setupDynamicQueryFeatures(TEST_CARRIER_DEFAULT_NAME, carrierFeatures, 1);
+
+        // Verify that all features that have been defined for the carrier override are bound
+        verify(carrierController).bind(carrierFeatures);
+        verify(carrierController, never()).unbind();
+        assertEquals(TEST_CARRIER_DEFAULT_NAME, carrierController.getComponentName());
+        // Verify that all features that are not defined in the carrier override are bound in the
+        // device controller (including emergency voice for slot 0)
+        HashSet<ImsFeatureConfiguration.FeatureSlotPair> deviceFeatureSet =
+                convertToHashSet(deviceFeatures, 0);
+        deviceFeatureSet.addAll(convertToHashSet(deviceFeatures, 1));
+        deviceFeatureSet.removeAll(carrierFeatures);
+        verify(deviceController).bind(deviceFeatureSet);
+        verify(deviceController, never()).unbind();
+        assertEquals(TEST_DEVICE_DEFAULT_NAME, deviceController.getComponentName());
+
+        // Move to single SIM and verify the features in the ImsServiceController are changed as
+        // well.
+        PhoneConfigurationManager.notifyMultiSimConfigChange(1);
+        mLooper.processAllMessages();
+        carrierFeatures = new HashSet<>();
+        carrierFeatures.add(new ImsFeatureConfiguration.FeatureSlotPair(0, ImsFeature.FEATURE_RCS));
+        verify(carrierController).changeImsServiceFeatures(carrierFeatures);
+        deviceFeatureSet = convertToHashSet(deviceFeatures, 0);
+        deviceFeatureSet.removeAll(carrierFeatures);
+        verify(deviceController).changeImsServiceFeatures(deviceFeatureSet);
+        verify(deviceController, never()).unbind();
+    }
+
+    /**
+     * Test that the ImsService corresponding to the default device ImsService package name is
+     * bound to only RCS if METADATA_EMERGENCY_MMTEL_FEATURE but not METADATA_MMTEL_FEATURE.
+     */
+    @Test
+    @SmallTest
+    public void testDevicePackageInvalidMmTelBind() throws RemoteException {
+        setupResolver(1 /*numSlots*/, TEST_DEVICE_DEFAULT_NAME.getPackageName(),
+                TEST_DEVICE_DEFAULT_NAME.getPackageName());
+        List<ResolveInfo> info = new ArrayList<>();
+        Set<String> features = new HashSet<>();
+        features.add(ImsResolver.METADATA_EMERGENCY_MMTEL_FEATURE);
+        features.add(ImsResolver.METADATA_RCS_FEATURE);
+        // Use device default package, which will load the ImsService that the device provides
+        info.add(getResolveInfo(TEST_DEVICE_DEFAULT_NAME, features, true));
+        info.add(getResolveInfo(TEST_CARRIER_DEFAULT_NAME, new HashSet<>(), true));
+        setupPackageQuery(info);
+        ImsServiceController controller = setupController();
+
+
+        startBindNoCarrierConfig(1);
+        mLooper.processAllMessages();
 
         // There is no carrier override set, so make sure that the ImsServiceController binds
         // to all SIMs.
@@ -313,13 +773,14 @@
     @Test
     @SmallTest
     public void testDeviceAndCarrierPackageBind() throws RemoteException {
-        setupResolver(1/*numSlots*/);
+        setupResolver(1 /*numSlots*/, TEST_DEVICE_DEFAULT_NAME.getPackageName(),
+                TEST_DEVICE_DEFAULT_NAME.getPackageName());
         List<ResolveInfo> info = new ArrayList<>();
         Set<String> deviceFeatures = new HashSet<>();
         deviceFeatures.add(ImsResolver.METADATA_MMTEL_FEATURE);
         deviceFeatures.add(ImsResolver.METADATA_RCS_FEATURE);
         // Set the carrier override package for slot 0
-        setConfigCarrierString(0, TEST_CARRIER_DEFAULT_NAME.getPackageName());
+        setConfigCarrierStringMmTelRcs(0, TEST_CARRIER_DEFAULT_NAME.getPackageName());
         HashSet<ImsFeatureConfiguration.FeatureSlotPair> carrierFeatures = new HashSet<>();
         // Carrier service doesn't support the voice feature.
         carrierFeatures.add(new ImsFeatureConfiguration.FeatureSlotPair(0, ImsFeature.FEATURE_RCS));
@@ -332,7 +793,7 @@
         ImsServiceController carrierController = mock(ImsServiceController.class);
         setImsServiceControllerFactory(deviceController, carrierController);
 
-        startBind();
+        startBindCarrierConfigAlreadySet();
         setupDynamicQueryFeatures(TEST_CARRIER_DEFAULT_NAME, carrierFeatures, 1);
 
         // Verify that all features that have been defined for the carrier override are bound
@@ -343,6 +804,7 @@
         // device controller (including emergency voice for slot 0)
         HashSet<ImsFeatureConfiguration.FeatureSlotPair> deviceFeatureSet =
                 convertToHashSet(deviceFeatures, 0);
+        deviceFeatureSet.removeAll(carrierFeatures);
         verify(deviceController).bind(deviceFeatureSet);
         verify(deviceController, never()).unbind();
         assertEquals(TEST_DEVICE_DEFAULT_NAME, deviceController.getComponentName());
@@ -355,7 +817,8 @@
     @Test
     @SmallTest
     public void testGetDeviceCarrierFeatures() throws RemoteException {
-        setupResolver(2/*numSlots*/);
+        setupResolver(2 /*numSlots*/, TEST_DEVICE_DEFAULT_NAME.getPackageName(),
+                TEST_DEVICE_DEFAULT_NAME.getPackageName());
         ImsServiceController deviceController = mock(ImsServiceController.class);
         ImsServiceController carrierController = mock(ImsServiceController.class);
 
@@ -384,7 +847,8 @@
     @Test
     @SmallTest
     public void testAddDeviceFeatureNoCarrier() throws RemoteException {
-        setupResolver(2/*numSlots*/);
+        setupResolver(2 /*numSlots*/, TEST_DEVICE_DEFAULT_NAME.getPackageName(),
+                TEST_DEVICE_DEFAULT_NAME.getPackageName());
         List<ResolveInfo> info = new ArrayList<>();
         Set<String> features = new HashSet<>();
         features.add(ImsResolver.METADATA_MMTEL_FEATURE);
@@ -393,7 +857,7 @@
         setupPackageQuery(info);
         ImsServiceController controller = setupController();
         // Bind using default features
-        startBind();
+        startBindNoCarrierConfig(2);
         HashSet<ImsFeatureConfiguration.FeatureSlotPair> featureSet =
                 convertToHashSet(features, 0);
         featureSet.addAll(convertToHashSet(features, 1));
@@ -415,18 +879,100 @@
     }
 
     /**
+     * Bind to device ImsServices and change the feature set to include one that is not configured.
+     * Ensure it is not added.
+     */
+    @Test
+    @SmallTest
+    public void testMultipleDeviceAddFeatureNoCarrier() throws RemoteException {
+        setupResolver(2 /*numSlots*/, TEST_DEVICE_DEFAULT_NAME.getPackageName(),
+                TEST_DEVICE2_DEFAULT_NAME.getPackageName());
+        List<ResolveInfo> info = new ArrayList<>();
+        Set<String> featuresController1 = new HashSet<>();
+        featuresController1.add(ImsResolver.METADATA_MMTEL_FEATURE);
+        Set<String> featuresController2 = new HashSet<>();
+        featuresController2.add(ImsResolver.METADATA_RCS_FEATURE);
+        info.add(getResolveInfo(TEST_DEVICE_DEFAULT_NAME, featuresController1, true));
+        info.add(getResolveInfo(TEST_DEVICE2_DEFAULT_NAME, featuresController2, true));
+        setupPackageQuery(info);
+        ImsServiceController deviceController1 = mock(ImsServiceController.class);
+        ImsServiceController deviceController2 = mock(ImsServiceController.class);
+        setImsServiceControllerFactory(deviceController1, deviceController2, null, null);
+        // Bind using default features
+        startBindNoCarrierConfig(2);
+        HashSet<ImsFeatureConfiguration.FeatureSlotPair> featureSet1 =
+                convertToHashSet(featuresController1, 0);
+        featureSet1.addAll(convertToHashSet(featuresController1, 1));
+        HashSet<ImsFeatureConfiguration.FeatureSlotPair> featureSet2 =
+                convertToHashSet(featuresController2, 0);
+        featureSet2.addAll(convertToHashSet(featuresController2, 1));
+        verify(deviceController1).bind(featureSet1);
+        verify(deviceController2).bind(featureSet2);
+
+        // add RCS to features list for device 1
+        Set<String> newFeatures1 = new HashSet<>(featuresController1);
+        newFeatures1.add(ImsResolver.METADATA_RCS_FEATURE);
+        info.clear();
+        info.add(getResolveInfo(TEST_DEVICE_DEFAULT_NAME, newFeatures1, true));
+
+        packageChanged(TEST_DEVICE_DEFAULT_NAME.getPackageName());
+
+        // verify the devices have not changed features (because their configurations are still
+        // the same)
+        verify(deviceController1, times(2)).changeImsServiceFeatures(featureSet1);
+        verify(deviceController2, times(2)).changeImsServiceFeatures(featureSet2);
+    }
+
+    /**
+     * Bind to device ImsService and change the feature set while not supporting that configuration.
+     * Verify that changeImsServiceFeature is called with the original feature set.
+     */
+    @Test
+    @SmallTest
+    public void testAddDeviceFeatureNoCarrierRcsNotSupported() throws RemoteException {
+        setupResolver(2 /*numSlots*/, TEST_DEVICE_DEFAULT_NAME.getPackageName(), "");
+        List<ResolveInfo> info = new ArrayList<>();
+        Set<String> features = new HashSet<>();
+        features.add(ImsResolver.METADATA_MMTEL_FEATURE);
+        // Doesn't include RCS feature by default
+        info.add(getResolveInfo(TEST_DEVICE_DEFAULT_NAME, features, true));
+        setupPackageQuery(info);
+        ImsServiceController controller = setupController();
+        // Bind using default features
+        startBindNoCarrierConfig(2);
+        HashSet<ImsFeatureConfiguration.FeatureSlotPair> featureSet =
+                convertToHashSet(features, 0);
+        featureSet.addAll(convertToHashSet(features, 1));
+        verify(controller).bind(featureSet);
+
+        // add RCS to features list
+        Set<String> newFeatures = new HashSet<>(features);
+        newFeatures.add(ImsResolver.METADATA_RCS_FEATURE);
+        info.clear();
+        info.add(getResolveInfo(TEST_DEVICE_DEFAULT_NAME, newFeatures, true));
+
+        packageChanged(TEST_DEVICE_DEFAULT_NAME.getPackageName());
+
+        // Verify new feature is not added to the device default, since it is not configured.
+        // This happens twice because two CarrierConfigChanged events occur, causing a
+        // changeImsServiceFeatures after bind() and then another after packageChanged.
+        verify(controller, times(2)).changeImsServiceFeatures(featureSet);
+    }
+
+    /**
      * Bind to device ImsService and change the feature set. Verify that changeImsServiceFeature
      * is called with the new feature set on the sub that doesn't include the carrier override.
      */
     @Test
     @SmallTest
     public void testAddDeviceFeatureWithCarrier() throws RemoteException {
-        setupResolver(2/*numSlots*/);
+        setupResolver(2 /*numSlots*/, TEST_DEVICE_DEFAULT_NAME.getPackageName(),
+                TEST_DEVICE_DEFAULT_NAME.getPackageName());
         List<ResolveInfo> info = new ArrayList<>();
         Set<String> deviceFeatures = new HashSet<>();
         deviceFeatures.add(ImsResolver.METADATA_MMTEL_FEATURE);
         // Set the carrier override package for slot 0
-        setConfigCarrierString(0, TEST_CARRIER_DEFAULT_NAME.getPackageName());
+        setConfigCarrierStringMmTelRcs(0, TEST_CARRIER_DEFAULT_NAME.getPackageName());
         HashSet<ImsFeatureConfiguration.FeatureSlotPair> carrierFeatures = new HashSet<>();
         // Carrier service doesn't support the emergency voice feature.
         carrierFeatures.add(new ImsFeatureConfiguration.FeatureSlotPair(0,
@@ -441,7 +987,7 @@
         ImsServiceController carrierController = mock(ImsServiceController.class);
         setImsServiceControllerFactory(deviceController, carrierController);
 
-        startBind();
+        startBindCarrierConfigAlreadySet();
         setupDynamicQueryFeatures(TEST_CARRIER_DEFAULT_NAME, carrierFeatures, 1);
 
         // Verify that all features that have been defined for the carrier override are bound
@@ -454,9 +1000,7 @@
                 convertToHashSet(deviceFeatures, 1);
         deviceFeatureSet.addAll(convertToHashSet(deviceFeatures, 0));
         deviceFeatureSet.removeAll(carrierFeatures);
-        // we will first have bound to device and then the features will change once the dynamic
-        // returns. So, instead of checking the bind parameters, we will check the change parameters
-        verify(deviceController).changeImsServiceFeatures(deviceFeatureSet);
+        verify(deviceController).bind(deviceFeatureSet);
         verify(deviceController, never()).unbind();
         assertEquals(TEST_DEVICE_DEFAULT_NAME, deviceController.getComponentName());
 
@@ -478,65 +1022,79 @@
         // remove carrier overrides for slot 0
         newDeviceFeatureSet.removeAll(carrierFeatures);
         verify(deviceController).changeImsServiceFeatures(newDeviceFeatureSet);
-        verify(carrierController, never()).changeImsServiceFeatures(any());
+        // features should be the same as before, ImsServiceController will disregard change if it
+        // is the same feature set anyway.
+        verify(carrierController).changeImsServiceFeatures(carrierFeatures);
     }
 
     /**
-     * Bind to device ImsService and change the feature set of the carrier overridden ImsService.
+     * Bind to device ImsServices and change the feature set of the carrier overridden ImsService.
      * Verify that the device and carrier ImsServices are changed.
      */
     @Test
     @SmallTest
     public void testAddCarrierFeature() throws RemoteException {
-        setupResolver(2/*numSlots*/);
+        setupResolver(2 /*numSlots*/, TEST_DEVICE_DEFAULT_NAME.getPackageName(),
+                TEST_DEVICE2_DEFAULT_NAME.getPackageName());
         List<ResolveInfo> info = new ArrayList<>();
-        Set<String> deviceFeatures = new HashSet<>();
-        deviceFeatures.add(ImsResolver.METADATA_MMTEL_FEATURE);
-        deviceFeatures.add(ImsResolver.METADATA_RCS_FEATURE);
+        Set<String> deviceFeatures1 = new HashSet<>();
+        deviceFeatures1.add(ImsResolver.METADATA_MMTEL_FEATURE);
+        Set<String> deviceFeatures2 = new HashSet<>();
+        deviceFeatures2.add(ImsResolver.METADATA_RCS_FEATURE);
+        Set<String> allDeviceFeatures = new HashSet<>(deviceFeatures1);
+        allDeviceFeatures.addAll(deviceFeatures2);
         // Set the carrier override package for slot 0
-        setConfigCarrierString(0, TEST_CARRIER_DEFAULT_NAME.getPackageName());
+        setConfigCarrierStringMmTelRcs(0, TEST_CARRIER_DEFAULT_NAME.getPackageName());
         HashSet<ImsFeatureConfiguration.FeatureSlotPair> carrierFeatures = new HashSet<>();
         // Carrier service doesn't support the emergency voice feature.
         carrierFeatures.add(new ImsFeatureConfiguration.FeatureSlotPair(0,
                 ImsFeature.FEATURE_MMTEL));
-        // Use device default package, which will load the ImsService that the device provides
-        info.add(getResolveInfo(TEST_DEVICE_DEFAULT_NAME, deviceFeatures, true));
+        // Use device default packages, which will load the ImsServices that the device provides
+        info.add(getResolveInfo(TEST_DEVICE_DEFAULT_NAME, allDeviceFeatures, true));
+        info.add(getResolveInfo(TEST_DEVICE2_DEFAULT_NAME, allDeviceFeatures, true));
         info.add(getResolveInfo(TEST_CARRIER_DEFAULT_NAME, new HashSet<>(), true));
         setupPackageQuery(info);
-        ImsServiceController deviceController = mock(ImsServiceController.class);
+        ImsServiceController deviceController1 = mock(ImsServiceController.class);
+        ImsServiceController deviceController2 = mock(ImsServiceController.class);
         ImsServiceController carrierController = mock(ImsServiceController.class);
-        setImsServiceControllerFactory(deviceController, carrierController);
+        setImsServiceControllerFactory(deviceController1, deviceController2, carrierController,
+                null);
 
-        startBind();
+        startBindCarrierConfigAlreadySet();
         setupDynamicQueryFeatures(TEST_CARRIER_DEFAULT_NAME, carrierFeatures, 1);
         // Verify that all features that have been defined for the carrier override are bound
         verify(carrierController).bind(carrierFeatures);
         verify(carrierController, never()).unbind();
         assertEquals(TEST_CARRIER_DEFAULT_NAME, carrierController.getComponentName());
         // Verify that all features that are not defined in the carrier override are bound in the
-        // device controller (including emergency voice for slot 0)
-        HashSet<ImsFeatureConfiguration.FeatureSlotPair> deviceFeatureSet =
-                convertToHashSet(deviceFeatures, 1);
-        deviceFeatureSet.addAll(convertToHashSet(deviceFeatures, 0));
-        deviceFeatureSet.removeAll(carrierFeatures);
-        // device ImsService will bind with all of its defined features first and then when the
-        // carrier query comes back, it will change. So, checking change instead of bind here.
-        verify(deviceController).changeImsServiceFeatures(deviceFeatureSet);
-        verify(deviceController, never()).unbind();
-        assertEquals(TEST_DEVICE_DEFAULT_NAME, deviceController.getComponentName());
+        // device controllers.
+        HashSet<ImsFeatureConfiguration.FeatureSlotPair> deviceFeatureSet1 =
+                convertToHashSet(deviceFeatures1, 1);
+        deviceFeatureSet1.removeAll(carrierFeatures);
+        verify(deviceController1).bind(deviceFeatureSet1);
+        verify(deviceController1, never()).unbind();
+        HashSet<ImsFeatureConfiguration.FeatureSlotPair> deviceFeatureSet2 =
+                convertToHashSet(deviceFeatures2, 0);
+        deviceFeatureSet2.addAll(convertToHashSet(deviceFeatures2, 1));
+        deviceFeatureSet2.removeAll(carrierFeatures);
+        verify(deviceController2).bind(deviceFeatureSet2);
+        verify(deviceController2, never()).unbind();
+        assertEquals(TEST_DEVICE_DEFAULT_NAME, deviceController1.getComponentName());
+        assertEquals(TEST_DEVICE2_DEFAULT_NAME, deviceController2.getComponentName());
 
         // add RCS to carrier features list
         carrierFeatures.add(new ImsFeatureConfiguration.FeatureSlotPair(0, ImsFeature.FEATURE_RCS));
 
-        // Tell the package manager that a new device feature is installed
+        // A new carrier feature is installed
         packageChanged(TEST_CARRIER_DEFAULT_NAME.getPackageName());
         setupDynamicQueryFeatures(TEST_CARRIER_DEFAULT_NAME, carrierFeatures, 2);
 
         //Verify new feature is added to the carrier override.
-        // add all features for slot 0
         verify(carrierController).changeImsServiceFeatures(carrierFeatures);
-        deviceFeatureSet.removeAll(carrierFeatures);
-        verify(deviceController).changeImsServiceFeatures(deviceFeatureSet);
+        deviceFeatureSet1.removeAll(carrierFeatures);
+        verify(deviceController1, times(2)).changeImsServiceFeatures(deviceFeatureSet1);
+        deviceFeatureSet2.removeAll(carrierFeatures);
+        verify(deviceController2).changeImsServiceFeatures(deviceFeatureSet2);
     }
 
     /**
@@ -547,13 +1105,14 @@
     @Test
     @SmallTest
     public void testRemoveCarrierFeature() throws RemoteException {
-        setupResolver(2/*numSlots*/);
+        setupResolver(2 /*numSlots*/, TEST_DEVICE_DEFAULT_NAME.getPackageName(),
+                TEST_DEVICE_DEFAULT_NAME.getPackageName());
         List<ResolveInfo> info = new ArrayList<>();
         Set<String> deviceFeatures = new HashSet<>();
         deviceFeatures.add(ImsResolver.METADATA_MMTEL_FEATURE);
         deviceFeatures.add(ImsResolver.METADATA_RCS_FEATURE);
         // Set the carrier override package for slot 0
-        setConfigCarrierString(0, TEST_CARRIER_DEFAULT_NAME.getPackageName());
+        setConfigCarrierStringMmTelRcs(0, TEST_CARRIER_DEFAULT_NAME.getPackageName());
         HashSet<ImsFeatureConfiguration.FeatureSlotPair> carrierFeatures = new HashSet<>();
         // Carrier service doesn't support the voice feature.
         carrierFeatures.add(new ImsFeatureConfiguration.FeatureSlotPair(0, ImsFeature.FEATURE_RCS));
@@ -565,7 +1124,7 @@
         ImsServiceController carrierController = mock(ImsServiceController.class);
         setImsServiceControllerFactory(deviceController, carrierController);
 
-        startBind();
+        startBindCarrierConfigAlreadySet();
         setupDynamicQueryFeatures(TEST_CARRIER_DEFAULT_NAME, carrierFeatures, 1);
         // Verify that all features that have been defined for the carrier override are bound
         verify(carrierController).bind(carrierFeatures);
@@ -577,9 +1136,7 @@
                 convertToHashSet(deviceFeatures, 1);
         deviceFeatureSet.addAll(convertToHashSet(deviceFeatures, 0));
         deviceFeatureSet.removeAll(carrierFeatures);
-        // we will first have bound to device and then the features will change once the dynamic
-        // returns. So, instead of checking the bind parameters, we will check the change parameters
-        verify(deviceController).changeImsServiceFeatures(deviceFeatureSet);
+        verify(deviceController).bind(deviceFeatureSet);
         verify(deviceController, never()).unbind();
         assertEquals(TEST_DEVICE_DEFAULT_NAME, deviceController.getComponentName());
 
@@ -587,7 +1144,7 @@
         carrierFeatures.clear();
         carrierFeatures.add(new ImsFeatureConfiguration.FeatureSlotPair(0,
                 ImsFeature.FEATURE_MMTEL));
-        // Tell the package manager that a new device feature is installed
+        //  new carrier feature has been removed
         packageChanged(TEST_CARRIER_DEFAULT_NAME.getPackageName());
         setupDynamicQueryFeatures(TEST_CARRIER_DEFAULT_NAME, carrierFeatures, 2);
 
@@ -609,13 +1166,14 @@
     @Test
     @SmallTest
     public void testInstallCarrierImsService() throws RemoteException {
-        setupResolver(2/*numSlots*/);
+        setupResolver(2 /*numSlots*/, TEST_DEVICE_DEFAULT_NAME.getPackageName(),
+                TEST_DEVICE_DEFAULT_NAME.getPackageName());
         List<ResolveInfo> info = new ArrayList<>();
         Set<String> deviceFeatures = new HashSet<>();
         deviceFeatures.add(ImsResolver.METADATA_MMTEL_FEATURE);
         deviceFeatures.add(ImsResolver.METADATA_RCS_FEATURE);
         // Set the carrier override package for slot 0
-        setConfigCarrierString(0, TEST_CARRIER_DEFAULT_NAME.getPackageName());
+        setConfigCarrierStringMmTelRcs(0, TEST_CARRIER_DEFAULT_NAME.getPackageName());
         // Use device default package, which will load the ImsService that the device provides
         info.add(getResolveInfo(TEST_DEVICE_DEFAULT_NAME, deviceFeatures, true));
         setupPackageQuery(info);
@@ -623,7 +1181,7 @@
         ImsServiceController carrierController = mock(ImsServiceController.class);
         setImsServiceControllerFactory(deviceController, carrierController);
 
-        startBind();
+        startBindCarrierConfigAlreadySet();
 
         HashSet<ImsFeatureConfiguration.FeatureSlotPair> carrierFeatures = new HashSet<>();
         // Carrier service doesn't support the voice feature.
@@ -651,13 +1209,14 @@
     @Test
     @SmallTest
     public void testUninstallCarrierImsService() throws RemoteException {
-        setupResolver(2/*numSlots*/);
+        setupResolver(2 /*numSlots*/, TEST_DEVICE_DEFAULT_NAME.getPackageName(),
+                TEST_DEVICE_DEFAULT_NAME.getPackageName());
         List<ResolveInfo> info = new ArrayList<>();
         Set<String> deviceFeatures = new HashSet<>();
         deviceFeatures.add(ImsResolver.METADATA_MMTEL_FEATURE);
         deviceFeatures.add(ImsResolver.METADATA_RCS_FEATURE);
         // Set the carrier override package for slot 0
-        setConfigCarrierString(0, TEST_CARRIER_DEFAULT_NAME.getPackageName());
+        setConfigCarrierStringMmTelRcs(0, TEST_CARRIER_DEFAULT_NAME.getPackageName());
         HashSet<ImsFeatureConfiguration.FeatureSlotPair> carrierFeatures = new HashSet<>();
         // Carrier service doesn't support the voice feature.
         carrierFeatures.add(new ImsFeatureConfiguration.FeatureSlotPair(0, ImsFeature.FEATURE_RCS));
@@ -669,7 +1228,7 @@
         ImsServiceController carrierController = mock(ImsServiceController.class);
         setImsServiceControllerFactory(deviceController, carrierController);
 
-        startBind();
+        startBindCarrierConfigAlreadySet();
         setupDynamicQueryFeatures(TEST_CARRIER_DEFAULT_NAME, carrierFeatures, 1);
 
         // Tell the package manager that carrier app is uninstalled
@@ -695,13 +1254,14 @@
     @Test
     @SmallTest
     public void testCarrierConfigChangedToNone() throws RemoteException {
-        setupResolver(2/*numSlots*/);
+        setupResolver(2 /*numSlots*/, TEST_DEVICE_DEFAULT_NAME.getPackageName(),
+                TEST_DEVICE_DEFAULT_NAME.getPackageName());
         List<ResolveInfo> info = new ArrayList<>();
         Set<String> deviceFeatures = new HashSet<>();
         deviceFeatures.add(ImsResolver.METADATA_MMTEL_FEATURE);
         deviceFeatures.add(ImsResolver.METADATA_RCS_FEATURE);
         // Set the carrier override package for slot 0
-        setConfigCarrierString(0, TEST_CARRIER_DEFAULT_NAME.getPackageName());
+        setConfigCarrierStringMmTelRcs(0, TEST_CARRIER_DEFAULT_NAME.getPackageName());
         HashSet<ImsFeatureConfiguration.FeatureSlotPair> carrierFeatures = new HashSet<>();
         // Carrier service doesn't support the voice feature.
         carrierFeatures.add(new ImsFeatureConfiguration.FeatureSlotPair(0, ImsFeature.FEATURE_RCS));
@@ -713,14 +1273,11 @@
         ImsServiceController carrierController = mock(ImsServiceController.class);
         setImsServiceControllerFactory(deviceController, carrierController);
 
-        startBind();
+        startBindCarrierConfigAlreadySet();
         setupDynamicQueryFeatures(TEST_CARRIER_DEFAULT_NAME, carrierFeatures, 1);
 
-        setConfigCarrierString(0, null);
-        Intent carrierConfigIntent = new Intent();
-        carrierConfigIntent.putExtra(CarrierConfigManager.EXTRA_SLOT_INDEX, 0);
-        mTestCarrierConfigReceiver.onReceive(null, carrierConfigIntent);
-        waitForHandlerAction(mTestImsResolver.getHandler(), TEST_TIMEOUT);
+        setConfigCarrierStringMmTelRcs(0, null);
+        sendCarrierConfigChanged(0, 0);
 
         // Verify that the carrier controller is unbound
         verify(carrierController).unbind();
@@ -740,13 +1297,14 @@
     @Test
     @SmallTest
     public void testCarrierConfigChangedToAnotherService() throws RemoteException {
-        setupResolver(2/*numSlots*/);
+        setupResolver(2 /*numSlots*/, TEST_DEVICE_DEFAULT_NAME.getPackageName(),
+                TEST_DEVICE_DEFAULT_NAME.getPackageName());
         List<ResolveInfo> info = new ArrayList<>();
         Set<String> deviceFeatures = new HashSet<>();
         deviceFeatures.add(ImsResolver.METADATA_MMTEL_FEATURE);
         deviceFeatures.add(ImsResolver.METADATA_RCS_FEATURE);
         // Set the carrier override package for slot 0
-        setConfigCarrierString(0, TEST_CARRIER_DEFAULT_NAME.getPackageName());
+        setConfigCarrierStringMmTelRcs(0, TEST_CARRIER_DEFAULT_NAME.getPackageName());
         HashSet<ImsFeatureConfiguration.FeatureSlotPair> carrierFeatures1 = new HashSet<>();
         // Carrier service 1
         carrierFeatures1.add(new ImsFeatureConfiguration.FeatureSlotPair(0,
@@ -767,14 +1325,11 @@
         ImsServiceController carrierController2 = mock(ImsServiceController.class);
         setImsServiceControllerFactory(deviceController, carrierController1, carrierController2);
 
-        startBind();
+        startBindCarrierConfigAlreadySet();
         setupDynamicQueryFeatures(TEST_CARRIER_DEFAULT_NAME, carrierFeatures1, 1);
 
-        setConfigCarrierString(0, TEST_CARRIER_2_DEFAULT_NAME.getPackageName());
-        Intent carrierConfigIntent = new Intent();
-        carrierConfigIntent.putExtra(CarrierConfigManager.EXTRA_SLOT_INDEX, 0);
-        mTestCarrierConfigReceiver.onReceive(null, carrierConfigIntent);
-        waitForHandlerAction(mTestImsResolver.getHandler(), TEST_TIMEOUT);
+        setConfigCarrierStringMmTelRcs(0, TEST_CARRIER_2_DEFAULT_NAME.getPackageName());
+        sendCarrierConfigChanged(0, 0);
         setupDynamicQueryFeatures(TEST_CARRIER_2_DEFAULT_NAME, carrierFeatures2, 1);
 
         // Verify that carrier 1 is unbound
@@ -793,42 +1348,346 @@
         verify(deviceController).changeImsServiceFeatures(deviceFeatureSet);
     }
 
-    private void setupResolver(int numSlots) {
+    /**
+     * Inform the ImsResolver that BOOT_COMPLETE has happened. A non-FBE enabled ImsService is now
+     * available to be bound.
+     */
+    @Test
+    @SmallTest
+    public void testBootCompleteNonFbeEnabledCarrierImsService() throws RemoteException {
+        setupResolver(2 /*numSlots*/, TEST_DEVICE_DEFAULT_NAME.getPackageName(),
+                TEST_DEVICE_DEFAULT_NAME.getPackageName());
+        List<ResolveInfo> info = new ArrayList<>();
+        Set<String> deviceFeatures = new HashSet<>();
+        deviceFeatures.add(ImsResolver.METADATA_MMTEL_FEATURE);
+        deviceFeatures.add(ImsResolver.METADATA_RCS_FEATURE);
+        // Set the carrier override package for slot 0
+        setConfigCarrierStringMmTelRcs(0, TEST_CARRIER_DEFAULT_NAME.getPackageName());
+        // Use device default package, which will load the ImsService that the device provides
+        info.add(getResolveInfo(TEST_DEVICE_DEFAULT_NAME, deviceFeatures, true));
+        setupPackageQuery(info);
+        ImsServiceController deviceController = mock(ImsServiceController.class);
+        ImsServiceController carrierController = mock(ImsServiceController.class);
+        setImsServiceControllerFactory(deviceController, carrierController);
+
+        // Bind with device ImsService
+        startBindCarrierConfigAlreadySet();
+
+        // Boot complete happens and the Carrier ImsService is now available.
+        HashSet<ImsFeatureConfiguration.FeatureSlotPair> carrierFeatures = new HashSet<>();
+        // Carrier service doesn't support the voice feature.
+        carrierFeatures.add(new ImsFeatureConfiguration.FeatureSlotPair(0, ImsFeature.FEATURE_RCS));
+        info.add(getResolveInfo(TEST_CARRIER_DEFAULT_NAME, new HashSet<>(), true));
+        // Boot complete has happened and the carrier ImsService is now available.
+        mTestBootCompleteReceiver.onReceive(null, new Intent(Intent.ACTION_BOOT_COMPLETED));
+        mLooper.processAllMessages();
+        setupDynamicQueryFeatures(TEST_CARRIER_DEFAULT_NAME, carrierFeatures, 1);
+
+        // Verify that all features that have been defined for the carrier override are bound
+        verify(carrierController).bind(carrierFeatures);
+        // device features change
+        HashSet<ImsFeatureConfiguration.FeatureSlotPair> deviceFeatureSet =
+                convertToHashSet(deviceFeatures, 1);
+        deviceFeatureSet.addAll(convertToHashSet(deviceFeatures, 0));
+        deviceFeatureSet.removeAll(carrierFeatures);
+        verify(deviceController).changeImsServiceFeatures(deviceFeatureSet);
+    }
+
+    /**
+     * If a misbehaving ImsService returns null for the Binder connection when we perform a dynamic
+     * feature query, verify we never perform a full bind for any features.
+     */
+    @Test
+    @SmallTest
+    public void testPermanentBindFailureDuringFeatureQuery() throws RemoteException {
+        setupResolver(1 /*numSlots*/, TEST_DEVICE_DEFAULT_NAME.getPackageName(),
+                TEST_DEVICE_DEFAULT_NAME.getPackageName());
+        List<ResolveInfo> info = new ArrayList<>();
+        Set<String> deviceFeatures = new HashSet<>();
+        deviceFeatures.add(ImsResolver.METADATA_MMTEL_FEATURE);
+        deviceFeatures.add(ImsResolver.METADATA_RCS_FEATURE);
+        // Set the carrier override package for slot 0
+        setConfigCarrierStringMmTelRcs(0, TEST_CARRIER_DEFAULT_NAME.getPackageName());
+        HashSet<ImsFeatureConfiguration.FeatureSlotPair> carrierFeatures = new HashSet<>();
+        // Carrier service doesn't support the voice feature.
+        carrierFeatures.add(new ImsFeatureConfiguration.FeatureSlotPair(0, ImsFeature.FEATURE_RCS));
+        // Use device default package, which will load the ImsService that the device provides
+        info.add(getResolveInfo(TEST_DEVICE_DEFAULT_NAME, deviceFeatures, true));
+        info.add(getResolveInfo(TEST_CARRIER_DEFAULT_NAME, new HashSet<>(), true));
+        setupPackageQuery(info);
+        ImsServiceController deviceController = mock(ImsServiceController.class);
+        ImsServiceController carrierController = mock(ImsServiceController.class);
+        setImsServiceControllerFactory(deviceController, carrierController);
+
+        startBindCarrierConfigAlreadySet();
+        // dynamic query results in a failure.
+        setupDynamicQueryFeaturesFailure(TEST_CARRIER_DEFAULT_NAME, 1);
+
+        // Verify that a bind never occurs for the carrier controller.
+        verify(carrierController, never()).bind(any());
+        verify(carrierController, never()).unbind();
+        // Verify that all features are used to bind to the device ImsService since the carrier
+        // ImsService failed to bind properly.
+        HashSet<ImsFeatureConfiguration.FeatureSlotPair> deviceFeatureSet =
+                convertToHashSet(deviceFeatures, 0);
+        verify(deviceController).bind(deviceFeatureSet);
+        verify(deviceController, never()).unbind();
+        assertEquals(TEST_DEVICE_DEFAULT_NAME, deviceController.getComponentName());
+    }
+
+    /**
+     * If a misbehaving ImsService returns null for the Binder connection when we perform bind,
+     * verify the service is disconnected.
+     */
+    @Test
+    @SmallTest
+    public void testPermanentBindFailureDuringBind() throws RemoteException {
+        setupResolver(1 /*numSlots*/, TEST_DEVICE_DEFAULT_NAME.getPackageName(),
+                TEST_DEVICE_DEFAULT_NAME.getPackageName());
+        List<ResolveInfo> info = new ArrayList<>();
+        Set<String> deviceFeatures = new HashSet<>();
+        deviceFeatures.add(ImsResolver.METADATA_MMTEL_FEATURE);
+        deviceFeatures.add(ImsResolver.METADATA_RCS_FEATURE);
+        // Set the carrier override package for slot 0
+        setConfigCarrierStringMmTelRcs(0, TEST_CARRIER_DEFAULT_NAME.getPackageName());
+        HashSet<ImsFeatureConfiguration.FeatureSlotPair> carrierFeatures = new HashSet<>();
+        // Carrier service doesn't support the voice feature.
+        carrierFeatures.add(new ImsFeatureConfiguration.FeatureSlotPair(0, ImsFeature.FEATURE_RCS));
+        // Use device default package, which will load the ImsService that the device provides
+        info.add(getResolveInfo(TEST_DEVICE_DEFAULT_NAME, deviceFeatures, true));
+        info.add(getResolveInfo(TEST_CARRIER_DEFAULT_NAME, new HashSet<>(), true));
+        setupPackageQuery(info);
+        ImsServiceController deviceController = mock(ImsServiceController.class);
+        ImsServiceController carrierController = mock(ImsServiceController.class);
+        setImsServiceControllerFactory(deviceController, carrierController);
+
+        startBindCarrierConfigAlreadySet();
+        setupDynamicQueryFeatures(TEST_CARRIER_DEFAULT_NAME, carrierFeatures, 1);
+
+        // Verify that a bind never occurs for the carrier controller.
+        verify(carrierController).bind(carrierFeatures);
+        verify(carrierController, never()).unbind();
+        // Verify that all features that are not defined in the carrier override are bound in the
+        // device controller (including emergency voice for slot 0)
+        HashSet<ImsFeatureConfiguration.FeatureSlotPair> deviceFeatureSet =
+                convertToHashSet(deviceFeatures, 0);
+        deviceFeatureSet.removeAll(carrierFeatures);
+        verify(deviceController).bind(deviceFeatureSet);
+        verify(deviceController, never()).unbind();
+        assertEquals(TEST_DEVICE_DEFAULT_NAME, deviceController.getComponentName());
+
+        mTestImsResolver.imsServiceBindPermanentError(TEST_CARRIER_DEFAULT_NAME);
+        mLooper.processAllMessages();
+        verify(carrierController).unbind();
+        // Verify that the device ImsService features are changed to include the ones previously
+        // taken by the carrier app.
+        HashSet<ImsFeatureConfiguration.FeatureSlotPair> originalDeviceFeatureSet =
+                convertToHashSet(deviceFeatures, 0);
+        verify(deviceController).changeImsServiceFeatures(originalDeviceFeatureSet);
+    }
+
+    /**
+     * Bind to device ImsService only, which is configured to be the MMTEL ImsService. Ensure it
+     * does not also try to bind to RCS.
+     */
+    @Test
+    @SmallTest
+    public void testDifferentDevicePackagesMmTelOnly() throws RemoteException {
+        setupResolver(2 /*numSlots*/, TEST_DEVICE_DEFAULT_NAME.getPackageName(), "");
+        List<ResolveInfo> info = new ArrayList<>();
+        Set<String> features = new HashSet<>();
+        features.add(ImsResolver.METADATA_MMTEL_FEATURE);
+        features.add(ImsResolver.METADATA_RCS_FEATURE);
+        // Use device default package, which will load the ImsService that the device provides
+        info.add(getResolveInfo(TEST_DEVICE_DEFAULT_NAME, features, true));
+        info.add(getResolveInfo(TEST_DEVICE2_DEFAULT_NAME, features, true));
+        info.add(getResolveInfo(TEST_CARRIER_DEFAULT_NAME, new HashSet<>(), true));
+        setupPackageQuery(info);
+        ImsServiceController deviceController1 = mock(ImsServiceController.class);
+        ImsServiceController deviceController2 = mock(ImsServiceController.class);
+        setImsServiceControllerFactory(deviceController1, deviceController2, null, null);
+
+        startBindNoCarrierConfig(1);
+        mLooper.processAllMessages();
+
+        Set<String> featureResult = new HashSet<>();
+        featureResult.add(ImsResolver.METADATA_MMTEL_FEATURE);
+        HashSet<ImsFeatureConfiguration.FeatureSlotPair> featureResultSet =
+                convertToHashSet(featureResult, 0);
+        featureResultSet.addAll(convertToHashSet(featureResult, 1));
+        verify(deviceController1).bind(featureResultSet);
+        verify(deviceController1, never()).unbind();
+        verify(deviceController2, never()).bind(any());
+        verify(deviceController2, never()).unbind();
+        verify(mMockQueryManager, never()).startQuery(any(), any());
+        assertEquals(TEST_DEVICE_DEFAULT_NAME, deviceController1.getComponentName());
+    }
+
+    /**
+     * Bind to device ImsService only, which is configured to be the RCS ImsService. Ensure it
+     * does not also try to bind to MMTEL.
+     */
+    @Test
+    @SmallTest
+    public void testDifferentDevicePackagesRcsOnly() throws RemoteException {
+        setupResolver(2 /*numSlots*/, "", TEST_DEVICE_DEFAULT_NAME.getPackageName());
+        List<ResolveInfo> info = new ArrayList<>();
+        Set<String> features = new HashSet<>();
+        features.add(ImsResolver.METADATA_MMTEL_FEATURE);
+        features.add(ImsResolver.METADATA_RCS_FEATURE);
+        // Use device default package, which will load the ImsService that the device provides
+        info.add(getResolveInfo(TEST_DEVICE_DEFAULT_NAME, features, true));
+        info.add(getResolveInfo(TEST_DEVICE2_DEFAULT_NAME, features, true));
+        info.add(getResolveInfo(TEST_CARRIER_DEFAULT_NAME, new HashSet<>(), true));
+        setupPackageQuery(info);
+        ImsServiceController deviceController1 = mock(ImsServiceController.class);
+        ImsServiceController deviceController2 = mock(ImsServiceController.class);
+        setImsServiceControllerFactory(deviceController1, deviceController2, null, null);
+
+        startBindNoCarrierConfig(1);
+        mLooper.processAllMessages();
+
+        Set<String> featureResult = new HashSet<>();
+        featureResult.add(ImsResolver.METADATA_RCS_FEATURE);
+        HashSet<ImsFeatureConfiguration.FeatureSlotPair> featureResultSet =
+                convertToHashSet(featureResult, 0);
+        featureResultSet.addAll(convertToHashSet(featureResult, 1));
+        verify(deviceController1).bind(featureResultSet);
+        verify(deviceController1, never()).unbind();
+        verify(deviceController2, never()).bind(any());
+        verify(deviceController2, never()).unbind();
+        verify(mMockQueryManager, never()).startQuery(any(), any());
+        assertEquals(TEST_DEVICE_DEFAULT_NAME, deviceController1.getComponentName());
+    }
+
+    /**
+     * Bind to multiple ImsServices, one for MMTEL and one for RCS. Ensure neither of them bind to
+     * both.
+     */
+    @Test
+    @SmallTest
+    public void testDifferentDevicePackagesMmTelRcs() throws RemoteException {
+        setupResolver(2 /*numSlots*/, TEST_DEVICE_DEFAULT_NAME.getPackageName(),
+                TEST_DEVICE2_DEFAULT_NAME.getPackageName());
+        List<ResolveInfo> info = new ArrayList<>();
+        Set<String> features1 = new HashSet<>();
+        features1.add(ImsResolver.METADATA_MMTEL_FEATURE);
+        Set<String> features2 = new HashSet<>();
+        features2.add(ImsResolver.METADATA_RCS_FEATURE);
+        Set<String> allFeatures = new HashSet<>(features1);
+        allFeatures.addAll(features2);
+        // Use device default package, which will load the ImsService that the device provides
+        info.add(getResolveInfo(TEST_DEVICE_DEFAULT_NAME, allFeatures, true));
+        info.add(getResolveInfo(TEST_DEVICE2_DEFAULT_NAME, allFeatures, true));
+        info.add(getResolveInfo(TEST_CARRIER_DEFAULT_NAME, new HashSet<>(), true));
+        setupPackageQuery(info);
+        ImsServiceController deviceController1 = mock(ImsServiceController.class);
+        ImsServiceController deviceController2 = mock(ImsServiceController.class);
+        setImsServiceControllerFactory(deviceController1, deviceController2, null, null);
+
+        startBindNoCarrierConfig(1);
+        mLooper.processAllMessages();
+
+        HashSet<ImsFeatureConfiguration.FeatureSlotPair> featureSet1 =
+                convertToHashSet(features1, 0);
+        featureSet1.addAll(convertToHashSet(features1, 1));
+        HashSet<ImsFeatureConfiguration.FeatureSlotPair> featureSet2 =
+                convertToHashSet(features2, 0);
+        featureSet2.addAll(convertToHashSet(features2, 1));
+        verify(deviceController1).bind(featureSet1);
+        verify(deviceController1, never()).unbind();
+        verify(deviceController2).bind(featureSet2);
+        verify(deviceController2, never()).unbind();
+        verify(mMockQueryManager, never()).startQuery(any(), any());
+        assertEquals(TEST_DEVICE_DEFAULT_NAME, deviceController1.getComponentName());
+        assertEquals(TEST_DEVICE2_DEFAULT_NAME, deviceController2.getComponentName());
+    }
+
+    /**
+     * Set the device configuration to opposite of the supported features in the metadata and ensure
+     * there is no bind.
+     */
+    @Test
+    @SmallTest
+    public void testDifferentDevicePackagesNoSupported() throws RemoteException {
+        setupResolver(2 /*numSlots*/, TEST_DEVICE_DEFAULT_NAME.getPackageName(),
+                TEST_DEVICE2_DEFAULT_NAME.getPackageName());
+        List<ResolveInfo> info = new ArrayList<>();
+        Set<String> features1 = new HashSet<>();
+        features1.add(ImsResolver.METADATA_RCS_FEATURE);
+        Set<String> features2 = new HashSet<>();
+        features2.add(ImsResolver.METADATA_MMTEL_FEATURE);
+        // The configuration is opposite of the device supported features
+        info.add(getResolveInfo(TEST_DEVICE_DEFAULT_NAME, features1, true));
+        info.add(getResolveInfo(TEST_DEVICE2_DEFAULT_NAME, features2, true));
+        info.add(getResolveInfo(TEST_CARRIER_DEFAULT_NAME, new HashSet<>(), true));
+        setupPackageQuery(info);
+        ImsServiceController deviceController1 = mock(ImsServiceController.class);
+        ImsServiceController deviceController2 = mock(ImsServiceController.class);
+        setImsServiceControllerFactory(deviceController1, deviceController2, null, null);
+
+        startBindNoCarrierConfig(1);
+        mLooper.processAllMessages();
+
+        verify(deviceController1, never()).bind(any());
+        verify(deviceController1, never()).unbind();
+        verify(deviceController2, never()).bind(any());
+        verify(deviceController2, never()).unbind();
+        verify(mMockQueryManager, never()).startQuery(any(), any());
+    }
+
+    private void setupResolver(int numSlots, String deviceMmTelPkgName,
+            String deviceRcsPkgName) {
+        // all tests call setupResolver before running
+        when(mMockContext.getPackageManager()).thenReturn(mMockPM);
+        when(mMockContext.createContextAsUser(any(), eq(0))).thenReturn(mMockContext);
         when(mMockContext.getSystemService(eq(Context.CARRIER_CONFIG_SERVICE))).thenReturn(
                 mMockCarrierConfigManager);
-        when(mMockContext.getPackageManager()).thenReturn(mMockPM);
-        mCarrierConfigs = new PersistableBundle[numSlots];
-        for (int i = 0; i < numSlots; i++) {
+        when(mMockContext.getSystemService(eq(Context.USER_SERVICE))).thenReturn(mMockUserManager);
+
+        //If this is not false, then HANDLER_BOOT_COMPLETE is fired now but the controller factories
+        //used in the test methods aren't created in time.
+        when(mMockUserManager.isUserUnlocked()).thenReturn(false);
+
+        // Support configs for MSIM always in case we are testing dynamic sim slot config changes.
+        mCarrierConfigs = new PersistableBundle[NUM_MAX_SLOTS];
+        for (int i = 0; i < NUM_MAX_SLOTS; i++) {
             mCarrierConfigs[i] = new PersistableBundle();
             when(mMockCarrierConfigManager.getConfigForSubId(eq(i))).thenReturn(
                     mCarrierConfigs[i]);
             when(mTestSubscriptionManagerProxy.getSlotIndex(eq(i))).thenReturn(i);
             when(mTestSubscriptionManagerProxy.getSubId(eq(i))).thenReturn(i);
+            when(mTestTelephonyManagerProxy.getSimState(any(Context.class), eq(i))).thenReturn(
+                    TelephonyManager.SIM_STATE_READY);
         }
 
-        mTestImsResolver = new ImsResolver(mMockContext, TEST_DEVICE_DEFAULT_NAME.getPackageName(),
-                numSlots, true);
+        mTestImsResolver = new ImsResolver(mMockContext, deviceMmTelPkgName, deviceRcsPkgName,
+                numSlots);
+        try {
+            mLooper = new TestableLooper(mTestImsResolver.getHandler().getLooper());
+        } catch (Exception e) {
+            fail("Unable to create looper from handler.");
+        }
 
-        ArgumentCaptor<BroadcastReceiver> packageBroadcastCaptor =
+        ArgumentCaptor<BroadcastReceiver> receiversCaptor =
                 ArgumentCaptor.forClass(BroadcastReceiver.class);
-        ArgumentCaptor<BroadcastReceiver> carrierConfigCaptor =
-                ArgumentCaptor.forClass(BroadcastReceiver.class);
-        verify(mMockContext).registerReceiverAsUser(packageBroadcastCaptor.capture(), any(),
-                any(), any(), any());
-        verify(mMockContext).registerReceiver(carrierConfigCaptor.capture(), any());
-        mTestCarrierConfigReceiver = carrierConfigCaptor.getValue();
-        mTestPackageBroadcastReceiver = packageBroadcastCaptor.getValue();
+        verify(mMockContext, times(3)).registerReceiver(receiversCaptor.capture(), any());
+        mTestPackageBroadcastReceiver = receiversCaptor.getAllValues().get(0);
+        mTestCarrierConfigReceiver = receiversCaptor.getAllValues().get(1);
+        mTestBootCompleteReceiver = receiversCaptor.getAllValues().get(2);
         mTestImsResolver.setSubscriptionManagerProxy(mTestSubscriptionManagerProxy);
+        mTestImsResolver.setTelephonyManagerProxy(mTestTelephonyManagerProxy);
         when(mMockQueryManagerFactory.create(any(Context.class),
                 any(ImsServiceFeatureQueryManager.Listener.class))).thenReturn(mMockQueryManager);
         mTestImsResolver.setImsDynamicQueryManagerFactory(mMockQueryManagerFactory);
+        mLooper.processAllMessages();
     }
 
     private void setupPackageQuery(List<ResolveInfo> infos) {
         // Only return info if not using the compat argument
         when(mMockPM.queryIntentServicesAsUser(
                 argThat(argument -> ImsService.SERVICE_INTERFACE.equals(argument.getAction())),
-                anyInt(), anyInt())).thenReturn(infos);
+                anyInt(), any())).thenReturn(infos);
     }
 
     private void setupPackageQuery(ComponentName name, Set<String> features,
@@ -838,7 +1697,7 @@
         // Only return info if not using the compat argument
         when(mMockPM.queryIntentServicesAsUser(
                 argThat(argument -> ImsService.SERVICE_INTERFACE.equals(argument.getAction())),
-                anyInt(), anyInt())).thenReturn(info);
+                anyInt(), any())).thenReturn(info);
     }
 
     private ImsServiceController setupController() {
@@ -860,26 +1719,53 @@
         return controller;
     }
 
-    private void startBind() {
-        mTestImsResolver.initPopulateCacheAndStartBind();
+    /**
+     * In this case, there is a CarrierConfig already set for the sub/slot combo when initializing.
+     * This automatically kicks off the binding internally.
+     */
+    private void startBindCarrierConfigAlreadySet() {
+        mTestImsResolver.initialize();
         ArgumentCaptor<ImsServiceFeatureQueryManager.Listener> queryManagerCaptor =
                 ArgumentCaptor.forClass(ImsServiceFeatureQueryManager.Listener.class);
         verify(mMockQueryManagerFactory).create(any(Context.class), queryManagerCaptor.capture());
         mDynamicQueryListener = queryManagerCaptor.getValue();
-        waitForHandlerAction(mTestImsResolver.getHandler(), TEST_TIMEOUT);
+        when(mMockQueryManager.startQuery(any(ComponentName.class), any(String.class)))
+                .thenReturn(true);
+        mLooper.processAllMessages();
+    }
+
+    /**
+     * In this case, there is no carrier config override, send CarrierConfig loaded intent to all
+     * slots, indicating that the SIMs are loaded and to bind the device default.
+     */
+    private void startBindNoCarrierConfig(int numSlots) {
+        mTestImsResolver.initialize();
+        ArgumentCaptor<ImsServiceFeatureQueryManager.Listener> queryManagerCaptor =
+                ArgumentCaptor.forClass(ImsServiceFeatureQueryManager.Listener.class);
+        verify(mMockQueryManagerFactory).create(any(Context.class), queryManagerCaptor.capture());
+        mDynamicQueryListener = queryManagerCaptor.getValue();
+        mLooper.processAllMessages();
+        // For ease of testing, slotId = subId
+        for (int i = 0; i < numSlots; i++) {
+            sendCarrierConfigChanged(i, i);
+        }
     }
 
     private void setupDynamicQueryFeatures(ComponentName name,
             HashSet<ImsFeatureConfiguration.FeatureSlotPair> features, int times) {
-        // wait for schedule to happen
-        waitForHandlerAction(mTestImsResolver.getHandler(), TEST_TIMEOUT);
+        mLooper.processAllMessages();
         // ensure that startQuery was called
-        when(mMockQueryManager.startQuery(any(ComponentName.class), any(String.class)))
-                .thenReturn(true);
-        verify(mMockQueryManager, Mockito.times(times)).startQuery(eq(name), any(String.class));
+        verify(mMockQueryManager, times(times)).startQuery(eq(name), any(String.class));
         mDynamicQueryListener.onComplete(name, features);
-        // wait for handling of onComplete
-        waitForHandlerAction(mTestImsResolver.getHandler(), TEST_TIMEOUT);
+        mLooper.processAllMessages();
+    }
+
+    private void setupDynamicQueryFeaturesFailure(ComponentName name, int times) {
+        mLooper.processAllMessages();
+        // ensure that startQuery was called
+        verify(mMockQueryManager, times(times)).startQuery(eq(name), any(String.class));
+        mDynamicQueryListener.onPermanentError(name);
+        mLooper.processAllMessages();
     }
 
     public void packageChanged(String packageName) {
@@ -889,7 +1775,7 @@
         addPackageIntent.setData(new Uri.Builder().scheme("package").opaquePart(packageName)
                 .build());
         mTestPackageBroadcastReceiver.onReceive(null, addPackageIntent);
-        waitForHandlerAction(mTestImsResolver.getHandler(), TEST_TIMEOUT);
+        mLooper.processAllMessages();
     }
 
     public void packageRemoved(String packageName) {
@@ -898,7 +1784,23 @@
         removePackageIntent.setData(new Uri.Builder().scheme("package")
                 .opaquePart(TEST_CARRIER_DEFAULT_NAME.getPackageName()).build());
         mTestPackageBroadcastReceiver.onReceive(null, removePackageIntent);
-        waitForHandlerAction(mTestImsResolver.getHandler(), TEST_TIMEOUT);
+        mLooper.processAllMessages();
+    }
+
+    private void setImsServiceControllerFactory(Map<String, ImsServiceController> controllerMap) {
+        mTestImsResolver.setImsServiceControllerFactory(
+                new ImsResolver.ImsServiceControllerFactory() {
+                    @Override
+                    public String getServiceInterface() {
+                        return ImsService.SERVICE_INTERFACE;
+                    }
+
+                    @Override
+                    public ImsServiceController create(Context context, ComponentName componentName,
+                            ImsServiceController.ImsServiceControllerCallbacks callbacks) {
+                        return controllerMap.get(componentName.getPackageName());
+                    }
+                });
     }
 
     private void setImsServiceControllerFactory(ImsServiceController deviceController,
@@ -957,12 +1859,65 @@
                 });
     }
 
+    private void setImsServiceControllerFactory(ImsServiceController deviceController1,
+            ImsServiceController deviceController2, ImsServiceController carrierController1,
+            ImsServiceController carrierController2) {
+        mTestImsResolver.setImsServiceControllerFactory(
+                new ImsResolver.ImsServiceControllerFactory() {
+                    @Override
+                    public String getServiceInterface() {
+                        return ImsService.SERVICE_INTERFACE;
+                    }
 
-    private void setConfigCarrierString(int subId, String packageName) {
+                    @Override
+                    public ImsServiceController create(Context context, ComponentName componentName,
+                            ImsServiceController.ImsServiceControllerCallbacks callbacks) {
+                        if (TEST_DEVICE_DEFAULT_NAME.getPackageName().equals(
+                                componentName.getPackageName())) {
+                            when(deviceController1.getComponentName()).thenReturn(componentName);
+                            return deviceController1;
+                        } else if (TEST_DEVICE2_DEFAULT_NAME.getPackageName().equals(
+                                componentName.getPackageName())) {
+                            when(deviceController2.getComponentName()).thenReturn(componentName);
+                            return deviceController2;
+                        } else if (TEST_CARRIER_DEFAULT_NAME.getPackageName().equals(
+                                componentName.getPackageName())) {
+                            when(carrierController1.getComponentName()).thenReturn(componentName);
+                            return carrierController1;
+                        } else if (TEST_CARRIER_2_DEFAULT_NAME.getPackageName().equals(
+                                componentName.getPackageName())) {
+                            when(carrierController2.getComponentName()).thenReturn(componentName);
+                            return carrierController2;
+                        }
+                        return null;
+                    }
+                });
+    }
+
+
+    private void sendCarrierConfigChanged(int subId, int slotId) {
+        Intent carrierConfigIntent = new Intent();
+        carrierConfigIntent.putExtra(CarrierConfigManager.EXTRA_SUBSCRIPTION_INDEX, subId);
+        carrierConfigIntent.putExtra(CarrierConfigManager.EXTRA_SLOT_INDEX, slotId);
+        mTestCarrierConfigReceiver.onReceive(null, carrierConfigIntent);
+        mLooper.processAllMessages();
+    }
+
+    private void setConfigCarrierStringMmTelRcs(int subId, String packageName) {
         mCarrierConfigs[subId].putString(
                 CarrierConfigManager.KEY_CONFIG_IMS_PACKAGE_OVERRIDE_STRING, packageName);
     }
 
+    private void setConfigCarrierStringMmTel(int subId, String packageName) {
+        mCarrierConfigs[subId].putString(
+                CarrierConfigManager.KEY_CONFIG_IMS_MMTEL_PACKAGE_OVERRIDE_STRING, packageName);
+    }
+
+    private void setConfigCarrierStringRcs(int subId, String packageName) {
+        mCarrierConfigs[subId].putString(
+                CarrierConfigManager.KEY_CONFIG_IMS_RCS_PACKAGE_OVERRIDE_STRING, packageName);
+    }
+
     private HashSet<ImsFeatureConfiguration.FeatureSlotPair> convertToHashSet(
             Set<String> features, int slotId) {
         return features.stream()
@@ -973,6 +1928,11 @@
                 .collect(Collectors.toCollection(HashSet::new));
     }
 
+    private HashSet<ImsFeatureConfiguration.FeatureSlotPair> convertToFeatureSlotPairs(
+            int slotId, String... features) {
+        return convertToHashSet(new ArraySet<>(features), slotId);
+    }
+
     private int metadataStringToFeature(String f) {
         switch (f) {
             case ImsResolver.METADATA_MMTEL_FEATURE:
diff --git a/tests/telephonytests/src/com/android/internal/telephony/ims/ImsServiceControllerTest.java b/tests/telephonytests/src/com/android/internal/telephony/ims/ImsServiceControllerTest.java
index 7e1d71c..ff17216 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/ims/ImsServiceControllerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/ims/ImsServiceControllerTest.java
@@ -38,6 +38,7 @@
 import android.os.RemoteException;
 import android.telephony.ims.ImsService;
 import android.telephony.ims.aidl.IImsServiceController;
+import android.telephony.ims.feature.ImsFeature;
 import android.telephony.ims.stub.ImsFeatureConfiguration;
 import android.test.suitebuilder.annotation.SmallTest;
 
@@ -60,6 +61,9 @@
 @RunWith(AndroidJUnit4.class)
 public class ImsServiceControllerTest extends ImsTestBase {
 
+    private static final int SLOT_0 = 0;
+    private static final int SLOT_1 = 1;
+
     private static final ImsServiceController.RebindRetry REBIND_RETRY =
             new ImsServiceController.RebindRetry() {
         @Override
@@ -110,10 +114,10 @@
     @Test
     public void testBindService() {
         HashSet<ImsFeatureConfiguration.FeatureSlotPair> testFeatures = new HashSet<>();
-        // Slot 1, MMTel
-        testFeatures.add(new ImsFeatureConfiguration.FeatureSlotPair(1, 1));
-        // Slot 1, RCS
-        testFeatures.add(new ImsFeatureConfiguration.FeatureSlotPair(1, 2));
+        testFeatures.add(new ImsFeatureConfiguration.FeatureSlotPair(SLOT_0,
+                ImsFeature.FEATURE_MMTEL));
+        testFeatures.add(new ImsFeatureConfiguration.FeatureSlotPair(SLOT_0,
+                ImsFeature.FEATURE_RCS));
         ArgumentCaptor<Intent> intentCaptor =
                 ArgumentCaptor.forClass(Intent.class);
 
@@ -134,8 +138,8 @@
     @Test
     public void testBindFailureWhenBound() {
         HashSet<ImsFeatureConfiguration.FeatureSlotPair> testFeatures = new HashSet<>();
-        // Slot 1, MMTel
-        testFeatures.add(new ImsFeatureConfiguration.FeatureSlotPair(1, 1));
+        testFeatures.add(new ImsFeatureConfiguration.FeatureSlotPair(SLOT_0,
+                ImsFeature.FEATURE_MMTEL));
         bindAndConnectService(testFeatures);
 
         // already bound, should return false
@@ -152,21 +156,21 @@
     @Test
     public void testBindServiceAndConnected() throws RemoteException {
         HashSet<ImsFeatureConfiguration.FeatureSlotPair> testFeatures = new HashSet<>();
-        // Slot 1, MMTel
-        testFeatures.add(new ImsFeatureConfiguration.FeatureSlotPair(1, 1));
-        // Slot 1, RCS
-        testFeatures.add(new ImsFeatureConfiguration.FeatureSlotPair(1, 2));
+        testFeatures.add(new ImsFeatureConfiguration.FeatureSlotPair(SLOT_0,
+                ImsFeature.FEATURE_MMTEL));
+        testFeatures.add(new ImsFeatureConfiguration.FeatureSlotPair(SLOT_0,
+                ImsFeature.FEATURE_RCS));
 
         bindAndConnectService(testFeatures);
 
-        verify(mMockServiceControllerBinder).createMmTelFeature(eq(1), any());
-        verify(mMockServiceControllerBinder).createRcsFeature(eq(1), any());
-        verify(mMockCallbacks).imsServiceFeatureCreated(eq(1), eq(1),
+        verify(mMockServiceControllerBinder).createMmTelFeature(eq(SLOT_0), any());
+        verify(mMockServiceControllerBinder).createRcsFeature(eq(SLOT_0), any());
+        verify(mMockCallbacks).imsServiceFeatureCreated(eq(SLOT_0), eq(ImsFeature.FEATURE_MMTEL),
                 eq(mTestImsServiceController));
-        verify(mMockCallbacks).imsServiceFeatureCreated(eq(1), eq(2),
+        verify(mMockCallbacks).imsServiceFeatureCreated(eq(SLOT_0), eq(ImsFeature.FEATURE_RCS),
                 eq(mTestImsServiceController));
-        verify(mMockProxyCallbacks).imsFeatureCreated(eq(1), eq(1));
-        verify(mMockProxyCallbacks).imsFeatureCreated(eq(1), eq(2));
+        verify(mMockProxyCallbacks).imsFeatureCreated(eq(SLOT_0), eq(ImsFeature.FEATURE_MMTEL));
+        verify(mMockProxyCallbacks).imsFeatureCreated(eq(SLOT_0), eq(ImsFeature.FEATURE_RCS));
     }
 
     /**
@@ -177,24 +181,56 @@
     @Test
     public void testBindEmergencyMmTel() throws RemoteException {
         HashSet<ImsFeatureConfiguration.FeatureSlotPair> testFeatures = new HashSet<>();
-        // Slot 1, Emergency MMTel
-        testFeatures.add(new ImsFeatureConfiguration.FeatureSlotPair(1, 0));
-        // Slot 1, MmTel
-        testFeatures.add(new ImsFeatureConfiguration.FeatureSlotPair(1, 1));
+        testFeatures.add(new ImsFeatureConfiguration.FeatureSlotPair(SLOT_0,
+                ImsFeature.FEATURE_EMERGENCY_MMTEL));
+        testFeatures.add(new ImsFeatureConfiguration.FeatureSlotPair(SLOT_0,
+                ImsFeature.FEATURE_MMTEL));
 
         bindAndConnectService(testFeatures);
 
-        // We do not want this callback to happen for emergency MMTEL
-        verify(mMockServiceControllerBinder, never()).createMmTelFeature(eq(0), any());
-        verify(mMockCallbacks, never()).imsServiceFeatureCreated(eq(1), eq(0),
+        verify(mMockServiceControllerBinder).createMmTelFeature(eq(SLOT_0), any());
+        verify(mMockCallbacks).imsServiceFeatureCreated(eq(SLOT_0), eq(ImsFeature.FEATURE_MMTEL),
                 eq(mTestImsServiceController));
-        verify(mMockServiceControllerBinder).createMmTelFeature(eq(1), any());
-        verify(mMockCallbacks).imsServiceFeatureCreated(eq(1), eq(1),
-                eq(mTestImsServiceController));
+        verify(mMockCallbacks).imsServiceFeatureCreated(eq(SLOT_0),
+                eq(ImsFeature.FEATURE_EMERGENCY_MMTEL), eq(mTestImsServiceController));
         // Make sure this callback happens, which will notify the framework of emergency calling
         // availability.
-        verify(mMockProxyCallbacks).imsFeatureCreated(eq(1), eq(0));
-        verify(mMockProxyCallbacks).imsFeatureCreated(eq(1), eq(1));
+        verify(mMockProxyCallbacks).imsFeatureCreated(eq(SLOT_0),
+                eq(ImsFeature.FEATURE_EMERGENCY_MMTEL));
+        verify(mMockProxyCallbacks).imsFeatureCreated(eq(SLOT_0), eq(ImsFeature.FEATURE_MMTEL));
+    }
+
+    /**
+     * Tests to make sure that if EMERGENCY_MMTEL is specified, but not MMTEL, we do not bind to
+     * MMTEL.
+     */
+    @SmallTest
+    @Test
+    public void testBindEmergencyMmTelButNotMmTel() throws RemoteException {
+        HashSet<ImsFeatureConfiguration.FeatureSlotPair> testFeatures = new HashSet<>();
+        testFeatures.add(new ImsFeatureConfiguration.FeatureSlotPair(SLOT_0,
+                ImsFeature.FEATURE_EMERGENCY_MMTEL));
+        // did not add FEATURE_MMTEL
+        testFeatures.add(new ImsFeatureConfiguration.FeatureSlotPair(SLOT_0,
+                ImsFeature.FEATURE_RCS));
+
+        bindAndConnectService(testFeatures);
+
+        // Verify no MMTEL or EMERGENCY_MMTEL features are created
+        verify(mMockServiceControllerBinder, never()).createMmTelFeature(eq(SLOT_0), any());
+        verify(mMockCallbacks, never()).imsServiceFeatureCreated(eq(SLOT_0),
+                eq(ImsFeature.FEATURE_MMTEL), eq(mTestImsServiceController));
+        verify(mMockCallbacks, never()).imsServiceFeatureCreated(eq(SLOT_0),
+                eq(ImsFeature.FEATURE_EMERGENCY_MMTEL), eq(mTestImsServiceController));
+        verify(mMockProxyCallbacks, never()).imsFeatureCreated(eq(SLOT_0),
+                eq(ImsFeature.FEATURE_EMERGENCY_MMTEL));
+        verify(mMockProxyCallbacks, never()).imsFeatureCreated(eq(SLOT_0),
+                eq(ImsFeature.FEATURE_MMTEL));
+        // verify RCS feature is created
+        verify(mMockServiceControllerBinder).createRcsFeature(eq(SLOT_0), any());
+        verify(mMockCallbacks).imsServiceFeatureCreated(eq(SLOT_0), eq(ImsFeature.FEATURE_RCS),
+                eq(mTestImsServiceController));
+        verify(mMockProxyCallbacks).imsFeatureCreated(eq(SLOT_0), eq(ImsFeature.FEATURE_RCS));
     }
 
     /**
@@ -205,10 +241,10 @@
     @Test
     public void testCallbacksHappenWhenAddedAfterBind() throws RemoteException {
         HashSet<ImsFeatureConfiguration.FeatureSlotPair> testFeatures = new HashSet<>();
-        // Slot 1, Emergency MMTel
-        testFeatures.add(new ImsFeatureConfiguration.FeatureSlotPair(1, 0));
-        // Slot 1, MmTel
-        testFeatures.add(new ImsFeatureConfiguration.FeatureSlotPair(1, 1));
+        testFeatures.add(new ImsFeatureConfiguration.FeatureSlotPair(SLOT_0,
+                ImsFeature.FEATURE_EMERGENCY_MMTEL));
+        testFeatures.add(new ImsFeatureConfiguration.FeatureSlotPair(SLOT_0,
+                ImsFeature.FEATURE_MMTEL));
         mTestImsServiceController.removeImsServiceFeatureCallbacks();
 
         bindAndConnectService(testFeatures);
@@ -216,8 +252,9 @@
         mTestImsServiceController.addImsServiceFeatureCallback(mMockProxyCallbacks);
 
         // Make sure this callback happens for Emergency MMTEL and MMTEL
-        verify(mMockProxyCallbacks).imsFeatureCreated(eq(1), eq(0));
-        verify(mMockProxyCallbacks).imsFeatureCreated(eq(1), eq(1));
+        verify(mMockProxyCallbacks).imsFeatureCreated(eq(SLOT_0),
+                eq(ImsFeature.FEATURE_EMERGENCY_MMTEL));
+        verify(mMockProxyCallbacks).imsFeatureCreated(eq(SLOT_0), eq(ImsFeature.FEATURE_MMTEL));
     }
 
     /**
@@ -228,20 +265,20 @@
     @Test
     public void testBindServiceAndConnectedDisconnected() throws RemoteException {
         HashSet<ImsFeatureConfiguration.FeatureSlotPair> testFeatures = new HashSet<>();
-        // Slot 1, MMTel
-        testFeatures.add(new ImsFeatureConfiguration.FeatureSlotPair(1, 1));
-        // Slot 1, RCS
-        testFeatures.add(new ImsFeatureConfiguration.FeatureSlotPair(1, 2));
+        testFeatures.add(new ImsFeatureConfiguration.FeatureSlotPair(SLOT_0,
+                ImsFeature.FEATURE_MMTEL));
+        testFeatures.add(new ImsFeatureConfiguration.FeatureSlotPair(SLOT_0,
+                ImsFeature.FEATURE_RCS));
         ServiceConnection conn = bindAndConnectService(testFeatures);
 
         conn.onServiceDisconnected(mTestComponentName);
 
-        verify(mMockCallbacks).imsServiceFeatureRemoved(eq(1), eq(1),
+        verify(mMockCallbacks).imsServiceFeatureRemoved(eq(SLOT_0), eq(ImsFeature.FEATURE_MMTEL),
                 eq(mTestImsServiceController));
-        verify(mMockCallbacks).imsServiceFeatureRemoved(eq(1), eq(2),
+        verify(mMockCallbacks).imsServiceFeatureRemoved(eq(SLOT_0), eq(ImsFeature.FEATURE_RCS),
                 eq(mTestImsServiceController));
-        verify(mMockProxyCallbacks).imsFeatureRemoved(eq(1), eq(1));
-        verify(mMockProxyCallbacks).imsFeatureRemoved(eq(1), eq(2));
+        verify(mMockProxyCallbacks).imsFeatureRemoved(eq(SLOT_0), eq(ImsFeature.FEATURE_MMTEL));
+        verify(mMockProxyCallbacks).imsFeatureRemoved(eq(SLOT_0), eq(ImsFeature.FEATURE_RCS));
     }
 
     /**
@@ -252,23 +289,25 @@
     @Test
     public void testBindServiceBindUnbind() throws RemoteException {
         HashSet<ImsFeatureConfiguration.FeatureSlotPair> testFeatures = new HashSet<>();
-        // Slot 1, MMTel
-        testFeatures.add(new ImsFeatureConfiguration.FeatureSlotPair(1, 1));
-        // Slot 1, RCS
-        testFeatures.add(new ImsFeatureConfiguration.FeatureSlotPair(1, 2));
+        testFeatures.add(new ImsFeatureConfiguration.FeatureSlotPair(SLOT_0,
+                ImsFeature.FEATURE_MMTEL));
+        testFeatures.add(new ImsFeatureConfiguration.FeatureSlotPair(SLOT_0,
+                ImsFeature.FEATURE_RCS));
         ServiceConnection conn = bindAndConnectService(testFeatures);
 
         mTestImsServiceController.unbind();
 
         verify(mMockContext).unbindService(eq(conn));
-        verify(mMockServiceControllerBinder).removeImsFeature(eq(1), eq(1), any());
-        verify(mMockServiceControllerBinder).removeImsFeature(eq(1), eq(2), any());
-        verify(mMockCallbacks).imsServiceFeatureRemoved(eq(1), eq(1),
+        verify(mMockServiceControllerBinder).removeImsFeature(eq(SLOT_0),
+                eq(ImsFeature.FEATURE_MMTEL), any());
+        verify(mMockServiceControllerBinder).removeImsFeature(eq(SLOT_0),
+                eq(ImsFeature.FEATURE_RCS), any());
+        verify(mMockCallbacks).imsServiceFeatureRemoved(eq(SLOT_0), eq(ImsFeature.FEATURE_MMTEL),
                 eq(mTestImsServiceController));
-        verify(mMockCallbacks).imsServiceFeatureRemoved(eq(1), eq(2),
+        verify(mMockCallbacks).imsServiceFeatureRemoved(eq(SLOT_0), eq(ImsFeature.FEATURE_RCS),
                 eq(mTestImsServiceController));
-        verify(mMockProxyCallbacks).imsFeatureRemoved(eq(1), eq(1));
-        verify(mMockProxyCallbacks).imsFeatureRemoved(eq(1), eq(2));
+        verify(mMockProxyCallbacks).imsFeatureRemoved(eq(SLOT_0), eq(ImsFeature.FEATURE_MMTEL));
+        verify(mMockProxyCallbacks).imsFeatureRemoved(eq(SLOT_0), eq(ImsFeature.FEATURE_RCS));
     }
 
     /**
@@ -278,20 +317,40 @@
     @Test
     public void testBindServiceAndBinderDied() throws RemoteException {
         HashSet<ImsFeatureConfiguration.FeatureSlotPair> testFeatures = new HashSet<>();
-        // Slot 1, MMTel
-        testFeatures.add(new ImsFeatureConfiguration.FeatureSlotPair(1, 1));
-        // Slot 1, RCS
-        testFeatures.add(new ImsFeatureConfiguration.FeatureSlotPair(1, 2));
+        testFeatures.add(new ImsFeatureConfiguration.FeatureSlotPair(SLOT_0,
+                ImsFeature.FEATURE_MMTEL));
+        testFeatures.add(new ImsFeatureConfiguration.FeatureSlotPair(SLOT_0,
+                ImsFeature.FEATURE_RCS));
         ServiceConnection conn = bindAndConnectService(testFeatures);
 
         conn.onBindingDied(null /*null*/);
 
-        verify(mMockCallbacks).imsServiceFeatureRemoved(eq(1), eq(1),
+        verify(mMockCallbacks).imsServiceFeatureRemoved(eq(SLOT_0), eq(ImsFeature.FEATURE_MMTEL),
                 eq(mTestImsServiceController));
-        verify(mMockCallbacks).imsServiceFeatureRemoved(eq(1), eq(2),
+        verify(mMockCallbacks).imsServiceFeatureRemoved(eq(SLOT_0), eq(ImsFeature.FEATURE_RCS),
                 eq(mTestImsServiceController));
-        verify(mMockProxyCallbacks).imsFeatureRemoved(eq(1), eq(1));
-        verify(mMockProxyCallbacks).imsFeatureRemoved(eq(1), eq(2));
+        verify(mMockProxyCallbacks).imsFeatureRemoved(eq(SLOT_0), eq(ImsFeature.FEATURE_MMTEL));
+        verify(mMockProxyCallbacks).imsFeatureRemoved(eq(SLOT_0), eq(ImsFeature.FEATURE_RCS));
+    }
+
+    /**
+     * Ensures that imsServiceBindPermanentError is called when the binder returns null.
+     */
+    @SmallTest
+    @Test
+    public void testBindServiceAndReturnedNull() throws RemoteException {
+        HashSet<ImsFeatureConfiguration.FeatureSlotPair> testFeatures = new HashSet<>();
+        testFeatures.add(new ImsFeatureConfiguration.FeatureSlotPair(SLOT_0,
+                ImsFeature.FEATURE_MMTEL));
+        testFeatures.add(new ImsFeatureConfiguration.FeatureSlotPair(SLOT_0,
+                ImsFeature.FEATURE_RCS));
+
+        bindAndNullServiceError(testFeatures);
+
+        verify(mMockCallbacks, never()).imsServiceFeatureCreated(anyInt(), anyInt(),
+                eq(mTestImsServiceController));
+        verify(mMockProxyCallbacks, never()).imsFeatureCreated(anyInt(), anyInt());
+        verify(mMockCallbacks).imsServiceBindPermanentError(eq(mTestComponentName));
     }
 
     /**
@@ -301,24 +360,85 @@
     @Test
     public void testBindServiceAndAddFeature() throws RemoteException {
         HashSet<ImsFeatureConfiguration.FeatureSlotPair> testFeatures = new HashSet<>();
-        // Slot 1, MMTel
-        testFeatures.add(new ImsFeatureConfiguration.FeatureSlotPair(1, 1));
+        testFeatures.add(new ImsFeatureConfiguration.FeatureSlotPair(SLOT_0,
+                ImsFeature.FEATURE_MMTEL));
         bindAndConnectService(testFeatures);
-        verify(mMockServiceControllerBinder).createMmTelFeature(eq(1), any());
-        verify(mMockCallbacks).imsServiceFeatureCreated(eq(1), eq(1),
+        verify(mMockServiceControllerBinder).createMmTelFeature(eq(SLOT_0), any());
+        verify(mMockCallbacks).imsServiceFeatureCreated(eq(SLOT_0), eq(ImsFeature.FEATURE_MMTEL),
                 eq(mTestImsServiceController));
-        verify(mMockProxyCallbacks).imsFeatureCreated(eq(1), eq(1));
+        verify(mMockProxyCallbacks).imsFeatureCreated(eq(SLOT_0), eq(ImsFeature.FEATURE_MMTEL));
         // Create a new list with an additional item
         HashSet<ImsFeatureConfiguration.FeatureSlotPair> testFeaturesWithAddition = new HashSet<>(
                 testFeatures);
-        testFeaturesWithAddition.add(new ImsFeatureConfiguration.FeatureSlotPair(2, 1));
+        testFeaturesWithAddition.add(new ImsFeatureConfiguration.FeatureSlotPair(SLOT_1,
+                ImsFeature.FEATURE_MMTEL));
 
         mTestImsServiceController.changeImsServiceFeatures(testFeaturesWithAddition);
 
-        verify(mMockServiceControllerBinder).createMmTelFeature(eq(2), any());
-        verify(mMockCallbacks).imsServiceFeatureCreated(eq(2), eq(1),
+        verify(mMockServiceControllerBinder).createMmTelFeature(eq(SLOT_1), any());
+        verify(mMockCallbacks).imsServiceFeatureCreated(eq(SLOT_1), eq(ImsFeature.FEATURE_MMTEL),
                 eq(mTestImsServiceController));
-        verify(mMockProxyCallbacks).imsFeatureCreated(eq(2), eq(1));
+        verify(mMockProxyCallbacks).imsFeatureCreated(eq(SLOT_1), eq(ImsFeature.FEATURE_MMTEL));
+    }
+
+    /**
+     * Ensures that the when EMERGENCY_MMTEL_FEATURE is defined but not MMTEL_FEATURE when the
+     * features are changed, we do not bind to MMTEL.
+     */
+    @SmallTest
+    @Test
+    public void testBindServiceAndAddEmergencyButNotMmtel() throws RemoteException {
+        HashSet<ImsFeatureConfiguration.FeatureSlotPair> testFeatures = new HashSet<>();
+        testFeatures.add(new ImsFeatureConfiguration.FeatureSlotPair(SLOT_0,
+                ImsFeature.FEATURE_RCS));
+        bindAndConnectService(testFeatures);
+        verify(mMockServiceControllerBinder).createRcsFeature(eq(SLOT_0), any());
+        verify(mMockCallbacks).imsServiceFeatureCreated(eq(SLOT_0), eq(ImsFeature.FEATURE_RCS),
+                eq(mTestImsServiceController));
+        verify(mMockProxyCallbacks).imsFeatureCreated(eq(SLOT_0), eq(ImsFeature.FEATURE_RCS));
+        // Add FEATURE_EMERGENCY_MMTEL and ensure it doesn't cause MMTEL bind
+        HashSet<ImsFeatureConfiguration.FeatureSlotPair> testFeaturesWithAddition = new HashSet<>(
+                testFeatures);
+        testFeaturesWithAddition.add(new ImsFeatureConfiguration.FeatureSlotPair(SLOT_1,
+                ImsFeature.FEATURE_EMERGENCY_MMTEL));
+
+        mTestImsServiceController.changeImsServiceFeatures(testFeaturesWithAddition);
+
+        verify(mMockServiceControllerBinder, never()).createMmTelFeature(eq(SLOT_1), any());
+        verify(mMockCallbacks, never()).imsServiceFeatureCreated(eq(SLOT_1),
+                eq(ImsFeature.FEATURE_MMTEL),
+                eq(mTestImsServiceController));
+        verify(mMockProxyCallbacks, never()).imsFeatureCreated(eq(SLOT_1),
+                eq(ImsFeature.FEATURE_MMTEL));
+    }
+
+    /**
+     * Ensures ImsServiceController disregards changes to features that result in the same feature
+     * set.
+     */
+    @SmallTest
+    @Test
+    public void testBindServiceCallChangeWithNoNewFeatures() throws RemoteException {
+        HashSet<ImsFeatureConfiguration.FeatureSlotPair> testFeatures = new HashSet<>();
+        testFeatures.add(new ImsFeatureConfiguration.FeatureSlotPair(SLOT_0,
+                ImsFeature.FEATURE_MMTEL));
+        bindAndConnectService(testFeatures);
+        verify(mMockServiceControllerBinder).createMmTelFeature(eq(SLOT_0), any());
+        verify(mMockCallbacks).imsServiceFeatureCreated(eq(SLOT_0), eq(ImsFeature.FEATURE_MMTEL),
+                eq(mTestImsServiceController));
+        verify(mMockProxyCallbacks).imsFeatureCreated(eq(SLOT_0), eq(ImsFeature.FEATURE_MMTEL));
+
+        // Call change with the same features and make sure it is disregarded
+        mTestImsServiceController.changeImsServiceFeatures(testFeatures);
+
+        verify(mMockServiceControllerBinder, times(1)).createMmTelFeature(eq(SLOT_0), any());
+        verify(mMockServiceControllerBinder, never()).removeImsFeature(anyInt(), anyInt(), any());
+        verify(mMockCallbacks, times(1)).imsServiceFeatureCreated(eq(SLOT_0),
+                eq(ImsFeature.FEATURE_MMTEL), eq(mTestImsServiceController));
+        verify(mMockCallbacks, never()).imsServiceFeatureRemoved(anyInt(), anyInt(), any());
+        verify(mMockProxyCallbacks, times(1)).imsFeatureCreated(eq(SLOT_0),
+                eq(ImsFeature.FEATURE_MMTEL));
+        verify(mMockProxyCallbacks, never()).imsFeatureRemoved(anyInt(), anyInt());
     }
 
     /**
@@ -328,30 +448,32 @@
     @Test
     public void testBindServiceAndRemoveFeature() throws RemoteException {
         HashSet<ImsFeatureConfiguration.FeatureSlotPair> testFeatures = new HashSet<>();
-        // Slot 1, MMTel
-        testFeatures.add(new ImsFeatureConfiguration.FeatureSlotPair(1, 1));
-        // Slot 2, MMTel
-        testFeatures.add(new ImsFeatureConfiguration.FeatureSlotPair(2, 1));
+        testFeatures.add(new ImsFeatureConfiguration.FeatureSlotPair(SLOT_0,
+                ImsFeature.FEATURE_MMTEL));
+        testFeatures.add(new ImsFeatureConfiguration.FeatureSlotPair(SLOT_1,
+                ImsFeature.FEATURE_MMTEL));
         bindAndConnectService(testFeatures);
-        verify(mMockServiceControllerBinder).createMmTelFeature(eq(1), any());
-        verify(mMockCallbacks).imsServiceFeatureCreated(eq(1), eq(1),
+        verify(mMockServiceControllerBinder).createMmTelFeature(eq(SLOT_0), any());
+        verify(mMockCallbacks).imsServiceFeatureCreated(eq(SLOT_0), eq(ImsFeature.FEATURE_MMTEL),
                 eq(mTestImsServiceController));
-        verify(mMockProxyCallbacks).imsFeatureCreated(eq(1), eq(1));
-        verify(mMockServiceControllerBinder).createMmTelFeature(eq(2), any());
-        verify(mMockCallbacks).imsServiceFeatureCreated(eq(2), eq(1),
+        verify(mMockProxyCallbacks).imsFeatureCreated(eq(SLOT_0), eq(ImsFeature.FEATURE_MMTEL));
+        verify(mMockServiceControllerBinder).createMmTelFeature(eq(SLOT_1), any());
+        verify(mMockCallbacks).imsServiceFeatureCreated(eq(SLOT_1), eq(ImsFeature.FEATURE_MMTEL),
                 eq(mTestImsServiceController));
-        verify(mMockProxyCallbacks).imsFeatureCreated(eq(2), eq(1));
+        verify(mMockProxyCallbacks).imsFeatureCreated(eq(SLOT_1), eq(ImsFeature.FEATURE_MMTEL));
         // Create a new list with one less item
         HashSet<ImsFeatureConfiguration.FeatureSlotPair> testFeaturesWithSubtraction =
                 new HashSet<>(testFeatures);
-        testFeaturesWithSubtraction.remove(new ImsFeatureConfiguration.FeatureSlotPair(2, 1));
+        testFeaturesWithSubtraction.remove(new ImsFeatureConfiguration.FeatureSlotPair(SLOT_1,
+                ImsFeature.FEATURE_MMTEL));
 
         mTestImsServiceController.changeImsServiceFeatures(testFeaturesWithSubtraction);
 
-        verify(mMockServiceControllerBinder).removeImsFeature(eq(2), eq(1), any());
-        verify(mMockCallbacks).imsServiceFeatureRemoved(eq(2), eq(1),
+        verify(mMockServiceControllerBinder).removeImsFeature(eq(SLOT_1),
+                eq(ImsFeature.FEATURE_MMTEL), any());
+        verify(mMockCallbacks).imsServiceFeatureRemoved(eq(SLOT_1), eq(ImsFeature.FEATURE_MMTEL),
                 eq(mTestImsServiceController));
-        verify(mMockProxyCallbacks).imsFeatureRemoved(eq(2), eq(1));
+        verify(mMockProxyCallbacks).imsFeatureRemoved(eq(SLOT_1), eq(ImsFeature.FEATURE_MMTEL));
     }
 
     /**
@@ -361,31 +483,33 @@
     @Test
     public void testBindServiceAndRemoveAllFeatures() throws RemoteException {
         HashSet<ImsFeatureConfiguration.FeatureSlotPair> testFeatures = new HashSet<>();
-        // slot 1, MMTel
-        testFeatures.add(new ImsFeatureConfiguration.FeatureSlotPair(1, 1));
-        // slot 2, MMTel
-        testFeatures.add(new ImsFeatureConfiguration.FeatureSlotPair(2, 1));
+        testFeatures.add(new ImsFeatureConfiguration.FeatureSlotPair(SLOT_0,
+                ImsFeature.FEATURE_MMTEL));
+        testFeatures.add(new ImsFeatureConfiguration.FeatureSlotPair(SLOT_1,
+                ImsFeature.FEATURE_MMTEL));
         bindAndConnectService(testFeatures);
-        verify(mMockServiceControllerBinder).createMmTelFeature(eq(1), any());
-        verify(mMockCallbacks).imsServiceFeatureCreated(eq(1), eq(1),
+        verify(mMockServiceControllerBinder).createMmTelFeature(eq(SLOT_0), any());
+        verify(mMockCallbacks).imsServiceFeatureCreated(eq(SLOT_0), eq(ImsFeature.FEATURE_MMTEL),
                 eq(mTestImsServiceController));
-        verify(mMockProxyCallbacks).imsFeatureCreated(eq(1), eq(1));
-        verify(mMockServiceControllerBinder).createMmTelFeature(eq(2), any());
-        verify(mMockCallbacks).imsServiceFeatureCreated(eq(2), eq(1),
+        verify(mMockProxyCallbacks).imsFeatureCreated(eq(SLOT_0), eq(ImsFeature.FEATURE_MMTEL));
+        verify(mMockServiceControllerBinder).createMmTelFeature(eq(SLOT_1), any());
+        verify(mMockCallbacks).imsServiceFeatureCreated(eq(SLOT_1), eq(ImsFeature.FEATURE_MMTEL),
                 eq(mTestImsServiceController));
-        verify(mMockProxyCallbacks).imsFeatureCreated(eq(2), eq(1));
+        verify(mMockProxyCallbacks).imsFeatureCreated(eq(SLOT_1), eq(ImsFeature.FEATURE_MMTEL));
 
         // Create a new empty list
         mTestImsServiceController.changeImsServiceFeatures(new HashSet<>());
 
-        verify(mMockServiceControllerBinder).removeImsFeature(eq(1), eq(1), any());
-        verify(mMockCallbacks).imsServiceFeatureRemoved(eq(1), eq(1),
+        verify(mMockServiceControllerBinder).removeImsFeature(eq(SLOT_0),
+                eq(ImsFeature.FEATURE_MMTEL), any());
+        verify(mMockCallbacks).imsServiceFeatureRemoved(eq(SLOT_0), eq(ImsFeature.FEATURE_MMTEL),
                 eq(mTestImsServiceController));
-        verify(mMockProxyCallbacks).imsFeatureRemoved(eq(1), eq(1));
-        verify(mMockServiceControllerBinder).removeImsFeature(eq(2), eq(1), any());
-        verify(mMockCallbacks).imsServiceFeatureRemoved(eq(2), eq(1),
+        verify(mMockProxyCallbacks).imsFeatureRemoved(eq(SLOT_0), eq(ImsFeature.FEATURE_MMTEL));
+        verify(mMockServiceControllerBinder).removeImsFeature(eq(SLOT_1),
+                eq(ImsFeature.FEATURE_MMTEL), any());
+        verify(mMockCallbacks).imsServiceFeatureRemoved(eq(SLOT_1), eq(ImsFeature.FEATURE_MMTEL),
                 eq(mTestImsServiceController));
-        verify(mMockProxyCallbacks).imsFeatureRemoved(eq(2), eq(1));
+        verify(mMockProxyCallbacks).imsFeatureRemoved(eq(SLOT_1), eq(ImsFeature.FEATURE_MMTEL));
     }
 
     /**
@@ -395,22 +519,24 @@
     @Test
     public void testBindUnbindServiceAndAddFeature() throws RemoteException {
         HashSet<ImsFeatureConfiguration.FeatureSlotPair> testFeatures = new HashSet<>();
-        // Slot 1, MMTel
-        testFeatures.add(new ImsFeatureConfiguration.FeatureSlotPair(1, 1));
+        testFeatures.add(new ImsFeatureConfiguration.FeatureSlotPair(SLOT_0,
+                ImsFeature.FEATURE_MMTEL));
         bindAndConnectService(testFeatures);
         mTestImsServiceController.unbind();
         // Create a new list with an additional item
         HashSet<ImsFeatureConfiguration.FeatureSlotPair> testFeaturesWithAddition = new HashSet<>(
                 testFeatures);
         // Try to create an RCS feature
-        testFeaturesWithAddition.add(new ImsFeatureConfiguration.FeatureSlotPair(1, 2));
+        testFeaturesWithAddition.add(new ImsFeatureConfiguration.FeatureSlotPair(SLOT_0,
+                ImsFeature.FEATURE_RCS));
 
         mTestImsServiceController.changeImsServiceFeatures(testFeaturesWithAddition);
 
-        verify(mMockServiceControllerBinder, never()).createRcsFeature(eq(1), any());
-        verify(mMockCallbacks, never()).imsServiceFeatureCreated(eq(1), eq(2),
-                eq(mTestImsServiceController));
-        verify(mMockProxyCallbacks, never()).imsFeatureCreated(eq(1), eq(2));
+        verify(mMockServiceControllerBinder, never()).createRcsFeature(eq(SLOT_0), any());
+        verify(mMockCallbacks, never()).imsServiceFeatureCreated(eq(SLOT_0),
+                eq(ImsFeature.FEATURE_RCS), eq(mTestImsServiceController));
+        verify(mMockProxyCallbacks, never()).imsFeatureCreated(eq(SLOT_0),
+                eq(ImsFeature.FEATURE_RCS));
     }
 
     /**
@@ -421,10 +547,10 @@
     @Test
     public void testAutoBindAfterBinderDied() throws RemoteException {
         HashSet<ImsFeatureConfiguration.FeatureSlotPair> testFeatures = new HashSet<>();
-        // Slot 1, MMTel
-        testFeatures.add(new ImsFeatureConfiguration.FeatureSlotPair(1, 1));
-        // Slot 1, RCS
-        testFeatures.add(new ImsFeatureConfiguration.FeatureSlotPair(1, 2));
+        testFeatures.add(new ImsFeatureConfiguration.FeatureSlotPair(SLOT_0,
+                ImsFeature.FEATURE_MMTEL));
+        testFeatures.add(new ImsFeatureConfiguration.FeatureSlotPair(SLOT_0,
+                ImsFeature.FEATURE_RCS));
         ServiceConnection conn = bindAndConnectService(testFeatures);
 
         conn.onBindingDied(null /*null*/);
@@ -442,10 +568,10 @@
     @Test
     public void testNoAutoBindBeforeTimeout() throws RemoteException {
         HashSet<ImsFeatureConfiguration.FeatureSlotPair> testFeatures = new HashSet<>();
-        // Slot 1, MMTel
-        testFeatures.add(new ImsFeatureConfiguration.FeatureSlotPair(1, 1));
-        // Slot 1, RCS
-        testFeatures.add(new ImsFeatureConfiguration.FeatureSlotPair(1, 2));
+        testFeatures.add(new ImsFeatureConfiguration.FeatureSlotPair(SLOT_0,
+                ImsFeature.FEATURE_MMTEL));
+        testFeatures.add(new ImsFeatureConfiguration.FeatureSlotPair(SLOT_0,
+                ImsFeature.FEATURE_RCS));
         ServiceConnection conn = bindAndConnectService(testFeatures);
 
         conn.onBindingDied(null /*null*/);
@@ -461,10 +587,10 @@
     @Test
     public void testUnbindCauseAutoBindCancelAfterBinderDied() throws RemoteException {
         HashSet<ImsFeatureConfiguration.FeatureSlotPair> testFeatures = new HashSet<>();
-        // Slot 1, MMTel
-        testFeatures.add(new ImsFeatureConfiguration.FeatureSlotPair(1, 1));
-        // Slot 1, RCS
-        testFeatures.add(new ImsFeatureConfiguration.FeatureSlotPair(1, 2));
+        testFeatures.add(new ImsFeatureConfiguration.FeatureSlotPair(SLOT_0,
+                ImsFeature.FEATURE_MMTEL));
+        testFeatures.add(new ImsFeatureConfiguration.FeatureSlotPair(SLOT_0,
+                ImsFeature.FEATURE_RCS));
         ServiceConnection conn = bindAndConnectService(testFeatures);
 
         conn.onBindingDied(null /*null*/);
@@ -485,10 +611,10 @@
     @Test
     public void testBindCauseAutoBindCancelAfterBinderDied() throws RemoteException {
         HashSet<ImsFeatureConfiguration.FeatureSlotPair> testFeatures = new HashSet<>();
-        // Slot 1, MMTel
-        testFeatures.add(new ImsFeatureConfiguration.FeatureSlotPair(1, 1));
-        // Slot 1, RCS
-        testFeatures.add(new ImsFeatureConfiguration.FeatureSlotPair(1, 2));
+        testFeatures.add(new ImsFeatureConfiguration.FeatureSlotPair(SLOT_0,
+                ImsFeature.FEATURE_MMTEL));
+        testFeatures.add(new ImsFeatureConfiguration.FeatureSlotPair(SLOT_0,
+                ImsFeature.FEATURE_RCS));
         ServiceConnection conn = bindAndConnectService(testFeatures);
         conn.onBindingDied(null /*null*/);
         mTestImsServiceController.bind(testFeatures);
@@ -499,16 +625,27 @@
         verify(mMockContext, times(2)).bindService(any(), any(), anyInt());
     }
 
+    private void bindAndNullServiceError(
+            HashSet<ImsFeatureConfiguration.FeatureSlotPair> testFeatures) {
+        ServiceConnection connection = bindService(testFeatures);
+        connection.onNullBinding(mTestComponentName);
+    }
+
     private ServiceConnection bindAndConnectService(
             HashSet<ImsFeatureConfiguration.FeatureSlotPair> testFeatures) {
+        ServiceConnection connection = bindService(testFeatures);
+        IImsServiceController.Stub controllerStub = mock(IImsServiceController.Stub.class);
+        when(controllerStub.queryLocalInterface(any())).thenReturn(mMockServiceControllerBinder);
+        connection.onServiceConnected(mTestComponentName, controllerStub);
+        return connection;
+    }
+
+    private ServiceConnection bindService(
+            HashSet<ImsFeatureConfiguration.FeatureSlotPair> testFeatures) {
         ArgumentCaptor<ServiceConnection> serviceCaptor =
                 ArgumentCaptor.forClass(ServiceConnection.class);
         assertTrue(mTestImsServiceController.bind(testFeatures));
         verify(mMockContext).bindService(any(), serviceCaptor.capture(), anyInt());
-        IImsServiceController.Stub controllerStub = mock(IImsServiceController.Stub.class);
-        when(controllerStub.queryLocalInterface(any())).thenReturn(mMockServiceControllerBinder);
-        serviceCaptor.getValue().onServiceConnected(mTestComponentName,
-                controllerStub);
         return serviceCaptor.getValue();
     }
 }
diff --git a/tests/telephonytests/src/com/android/internal/telephony/ims/ImsUtTest.java b/tests/telephonytests/src/com/android/internal/telephony/ims/ImsUtTest.java
new file mode 100644
index 0000000..4948a67
--- /dev/null
+++ b/tests/telephonytests/src/com/android/internal/telephony/ims/ImsUtTest.java
@@ -0,0 +1,153 @@
+/*
+ * Copyright (C) 2019 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.ims;
+
+import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertNotNull;
+import static junit.framework.TestCase.fail;
+
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.verify;
+
+import android.os.AsyncResult;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.telephony.ims.ImsSsInfo;
+import android.telephony.ims.ImsUtListener;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.ims.ImsUt;
+import com.android.ims.internal.IImsUt;
+import com.android.internal.telephony.TelephonyTest;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.TimeUnit;
+
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper
+public class ImsUtTest extends TelephonyTest {
+
+    private static final int MSG_QUERY = 1;
+    private static final int TEST_TIMEOUT_MS = 5000;
+
+    private class TestHandler extends Handler {
+
+        TestHandler(Looper looper) {
+            super(looper);
+        }
+
+        private final LinkedBlockingQueue<ImsSsInfo> mPendingSsInfos = new LinkedBlockingQueue<>(1);
+        @Override
+        public void handleMessage(Message msg) {
+            if (msg.what == MSG_QUERY) {
+                AsyncResult ar = (AsyncResult) msg.obj;
+                mPendingSsInfos.offer((ImsSsInfo) ar.result);
+            }
+        }
+        public ImsSsInfo getPendingImsSsInfo() {
+            try {
+                return mPendingSsInfos.poll(TEST_TIMEOUT_MS, TimeUnit.MILLISECONDS);
+            } catch (InterruptedException e) {
+                fail("test interrupted!");
+            }
+            return null;
+        }
+    }
+
+    @Mock IImsUt mImsUtBinder;
+
+    private TestHandler mHandler;
+
+    @Before
+    public void setUp() throws Exception {
+        super.setUp("ImsUtTest");
+        mHandler = new TestHandler(Looper.myLooper());
+        processAllMessages();
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        super.tearDown();
+    }
+
+    @Test
+    @SmallTest
+    public void testClirConversionCompat() throws Exception {
+        ArgumentCaptor<ImsUt.IImsUtListenerProxy> captor =
+                ArgumentCaptor.forClass(ImsUt.IImsUtListenerProxy.class);
+        ImsUt mImsUt = new ImsUt(mImsUtBinder);
+        verify(mImsUtBinder).setListener(captor.capture());
+        ImsUt.IImsUtListenerProxy proxy = captor.getValue();
+        assertNotNull(proxy);
+
+        doReturn(2).when(mImsUtBinder).queryCLIR();
+        mImsUt.queryCLIR(Message.obtain(mHandler, MSG_QUERY));
+
+        Bundle result = new Bundle();
+        result.putIntArray(ImsUtListener.BUNDLE_KEY_CLIR, new int[] {
+                ImsSsInfo.CLIR_OUTGOING_INVOCATION, ImsSsInfo.CLIR_STATUS_PROVISIONED_PERMANENT});
+        // This is deprecated, will be converted from Bundle -> ImsSsInfo
+        proxy.utConfigurationQueried(null, 2 /*id*/, result);
+        processAllMessages();
+
+
+        ImsSsInfo info = mHandler.getPendingImsSsInfo();
+        assertNotNull(info);
+        assertEquals(ImsSsInfo.CLIR_OUTGOING_INVOCATION, info.getClirOutgoingState());
+        assertEquals(ImsSsInfo.CLIR_STATUS_PROVISIONED_PERMANENT,
+                info.getClirInterrogationStatus());
+    }
+
+    @Test
+    @SmallTest
+    public void testClipConversionCompat() throws Exception {
+        ArgumentCaptor<ImsUt.IImsUtListenerProxy> captor =
+                ArgumentCaptor.forClass(ImsUt.IImsUtListenerProxy.class);
+        ImsUt mImsUt = new ImsUt(mImsUtBinder);
+        verify(mImsUtBinder).setListener(captor.capture());
+        ImsUt.IImsUtListenerProxy proxy = captor.getValue();
+        assertNotNull(proxy);
+
+        doReturn(2).when(mImsUtBinder).queryCLIP();
+        mImsUt.queryCLIP(Message.obtain(mHandler, MSG_QUERY));
+
+        ImsSsInfo info = new ImsSsInfo.Builder(ImsSsInfo.ENABLED).setProvisionStatus(
+                ImsSsInfo.CLIR_STATUS_PROVISIONED_PERMANENT).build();
+        Bundle result = new Bundle();
+        result.putParcelable(ImsUtListener.BUNDLE_KEY_SSINFO, info);
+        // This is deprecated, will be converted from Bundle -> ImsSsInfo
+        proxy.utConfigurationQueried(null, 2 /*id*/, result);
+        processAllMessages();
+
+        ImsSsInfo resultInfo = mHandler.getPendingImsSsInfo();
+        assertNotNull(resultInfo);
+        assertEquals(info.getStatus(), resultInfo.getStatus());
+        assertEquals(info.getProvisionStatus(), resultInfo.getProvisionStatus());
+    }
+}
diff --git a/tests/telephonytests/src/com/android/internal/telephony/ims/MmTelFeatureConnectionTest.java b/tests/telephonytests/src/com/android/internal/telephony/ims/MmTelFeatureConnectionTest.java
index 26afbe3..87b8a56 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/ims/MmTelFeatureConnectionTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/ims/MmTelFeatureConnectionTest.java
@@ -34,7 +34,7 @@
 import android.telephony.SubscriptionManager;
 import android.test.suitebuilder.annotation.SmallTest;
 
-import com.android.ims.MmTelFeatureConnection;
+import com.android.ims.ImsCallbackAdapterManager;
 import com.android.internal.telephony.TelephonyTest;
 
 import org.junit.After;
@@ -55,12 +55,12 @@
     }
 
     private class CallbackManagerTest extends
-            MmTelFeatureConnection.CallbackAdapterManager<TestCallback> {
+            ImsCallbackAdapterManager<TestCallback> {
 
         List<TestCallback> mCallbacks = new ArrayList<>();
 
         CallbackManagerTest(Context context, Object lock) {
-            super(context, lock);
+            super(context, lock, 0 /*slotId*/);
         }
 
         // A callback has been registered. Register that callback with the MmTelFeature.
diff --git a/tests/telephonytests/src/com/android/internal/telephony/ims/RcsMessageStoreControllerTest.java b/tests/telephonytests/src/com/android/internal/telephony/ims/RcsMessageStoreControllerTest.java
deleted file mode 100644
index 1dff69e..0000000
--- a/tests/telephonytests/src/com/android/internal/telephony/ims/RcsMessageStoreControllerTest.java
+++ /dev/null
@@ -1,199 +0,0 @@
-/*
- * Copyright 2019 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.ims;
-
-import static android.provider.Telephony.RcsColumns.Rcs1To1ThreadColumns.FALLBACK_THREAD_ID_COLUMN;
-import static android.provider.Telephony.RcsColumns.RcsGroupThreadColumns.GROUP_ICON_COLUMN;
-import static android.provider.Telephony.RcsColumns.RcsGroupThreadColumns.GROUP_NAME_COLUMN;
-import static android.provider.Telephony.RcsColumns.RcsGroupThreadColumns.OWNER_PARTICIPANT_COLUMN;
-import static android.telephony.ims.RcsThreadQueryParams.THREAD_TYPE_GROUP;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import static org.mockito.Mockito.doReturn;
-
-import android.content.ContentValues;
-import android.database.MatrixCursor;
-import android.net.Uri;
-import android.provider.Telephony;
-import android.provider.Telephony.RcsColumns.RcsParticipantColumns;
-import android.telephony.ims.RcsParticipant;
-import android.telephony.ims.RcsThreadQueryParams;
-import android.test.mock.MockContentResolver;
-
-import com.android.internal.telephony.TelephonyTest;
-import com.android.internal.telephony.ims.FakeProviderWithAsserts.ExpectedInsert;
-import com.android.internal.telephony.ims.FakeProviderWithAsserts.ExpectedQuery;
-import com.android.internal.telephony.ims.FakeProviderWithAsserts.ExpectedUpdate;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-
-public class RcsMessageStoreControllerTest extends TelephonyTest {
-
-    private RcsMessageStoreController mRcsMessageStoreController;
-    private MockContentResolver mContentResolver;
-    private FakeProviderWithAsserts mFakeRcsProvider;
-
-    @Mock
-    RcsParticipant mMockParticipant;
-
-    @Before
-    public void setUp() throws Exception {
-        super.setUp("RcsMessageStoreControllerTest");
-        MockitoAnnotations.initMocks(this);
-
-        mFakeRcsProvider = new FakeProviderWithAsserts();
-        mContentResolver = (MockContentResolver) mContext.getContentResolver();
-        mContentResolver.addProvider("rcs", mFakeRcsProvider);
-
-        mRcsMessageStoreController = new RcsMessageStoreController(mContext);
-    }
-
-    @After
-    public void tearDown() throws Exception {
-        super.tearDown();
-    }
-
-    @Test
-    public void testGetRcsThreads() {
-        doReturn(123).when(mMockParticipant).getId();
-        RcsThreadQueryParams queryParameters =
-                new RcsThreadQueryParams.Builder().setParticipant(mMockParticipant)
-                        .setThreadType(THREAD_TYPE_GROUP).setResultLimit(30).build();
-
-        // TODO - limit the query as per queryParameters. This will change how the query is executed
-        mFakeRcsProvider.addExpectedOperation(new ExpectedQuery(
-                Uri.parse("content://rcs/thread"), null, null, null, null, null));
-
-        try {
-            mRcsMessageStoreController.getRcsThreads(queryParameters, getPackageName());
-        } catch (RuntimeException e) {
-            // eat the exception as there is no provider - we care about the expected update assert
-        }
-    }
-
-    @Test
-    public void testCreateRcsParticipant() {
-        String canonicalAddress = "+5551234567";
-
-        // verify the first query to canonical addresses
-        MatrixCursor canonicalAddressQueryCursor = new MatrixCursor(
-                new String[]{Telephony.CanonicalAddressesColumns._ID});
-        canonicalAddressQueryCursor.addRow(new Object[]{456});
-
-        Uri expectedCanonicalAddressUri = Uri.parse("content://rcs/canonical-address")
-                .buildUpon()
-                .appendQueryParameter("address", canonicalAddress)
-                .build();
-
-        mFakeRcsProvider.addExpectedOperation(new ExpectedQuery(
-                expectedCanonicalAddressUri, null, null, null, null, canonicalAddressQueryCursor));
-
-
-        // verify the final insert on rcs participants
-        ContentValues expectedRcsValues = new ContentValues(1);
-        expectedRcsValues.put(RcsParticipantColumns.CANONICAL_ADDRESS_ID_COLUMN, 456);
-        expectedRcsValues.put(RcsParticipantColumns.RCS_ALIAS_COLUMN, "alias");
-        mFakeRcsProvider.addExpectedOperation(new ExpectedInsert(
-                Uri.parse("content://rcs/participant"), expectedRcsValues,
-                Uri.parse("content://rcs/participant/1001")));
-
-        int participantId =
-                mRcsMessageStoreController.createRcsParticipant(canonicalAddress, "alias",
-                        getPackageName());
-
-        assertThat(participantId).isEqualTo(1001);
-    }
-
-    @Test
-    public void testUpdateRcsParticipantAlias() {
-        ContentValues contentValues = new ContentValues(1);
-        contentValues.put("rcs_alias", "New Alias");
-        mFakeRcsProvider.addExpectedOperation(new ExpectedUpdate(
-                Uri.parse("content://rcs/participant/551"), null, null, contentValues, 0));
-
-        try {
-            mRcsMessageStoreController.setRcsParticipantAlias(551, "New Alias", getPackageName());
-        } catch (RuntimeException e) {
-            // eat the exception as there is no provider - we care about the expected update assert
-        }
-    }
-
-    @Test
-    public void testSet1To1ThreadFallbackThreadId() {
-        ContentValues contentValues = new ContentValues(1);
-        contentValues.put(FALLBACK_THREAD_ID_COLUMN, 456L);
-        mFakeRcsProvider.addExpectedOperation(new ExpectedUpdate(
-                Uri.parse("content://rcs/p2p_thread/123"), null, null, contentValues, 0));
-        try {
-            mRcsMessageStoreController.set1To1ThreadFallbackThreadId(123, 456L, getPackageName());
-        } catch (RuntimeException e) {
-            // eat the exception as there is no provider - we care about the expected update assert
-        }
-    }
-
-    @Test
-    public void testSetGroupThreadName() {
-        ContentValues contentValues = new ContentValues(1);
-        contentValues.put(GROUP_NAME_COLUMN, "new name");
-        mFakeRcsProvider.addExpectedOperation(new ExpectedUpdate(
-                Uri.parse("content://rcs/group_thread/345"), null, null, contentValues, 0));
-
-        try {
-            mRcsMessageStoreController.setGroupThreadName(345, "new name", getPackageName());
-        } catch (RuntimeException e) {
-            // eat the exception as there is no provider - we care about the expected update assert
-        }
-    }
-
-    @Test
-    public void testSetGroupThreadIcon() {
-        ContentValues contentValues = new ContentValues(1);
-        contentValues.put(GROUP_ICON_COLUMN, "newIcon");
-        mFakeRcsProvider.addExpectedOperation(new ExpectedUpdate(
-                Uri.parse("content://rcs/group_thread/345"), null, null, contentValues, 0));
-
-        try {
-            mRcsMessageStoreController.setGroupThreadIcon(345, Uri.parse("newIcon"),
-                    getPackageName());
-        } catch (RuntimeException e) {
-            // eat the exception as there is no provider - we care about the expected update assert
-        }
-    }
-
-    @Test
-    public void testSetGroupThreadOwner() {
-        ContentValues contentValues = new ContentValues(1);
-        contentValues.put(OWNER_PARTICIPANT_COLUMN, 9);
-        mFakeRcsProvider.addExpectedOperation(new ExpectedUpdate(
-                Uri.parse("content://rcs/group_thread/454"), null, null, contentValues, 0));
-
-        try {
-            mRcsMessageStoreController.setGroupThreadOwner(454, 9, getPackageName());
-        } catch (RuntimeException e) {
-            // eat the exception as there is no provider - we care about the expected update assert
-        }
-    }
-
-    private String getPackageName() {
-        return mContext.getOpPackageName();
-    }
-}
diff --git a/tests/telephonytests/src/com/android/internal/telephony/imsphone/ImsCallTest.java b/tests/telephonytests/src/com/android/internal/telephony/imsphone/ImsCallTest.java
index d64c69d..9327414 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/imsphone/ImsCallTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/imsphone/ImsCallTest.java
@@ -16,24 +16,25 @@
 
 package com.android.internal.telephony.imsphone;
 
-import android.os.Bundle;
-import android.telephony.ServiceState;
-import android.test.suitebuilder.annotation.SmallTest;
-
-import com.android.ims.ImsCall;
-import android.telephony.ims.ImsCallProfile;
-
-import com.android.internal.telephony.TelephonyTest;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
+import static junit.framework.Assert.assertNotNull;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assert.assertTrue;
-import static org.mockito.Mockito.doReturn;
+
+import android.os.Bundle;
+import android.telephony.ServiceState;
+import android.telephony.TelephonyManager;
+import android.telephony.ims.ImsCallProfile;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import com.android.ims.ImsCall;
+import com.android.internal.telephony.TelephonyTest;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
 
 public class ImsCallTest extends TelephonyTest {
 
@@ -54,14 +55,46 @@
 
     @Test
     @SmallTest
-    public void testSetWifi() {
+    public void testSetWifiDeprecated() {
         ImsCall mTestImsCall = new ImsCall(mContext, mTestCallProfile);
         assertFalse(mTestImsCall.isWifiCall());
-        assertNotEquals(mTestImsCall.getRadioTechnology(), ServiceState.RIL_RADIO_TECHNOLOGY_LTE);
+        assertNotEquals(mTestImsCall.getNetworkType(), TelephonyManager.NETWORK_TYPE_LTE);
+        // use deprecated API
         mBundle.putString(ImsCallProfile.EXTRA_CALL_RAT_TYPE,
                 ServiceState.RIL_RADIO_TECHNOLOGY_IWLAN + "");
         assertTrue(mTestImsCall.isWifiCall());
-        assertNotEquals(mTestImsCall.getRadioTechnology(), ServiceState.RIL_RADIO_TECHNOLOGY_LTE);
+        assertNotEquals(mTestImsCall.getNetworkType(), TelephonyManager.NETWORK_TYPE_LTE);
+    }
+
+    @Test
+    @SmallTest
+    public void testNullCallProfile() {
+        ImsCall imsCall = new ImsCall(mContext, null /* imsCallProfile */);
+        assertNotNull(imsCall);
+        assertFalse(imsCall.wasVideoCall());
+    }
+
+    @Test
+    @SmallTest
+    public void testNonNulllVideoProfile() {
+        ImsCallProfile profile = new ImsCallProfile();
+        profile.mCallType = ImsCallProfile.CALL_TYPE_VT_TX;
+
+        ImsCall imsCall = new ImsCall(mContext, profile);
+        assertNotNull(imsCall);
+        assertTrue(imsCall.wasVideoCall());
+    }
+
+    @Test
+    @SmallTest
+    public void testSetWifi() {
+        ImsCall mTestImsCall = new ImsCall(mContext, mTestCallProfile);
+        assertFalse(mTestImsCall.isWifiCall());
+        assertNotEquals(mTestImsCall.getNetworkType(), TelephonyManager.NETWORK_TYPE_LTE);
+        mBundle.putInt(ImsCallProfile.EXTRA_CALL_NETWORK_TYPE,
+                TelephonyManager.NETWORK_TYPE_IWLAN);
+        assertTrue(mTestImsCall.isWifiCall());
+        assertNotEquals(mTestImsCall.getNetworkType(), TelephonyManager.NETWORK_TYPE_LTE);
     }
 
     @Test
@@ -69,11 +102,23 @@
     public void testSetWifiAlt() {
         ImsCall mTestImsCall = new ImsCall(mContext, mTestCallProfile);
         assertFalse(mTestImsCall.isWifiCall());
-        assertNotEquals(mTestImsCall.getRadioTechnology(), ServiceState.RIL_RADIO_TECHNOLOGY_LTE);
+        assertNotEquals(mTestImsCall.getNetworkType(), TelephonyManager.NETWORK_TYPE_LTE);
         mBundle.putString(ImsCallProfile.EXTRA_CALL_RAT_TYPE_ALT,
                 ServiceState.RIL_RADIO_TECHNOLOGY_IWLAN + "");
         assertTrue(mTestImsCall.isWifiCall());
-        assertNotEquals(mTestImsCall.getRadioTechnology(), ServiceState.RIL_RADIO_TECHNOLOGY_LTE);
+        assertNotEquals(mTestImsCall.getNetworkType(), TelephonyManager.NETWORK_TYPE_LTE);
+    }
+
+    @Test
+    @SmallTest
+    public void testSetLteNoWifiDeprecated() {
+        ImsCall mTestImsCall = new ImsCall(mContext, mTestCallProfile);
+        assertFalse(mTestImsCall.isWifiCall());
+        assertNotEquals(mTestImsCall.getNetworkType(), TelephonyManager.NETWORK_TYPE_LTE);
+        mBundle.putString(ImsCallProfile.EXTRA_CALL_RAT_TYPE,
+                ServiceState.RIL_RADIO_TECHNOLOGY_LTE + "");
+        assertFalse(mTestImsCall.isWifiCall());
+        assertEquals(mTestImsCall.getNetworkType(), TelephonyManager.NETWORK_TYPE_LTE);
     }
 
     @Test
@@ -81,11 +126,11 @@
     public void testSetLteNoWifi() {
         ImsCall mTestImsCall = new ImsCall(mContext, mTestCallProfile);
         assertFalse(mTestImsCall.isWifiCall());
-        assertNotEquals(mTestImsCall.getRadioTechnology(), ServiceState.RIL_RADIO_TECHNOLOGY_LTE);
-        mBundle.putString(ImsCallProfile.EXTRA_CALL_RAT_TYPE,
-                ServiceState.RIL_RADIO_TECHNOLOGY_LTE + "");
+        assertNotEquals(mTestImsCall.getNetworkType(), TelephonyManager.NETWORK_TYPE_LTE);
+        mBundle.putInt(ImsCallProfile.EXTRA_CALL_NETWORK_TYPE,
+                TelephonyManager.NETWORK_TYPE_LTE);
         assertFalse(mTestImsCall.isWifiCall());
-        assertEquals(mTestImsCall.getRadioTechnology(), ServiceState.RIL_RADIO_TECHNOLOGY_LTE);
+        assertEquals(mTestImsCall.getNetworkType(), TelephonyManager.NETWORK_TYPE_LTE);
     }
 
     @Test
@@ -93,10 +138,10 @@
     public void testSetLteNoWifiAlt() {
         ImsCall mTestImsCall = new ImsCall(mContext, mTestCallProfile);
         assertFalse(mTestImsCall.isWifiCall());
-        assertNotEquals(mTestImsCall.getRadioTechnology(), ServiceState.RIL_RADIO_TECHNOLOGY_LTE);
+        assertNotEquals(mTestImsCall.getNetworkType(), TelephonyManager.NETWORK_TYPE_LTE);
         mBundle.putString(ImsCallProfile.EXTRA_CALL_RAT_TYPE_ALT,
                 ServiceState.RIL_RADIO_TECHNOLOGY_LTE + "");
         assertFalse(mTestImsCall.isWifiCall());
-        assertEquals(mTestImsCall.getRadioTechnology(), ServiceState.RIL_RADIO_TECHNOLOGY_LTE);
+        assertEquals(mTestImsCall.getNetworkType(), TelephonyManager.NETWORK_TYPE_LTE);
     }
 }
diff --git a/tests/telephonytests/src/com/android/internal/telephony/imsphone/ImsPhoneCallTest.java b/tests/telephonytests/src/com/android/internal/telephony/imsphone/ImsPhoneCallTest.java
index 82c62dd..31fbfad 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/imsphone/ImsPhoneCallTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/imsphone/ImsPhoneCallTest.java
@@ -30,6 +30,8 @@
 
 import androidx.test.filters.FlakyTest;
 
+import com.android.ims.ImsCall;
+import com.android.ims.ImsException;
 import com.android.internal.telephony.Call;
 import com.android.internal.telephony.TelephonyTest;
 
@@ -90,7 +92,6 @@
     }
 
     @FlakyTest
-    @Ignore
     @Test
     @SmallTest
     public void testConnectionDisconnected() {
@@ -106,6 +107,8 @@
         doReturn(Call.State.DISCONNECTED).when(mConnection2).getState();
         mImsCallUT.connectionDisconnected(null);
         assertEquals(Call.State.DISCONNECTED, mImsCallUT.getState());
+        mImsCallUT.onHangupLocal();
+        assertEquals(Call.State.DISCONNECTED, mImsCallUT.getState());
     }
 
     @FlakyTest
@@ -182,4 +185,28 @@
         mImsCallUT.isMultiparty();
         verify(mImsCall, times(1)).isMultiparty();
     }
+
+    @Test
+    @SmallTest
+    public void testGetImsCall() {
+        doReturn(mImsCall).when(mConnection1).getImsCall();
+        mImsCallUT.attach(mConnection1, Call.State.ACTIVE);
+
+        ImsCall imsCall = mImsCallUT.getImsCall();
+        assertEquals(mImsCall, imsCall);
+    }
+
+    @Test
+    @SmallTest
+    public void testSetMute() {
+        doReturn(mImsCall).when(mConnection1).getImsCall();
+        mImsCallUT.attach(mConnection1, Call.State.ACTIVE);
+
+        mImsCallUT.setMute(true);
+        try {
+            verify(mImsCall).setMute(eq(true));
+        } catch (ImsException e) {
+            fail("Exception unexpected");
+        }
+    }
 }
diff --git a/tests/telephonytests/src/com/android/internal/telephony/imsphone/ImsPhoneCallTrackerTest.java b/tests/telephonytests/src/com/android/internal/telephony/imsphone/ImsPhoneCallTrackerTest.java
index f128404..0a972ab 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/imsphone/ImsPhoneCallTrackerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/imsphone/ImsPhoneCallTrackerTest.java
@@ -15,11 +15,22 @@
  */
 package com.android.internal.telephony.imsphone;
 
-import static com.android.internal.telephony.TelephonyTestUtils.waitForMs;
+import static android.net.NetworkStats.DEFAULT_NETWORK_YES;
+import static android.net.NetworkStats.METERED_YES;
+import static android.net.NetworkStats.ROAMING_NO;
+import static android.net.NetworkStats.SET_FOREGROUND;
+import static android.net.NetworkStats.TAG_NONE;
+import static android.net.NetworkStats.UID_ALL;
+
+import static com.android.testutils.NetworkStatsUtilsKt.assertNetworkStatsEquals;
+
+import static junit.framework.Assert.assertNotNull;
+import static junit.framework.TestCase.fail;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.ArgumentMatchers.nullable;
 import static org.mockito.Mockito.any;
 import static org.mockito.Mockito.anyBoolean;
@@ -32,26 +43,31 @@
 import static org.mockito.Mockito.isNull;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.reset;
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
 
+import android.annotation.Nullable;
 import android.content.Context;
 import android.content.SharedPreferences;
 import android.content.pm.PackageManager;
+import android.net.NetworkStats;
+import android.net.NetworkStats.Entry;
+import android.net.netstats.provider.INetworkStatsProviderCallback;
 import android.os.Bundle;
-import android.os.Handler;
-import android.os.HandlerThread;
 import android.os.Message;
 import android.os.PersistableBundle;
+import android.os.RemoteException;
 import android.telecom.VideoProfile;
 import android.telephony.CarrierConfigManager;
 import android.telephony.DisconnectCause;
 import android.telephony.PhoneNumberUtils;
-import android.telephony.ServiceState;
 import android.telephony.TelephonyManager;
 import android.telephony.ims.ImsCallProfile;
 import android.telephony.ims.ImsCallSession;
+import android.telephony.ims.ImsConferenceState;
 import android.telephony.ims.ImsMmTelManager;
 import android.telephony.ims.ImsReasonInfo;
 import android.telephony.ims.ImsStreamMediaProfile;
@@ -59,6 +75,8 @@
 import android.telephony.ims.feature.MmTelFeature;
 import android.telephony.ims.stub.ImsRegistrationImplBase;
 import android.test.suitebuilder.annotation.SmallTest;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
 
 import androidx.test.filters.FlakyTest;
 
@@ -72,28 +90,30 @@
 import com.android.internal.telephony.Connection;
 import com.android.internal.telephony.PhoneConstants;
 import com.android.internal.telephony.TelephonyTest;
+import com.android.internal.telephony.imsphone.ImsPhoneCallTracker.VtDataUsageProvider;
 
 import org.junit.After;
 import org.junit.Assert;
 import org.junit.Before;
 import org.junit.Ignore;
 import org.junit.Test;
+import org.junit.runner.RunWith;
 import org.mockito.ArgumentCaptor;
 import org.mockito.Mock;
 import org.mockito.invocation.InvocationOnMock;
 import org.mockito.stubbing.Answer;
 
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper
 public class ImsPhoneCallTrackerTest extends TelephonyTest {
     private ImsPhoneCallTracker mCTUT;
-    private ImsCTHandlerThread mImsCTHandlerThread;
     private MmTelFeature.Listener mMmTelListener;
-    private ImsMmTelManager.RegistrationCallback mRegistrationCallback;
     private ImsMmTelManager.CapabilityCallback mCapabilityCallback;
     private ImsCall.Listener mImsCallListener;
     private ImsCall mImsCall;
     private ImsCall mSecondImsCall;
     private Bundle mBundle = new Bundle();
-    private int mServiceId;
+    @Nullable private VtDataUsageProvider mVtDataUsageProvider;
     @Mock
     private ImsCallSession mImsCallSession;
     @Mock
@@ -102,34 +122,10 @@
     private ImsPhoneConnection.Listener mImsPhoneConnectionListener;
     @Mock
     private ImsConfig mImsConfig;
-    private Handler mCTHander;
-
-    private class ImsCTHandlerThread extends HandlerThread {
-
-        private ImsCTHandlerThread(String name) {
-            super(name);
-        }
-        @Override
-        public void onLooperPrepared() {
-            mCTUT = new ImsPhoneCallTracker(mImsPhone, Runnable::run);
-            mCTUT.addReasonCodeRemapping(null, "Wifi signal lost.", ImsReasonInfo.CODE_WIFI_LOST);
-            mCTUT.addReasonCodeRemapping(501, "Call answered elsewhere.",
-                    ImsReasonInfo.CODE_ANSWERED_ELSEWHERE);
-            mCTUT.addReasonCodeRemapping(510, "Call answered elsewhere.",
-                    ImsReasonInfo.CODE_ANSWERED_ELSEWHERE);
-            mCTUT.addReasonCodeRemapping(ImsReasonInfo.CODE_USER_TERMINATED_BY_REMOTE, "",
-                    ImsReasonInfo.CODE_SIP_FORBIDDEN);
-            mCTUT.addReasonCodeRemapping(ImsReasonInfo.CODE_SIP_SERVICE_UNAVAILABLE,
-                    "emergency calls over wifi not allowed in this location",
-                    ImsReasonInfo.CODE_EMERGENCY_CALL_OVER_WFC_NOT_AVAILABLE);
-            mCTUT.addReasonCodeRemapping(ImsReasonInfo.CODE_SIP_FORBIDDEN,
-                    "service not allowed in this location",
-                    ImsReasonInfo.CODE_WFC_SERVICE_NOT_AVAILABLE_IN_THIS_LOCATION);
-            mCTUT.setDataEnabled(true);
-            mCTHander = new Handler(mCTUT.getLooper());
-            setReady(true);
-        }
-    }
+    @Mock
+    private ImsPhoneConnection mImsPhoneConnection;
+    @Mock
+    private INetworkStatsProviderCallback mVtDataUsageProviderCb;
 
     private void imsCallMocking(final ImsCall imsCall) throws Exception {
 
@@ -186,12 +182,12 @@
     @Before
     public void setUp() throws Exception {
         super.setUp(this.getClass().getSimpleName());
-        mServiceId = 0;
         mImsCallProfile.mCallExtras = mBundle;
         mImsManagerInstances.put(mImsPhone.getPhoneId(), mImsManager);
         mImsCall = spy(new ImsCall(mContext, mImsCallProfile));
         mSecondImsCall = spy(new ImsCall(mContext, mImsCallProfile));
         mImsPhoneConnectionListener = mock(ImsPhoneConnection.Listener.class);
+        mImsPhoneConnection = mock(ImsPhoneConnection.class);
         imsCallMocking(mImsCall);
         imsCallMocking(mSecondImsCall);
         doReturn(ImsFeature.STATE_READY).when(mImsManager).getImsServiceState();
@@ -208,11 +204,11 @@
             @Override
             public ImsCall answer(InvocationOnMock invocation) throws Throwable {
                 mImsCallListener =
-                        (ImsCall.Listener) invocation.getArguments()[2];
+                        (ImsCall.Listener) invocation.getArguments()[1];
                 mImsCall.setListener(mImsCallListener);
                 return mImsCall;
             }
-        }).when(mImsManager).takeCall(any(), any(), any());
+        }).when(mImsManager).takeCall(any(), any());
 
         doAnswer(new Answer<ImsCall>() {
             @Override
@@ -222,16 +218,10 @@
                 mSecondImsCall.setListener(mImsCallListener);
                 return mSecondImsCall;
             }
-        }).when(mImsManager).makeCall(eq(mImsCallProfile), (String []) any(),
+        }).when(mImsManager).makeCall(eq(mImsCallProfile), (String[]) any(),
                 (ImsCall.Listener) any());
 
         doAnswer(invocation -> {
-            mRegistrationCallback = invocation.getArgument(0);
-            return mRegistrationCallback;
-        }).when(mImsManager).addRegistrationCallback(
-                any(android.telephony.ims.ImsMmTelManager.RegistrationCallback.class));
-
-        doAnswer(invocation -> {
             mCapabilityCallback = (ImsMmTelManager.CapabilityCallback) invocation.getArguments()[0];
             return mCapabilityCallback;
 
@@ -241,54 +231,42 @@
 
         doNothing().when(mImsManager).addNotifyStatusChangedCallbackIfAvailable(any());
 
-        mImsCTHandlerThread = new ImsCTHandlerThread(this.getClass().getSimpleName());
-        mImsCTHandlerThread.start();
+        mCTUT = new ImsPhoneCallTracker(mImsPhone, Runnable::run);
+        mCTUT.addReasonCodeRemapping(null, "Wifi signal lost.", ImsReasonInfo.CODE_WIFI_LOST);
+        mCTUT.addReasonCodeRemapping(501, "Call answered elsewhere.",
+                ImsReasonInfo.CODE_ANSWERED_ELSEWHERE);
+        mCTUT.addReasonCodeRemapping(510, "Call answered elsewhere.",
+                ImsReasonInfo.CODE_ANSWERED_ELSEWHERE);
+        mCTUT.addReasonCodeRemapping(ImsReasonInfo.CODE_USER_TERMINATED_BY_REMOTE, "",
+                ImsReasonInfo.CODE_SIP_FORBIDDEN);
+        mCTUT.addReasonCodeRemapping(ImsReasonInfo.CODE_SIP_SERVICE_UNAVAILABLE,
+                "emergency calls over wifi not allowed in this location",
+                ImsReasonInfo.CODE_EMERGENCY_CALL_OVER_WFC_NOT_AVAILABLE);
+        mCTUT.addReasonCodeRemapping(ImsReasonInfo.CODE_SIP_FORBIDDEN,
+                "service not allowed in this location",
+                ImsReasonInfo.CODE_WFC_SERVICE_NOT_AVAILABLE_IN_THIS_LOCATION);
+        mCTUT.setDataEnabled(true);
 
-        waitUntilReady();
+        final ArgumentCaptor<VtDataUsageProvider> vtDataUsageProviderCaptor =
+                ArgumentCaptor.forClass(VtDataUsageProvider.class);
+        verify(mStatsManager).registerNetworkStatsProvider(anyString(),
+                vtDataUsageProviderCaptor.capture());
+        mVtDataUsageProvider = vtDataUsageProviderCaptor.getValue();
+        assertNotNull(mVtDataUsageProvider);
+        mVtDataUsageProvider.setProviderCallbackBinder(mVtDataUsageProviderCb);
+
         logd("ImsPhoneCallTracker initiated");
-        /* Make sure getImsService is triggered on handler */
-        waitForHandlerAction(mCTHander, 100);
+        processAllMessages();
     }
 
     @After
     public void tearDown() throws Exception {
         mCTUT = null;
-        mImsCTHandlerThread.quit();
         super.tearDown();
     }
 
     @Test
     @SmallTest
-    public void testImsRegistered() {
-        // when IMS is registered
-        mRegistrationCallback.onRegistered(ImsRegistrationImplBase.REGISTRATION_TECH_LTE);
-        // then service state should be IN_SERVICE and ImsPhone state set to registered
-        verify(mImsPhone).setServiceState(eq(ServiceState.STATE_IN_SERVICE));
-        verify(mImsPhone).setImsRegistered(eq(true));
-    }
-
-    @Test
-    @SmallTest
-    public void testImsRegistering() {
-        // when IMS is registering
-        mRegistrationCallback.onRegistering(ImsRegistrationImplBase.REGISTRATION_TECH_LTE);
-        // then service state should be OUT_OF_SERVICE and ImsPhone state set to not registered
-        verify(mImsPhone).setServiceState(eq(ServiceState.STATE_OUT_OF_SERVICE));
-        verify(mImsPhone).setImsRegistered(eq(false));
-    }
-
-    @Test
-    @SmallTest
-    public void testImsDeregistered() {
-        // when IMS is deregistered
-        mRegistrationCallback.onUnregistered(new ImsReasonInfo());
-        // then service state should be OUT_OF_SERVICE and ImsPhone state set to not registered
-        verify(mImsPhone).setServiceState(eq(ServiceState.STATE_OUT_OF_SERVICE));
-        verify(mImsPhone).setImsRegistered(eq(false));
-    }
-
-    @Test
-    @SmallTest
     public void testVowifiDisabledOnLte() {
         // LTE is registered.
         doReturn(ImsRegistrationImplBase.REGISTRATION_TECH_LTE).when(
@@ -299,7 +277,7 @@
         MmTelFeature.MmTelCapabilities caps = new MmTelFeature.MmTelCapabilities();
         caps.addCapabilities(MmTelFeature.MmTelCapabilities.CAPABILITY_TYPE_VOICE);
         mCapabilityCallback.onCapabilitiesStatusChanged(caps);
-        waitForHandlerAction(mCTHander, 1000);
+        processAllMessages();
 
         // Voice over IWLAN is still disabled
         assertFalse(mCTUT.isVowifiEnabled());
@@ -317,7 +295,7 @@
         MmTelFeature.MmTelCapabilities caps = new MmTelFeature.MmTelCapabilities();
         caps.addCapabilities(MmTelFeature.MmTelCapabilities.CAPABILITY_TYPE_VOICE);
         mCapabilityCallback.onCapabilitiesStatusChanged(caps);
-        waitForHandlerAction(mCTHander, 1000);
+        processAllMessages();
 
         // Voice over IWLAN is enabled
         assertTrue(mCTUT.isVowifiEnabled());
@@ -335,7 +313,7 @@
         MmTelFeature.MmTelCapabilities caps = new MmTelFeature.MmTelCapabilities();
         caps.addCapabilities(MmTelFeature.MmTelCapabilities.CAPABILITY_TYPE_VOICE);
         mCapabilityCallback.onCapabilitiesStatusChanged(caps);
-        waitForHandlerAction(mCTHander, 1000);
+        processAllMessages();
 
         assertTrue(mCTUT.isVolteEnabled());
         assertFalse(mCTUT.isVideoCallEnabled());
@@ -348,7 +326,7 @@
         capsVideo.addCapabilities(MmTelFeature.MmTelCapabilities.CAPABILITY_TYPE_VOICE);
         capsVideo.addCapabilities(MmTelFeature.MmTelCapabilities.CAPABILITY_TYPE_VIDEO);
         mCapabilityCallback.onCapabilitiesStatusChanged(capsVideo);
-        waitForHandlerAction(mCTHander, 1000);
+        processAllMessages();
         assertTrue(mCTUT.isVideoCallEnabled());
         verify(mImsPhone, times(1)).notifyForVideoCapabilityChanged(eq(true));
     }
@@ -356,6 +334,8 @@
     @Test
     @SmallTest
     public void testImsMTCall() {
+        mImsCallProfile.setCallerNumberVerificationStatus(
+                ImsCallProfile.VERIFICATION_STATUS_PASSED);
         assertEquals(PhoneConstants.State.IDLE, mCTUT.getState());
         assertFalse(mCTUT.mRingingCall.isRinging());
         // mock a MT call
@@ -368,6 +348,8 @@
         ImsPhoneConnection connection =
                 (ImsPhoneConnection) mCTUT.mRingingCall.getConnections().get(0);
         connection.addListener(mImsPhoneConnectionListener);
+        assertEquals(android.telecom.Connection.VERIFICATION_STATUS_PASSED,
+                connection.getNumberVerificationStatus());
     }
 
     @Test
@@ -391,6 +373,73 @@
 
     @Test
     @SmallTest
+    public void testImsCepOnPeer() throws Exception {
+        testImsMTCallAccept();
+        doReturn(false).when(mImsCall).isConferenceHost();
+        doReturn(true).when(mImsCall).isMultiparty();
+
+        injectConferenceState();
+
+        verify(mImsPhoneConnectionListener).onConferenceParticipantsChanged(any());
+    }
+
+    @Test
+    @SmallTest
+    public void testImsNoCepOnPeer() throws Exception {
+        mCTUT.setSupportCepOnPeer(false);
+
+        testImsMTCallAccept();
+        doReturn(false).when(mImsCall).isConferenceHost();
+        doReturn(true).when(mImsCall).isMultiparty();
+
+        injectConferenceState();
+
+        verify(mImsPhoneConnectionListener, never()).onConferenceParticipantsChanged(any());
+    }
+
+    private void injectConferenceState() {
+        ImsPhoneConnection connection = mCTUT.getConnections().get(0);
+        connection.addListener(mImsPhoneConnectionListener);
+
+        ImsConferenceState state = new ImsConferenceState();
+        // Yuck
+        Bundle participant = new Bundle();
+        participant.putString(ImsConferenceState.USER, "sip:6505551212@fakeims.com");
+        participant.putString(ImsConferenceState.DISPLAY_TEXT, "yuck");
+        participant.putString(ImsConferenceState.ENDPOINT, "sip:6505551212@fakeims.com");
+        participant.putString(ImsConferenceState.STATUS, "connected");
+        state.mParticipants.put("sip:6505551212@fakeims.com", participant);
+
+        mImsCall.conferenceStateUpdated(state);
+    }
+
+    @Test
+    @SmallTest
+    public void testImsHoldException() throws Exception {
+        testImsMTCallAccept();
+        doThrow(new ImsException()).when(mImsCall).hold();
+        try {
+            mCTUT.holdActiveCall();
+            Assert.fail("No exception thrown");
+        } catch (Exception e) {
+            // expected
+            verify(mImsCall).hold();
+        }
+
+        // After the first hold exception, try holding (successfully) again to make sure that it
+        // goes through
+        doNothing().when(mImsCall).hold();
+        try {
+            mCTUT.holdActiveCall();
+            verify(mImsCall, times(2)).hold();
+        } catch (Exception ex) {
+            ex.printStackTrace();
+            Assert.fail("unexpected exception thrown" + ex.getMessage());
+        }
+    }
+
+    @Test
+    @SmallTest
     public void testImsMTCallReject() {
         testImsMTCall();
         assertTrue(mCTUT.mRingingCall.isRinging());
@@ -432,7 +481,7 @@
         // mock a new MT
         try {
             doReturn(mSecondImsCall).when(mImsManager).takeCall(any(IImsCallSession.class),
-                    any(Bundle.class), any(ImsCall.Listener.class));
+                    any(ImsCall.Listener.class));
         } catch (Exception ex) {
             ex.printStackTrace();
             Assert.fail("unexpected exception thrown" + ex.getMessage());
@@ -454,7 +503,7 @@
             Assert.fail("unexpected exception thrown" + ex.getMessage());
         }
 
-        waitForMs(100);
+        processAllMessages();
         assertEquals(Call.State.ACTIVE, mCTUT.mForegroundCall.getState());
         assertFalse(mCTUT.mRingingCall.isRinging());
         assertEquals(Call.State.HOLDING, mCTUT.mBackgroundCall.getState());
@@ -482,7 +531,7 @@
         }
 
         // Ensure that the correct key was queried from the shared prefs.
-        assertEquals("clir_key0", mStringCaptor.getValue());
+        assertEquals("clir_sub_key0", mStringCaptor.getValue());
     }
 
     /**
@@ -571,7 +620,7 @@
             ex.printStackTrace();
             Assert.fail("unexpected exception thrown" + ex.getMessage());
         }
-        waitForMs(100);
+        processAllMessages();
         assertEquals(Call.State.DIALING, mCTUT.mForegroundCall.getState());
         assertEquals(Call.State.HOLDING, mCTUT.mBackgroundCall.getState());
     }
@@ -602,7 +651,7 @@
         // mock a new MT
         try {
             doReturn(mSecondImsCall).when(mImsManager).takeCall(any(IImsCallSession.class),
-                    any(Bundle.class), any(ImsCall.Listener.class));
+                    any(ImsCall.Listener.class));
             mMmTelListener.onIncomingCall(mock(IImsCallSession.class), Bundle.EMPTY);
             mCTUT.acceptCall(ImsCallProfile.CALL_TYPE_VOICE);
         } catch (Exception ex) {
@@ -610,7 +659,7 @@
             Assert.fail("unexpected exception thrown" + ex.getMessage());
         }
 
-        waitForMs(100);
+        processAllMessages();
 
         mCTUT.sendDtmf(PhoneNumberUtils.WAIT, null);
         //verify trigger sendDtmf to mImsSecondCall
@@ -652,8 +701,7 @@
             Assert.fail();
         }
 
-        // wait for handler to process ImsService connection retry
-        waitForHandlerAction(mCTHander, 1000); // 1 second timeout
+        processAllMessages();
         verify(mImsManager, never()).makeCall(nullable(ImsCallProfile.class),
                 eq(new String[]{"+17005554141"}), nullable(ImsCall.Listener.class));
         // Make sure that open is called in ImsPhoneCallTracker when it was first connected and
@@ -675,14 +723,52 @@
 
         mCTUT.setUiTTYMode(0, new Message());
 
-        // wait for handler to process ImsService connection retry
-        waitForHandlerAction(mCTHander, 100);
+        processAllMessages();
         // Make sure that open is called in ImsPhoneCallTracker to re-establish connection to
         // ImsService
         verify(mImsManager, times(2)).open(
                 nullable(MmTelFeature.Listener.class));
     }
 
+    @Test
+    @SmallTest
+    public void testRewriteOutgoingNumber() {
+        try {
+            doAnswer(new Answer<ImsCall>() {
+                @Override
+                public ImsCall answer(InvocationOnMock invocation) throws Throwable {
+                    mImsCallListener =
+                            (ImsCall.Listener) invocation.getArguments()[2];
+                    ImsCall imsCall = spy(new ImsCall(mContext, mImsCallProfile));
+                    imsCall.setListener(mImsCallListener);
+                    imsCallMocking(imsCall);
+                    return imsCall;
+                }
+            }).when(mImsManager).makeCall(eq(mImsCallProfile), (String[]) any(),
+                    (ImsCall.Listener) any());
+        } catch (ImsException ie) {
+        }
+
+        // Perform a dial string remapping.
+        PersistableBundle bundle = mContextFixture.getCarrierConfigBundle();
+        bundle.putStringArray(CarrierConfigManager.KEY_DIAL_STRING_REPLACE_STRING_ARRAY,
+                new String[] {"*55:6505551212"});
+
+        ImsPhoneConnection connection = null;
+        try {
+            connection = (ImsPhoneConnection) mCTUT.dial("*55",
+                    ImsCallProfile.CALL_TYPE_VOICE, null);
+        } catch (Exception ex) {
+            ex.printStackTrace();
+            Assert.fail("unexpected exception thrown" + ex.getMessage());
+        }
+        if (connection == null) {
+            Assert.fail("connection is null");
+        }
+        Assert.assertEquals("6505551212", connection.getConvertedNumber());
+        Assert.assertEquals("*55", connection.getAddress());
+    }
+
     /**
      * Test notification of handover from LTE to WIFI and WIFI to LTE and ensure that the expected
      * connection events are sent.
@@ -702,18 +788,18 @@
 
         // First handover from LTE to WIFI; this takes us into a mid-call state.
         call.getImsCallSessionListenerProxy().callSessionHandover(call.getCallSession(),
-                ServiceState.RIL_RADIO_TECHNOLOGY_LTE, ServiceState.RIL_RADIO_TECHNOLOGY_IWLAN,
+                TelephonyManager.NETWORK_TYPE_LTE, TelephonyManager.NETWORK_TYPE_IWLAN,
                 new ImsReasonInfo());
         // Handover back to LTE.
         call.getImsCallSessionListenerProxy().callSessionHandover(call.getCallSession(),
-                ServiceState.RIL_RADIO_TECHNOLOGY_IWLAN, ServiceState.RIL_RADIO_TECHNOLOGY_LTE,
+                TelephonyManager.NETWORK_TYPE_IWLAN, TelephonyManager.NETWORK_TYPE_LTE,
                 new ImsReasonInfo());
         verify(mImsPhoneConnectionListener).onConnectionEvent(eq(
                 TelephonyManager.EVENT_HANDOVER_VIDEO_FROM_WIFI_TO_LTE), isNull());
 
         // Finally hand back to WIFI
         call.getImsCallSessionListenerProxy().callSessionHandover(call.getCallSession(),
-                ServiceState.RIL_RADIO_TECHNOLOGY_LTE, ServiceState.RIL_RADIO_TECHNOLOGY_IWLAN,
+                TelephonyManager.NETWORK_TYPE_LTE, TelephonyManager.NETWORK_TYPE_IWLAN,
                 new ImsReasonInfo());
         verify(mImsPhoneConnectionListener).onConnectionEvent(eq(
                 TelephonyManager.EVENT_HANDOVER_VIDEO_FROM_LTE_TO_WIFI), isNull());
@@ -920,6 +1006,23 @@
 
     @Test
     @SmallTest
+    public void testMergeComplete() {
+        boolean[] result = new boolean[1];
+        // Place a call.
+        ImsPhoneConnection connection = placeCallAndMakeActive();
+        connection.addListener(new Connection.ListenerBase() {
+            @Override
+            public void onConnectionEvent(String event, Bundle extras) {
+                result[0] = android.telecom.Connection.EVENT_MERGE_COMPLETE.equals(event);
+            }
+        });
+        ImsCall call = connection.getImsCall();
+        call.getListener().onCallMerged(call, null, false);
+        assertTrue(result[0]);
+    }
+
+    @Test
+    @SmallTest
     public void testNumericOnlyRemap() {
         assertEquals(ImsReasonInfo.CODE_SIP_FORBIDDEN, mCTUT.maybeRemapReasonCode(
                 new ImsReasonInfo(ImsReasonInfo.CODE_USER_TERMINATED_BY_REMOTE, 0)));
@@ -959,7 +1062,148 @@
                                 "SERVICE not allowed in this location")));
     }
 
-    private void placeCallAndMakeActive() {
+    @Test
+    @SmallTest
+    public void testNoHoldErrorMessageWhenCallDisconnected() {
+        when(mImsPhoneConnection.getImsCall()).thenReturn(mImsCall);
+        doAnswer(new Answer<Void>() {
+            @Override
+            public Void answer(InvocationOnMock invocationOnMock) {
+                fail("Error message showed when the call has already been disconnected!");
+                return null;
+            }
+        }).when(mImsPhoneConnection)
+                .onConnectionEvent(eq(android.telecom.Connection.EVENT_CALL_HOLD_FAILED), any());
+        mCTUT.getConnections().add(mImsPhoneConnection);
+        when(mImsPhoneConnection.getState()).thenReturn(ImsPhoneCall.State.DISCONNECTED);
+        ImsReasonInfo info = new ImsReasonInfo(ImsReasonInfo.CODE_UNSPECIFIED,
+                ImsReasonInfo.CODE_UNSPECIFIED, null);
+        mCTUT.getImsCallListener().onCallHoldFailed(mImsPhoneConnection.getImsCall(), info);
+    }
+
+    @Test
+    @SmallTest
+    public void testVtDataUsageProvider() throws RemoteException {
+        mVtDataUsageProvider.onRequestStatsUpdate(11);
+
+        // Verify that requestStatsUpdate triggers onStatsUpdated, where the initial token should
+        // be reported with current stats.
+        assertVtDataUsageUpdated(0, 0, 0);
+
+        // Establish a MT call.
+        testImsMTCallAccept();
+        final ImsPhoneConnection connection = mCTUT.mForegroundCall.getFirstConnection();
+        final ImsCall call = connection.getImsCall();
+        mCTUT.updateVtDataUsage(call, 51);
+
+        // Make another request, and verify stats updated accordingly, with previously issued token.
+        reset(mVtDataUsageProviderCb);
+        mVtDataUsageProvider.onRequestStatsUpdate(13);
+        assertVtDataUsageUpdated(11, 25, 25);
+
+        // Update accumulated data usage twice. updateVtDataUsage takes accumulated stats from
+        // boot up.
+        reset(mVtDataUsageProviderCb);
+        mCTUT.updateVtDataUsage(call, 70);
+        mCTUT.updateVtDataUsage(call, 91);
+        verify(mVtDataUsageProviderCb, never()).notifyStatsUpdated(anyInt(), any(), any());
+
+        // Verify that diff stats from last update is reported accordingly.
+        mVtDataUsageProvider.onRequestStatsUpdate(13);
+        // Rounding error occurs so (70-51)/2 + (91-70)/2 = 19 is expected for both direction.
+        assertVtDataUsageUpdated(13, 19, 19);
+    }
+
+    @Test
+    @SmallTest
+    public void testHangupHandoverCall() throws RemoteException {
+        doReturn("1").when(mImsCallSession).getCallId();
+        assertEquals(PhoneConstants.State.IDLE, mCTUT.getState());
+        assertFalse(mCTUT.mRingingCall.isRinging());
+        // mock a MT call
+        mMmTelListener.onIncomingCall(mock(IImsCallSession.class), Bundle.EMPTY);
+        verify(mImsPhone, times(1)).notifyNewRingingConnection((Connection) any());
+        verify(mImsPhone, times(1)).notifyIncomingRing();
+        assertEquals(PhoneConstants.State.RINGING, mCTUT.getState());
+        assertTrue(mCTUT.mRingingCall.isRinging());
+        assertEquals(1, mCTUT.mRingingCall.getConnections().size());
+        ImsPhoneConnection connection =
+                (ImsPhoneConnection) mCTUT.mRingingCall.getConnections().get(0);
+        connection.addListener(mImsPhoneConnectionListener);
+
+        // Move the connection to the handover state.
+        mCTUT.notifySrvccState(Call.SrvccState.COMPLETED);
+        assertEquals(1, mCTUT.mHandoverCall.getConnections().size());
+
+        // No need to go through all the rigamarole of the mocked termination we normally do; we
+        // can confirm the hangup gets processed without all that.
+        doNothing().when(mImsCall).terminate(anyInt());
+
+        try {
+            mCTUT.hangup(mCTUT.mHandoverCall);
+        } catch (CallStateException e) {
+            Assert.fail("CallStateException not expected");
+        }
+        assertEquals(DisconnectCause.LOCAL, connection.getDisconnectCause());
+    }
+
+    /**
+     * Verifies that the {@link ImsPhoneCallTracker#getState()} goes to IDLE when an SRVCC takes
+     * place.
+     * @throws RemoteException
+     */
+    @Test
+    @SmallTest
+    public void testTrackerStateOnHandover() throws RemoteException {
+        doReturn("1").when(mImsCallSession).getCallId();
+        assertEquals(PhoneConstants.State.IDLE, mCTUT.getState());
+        assertFalse(mCTUT.mRingingCall.isRinging());
+        // mock a MT call
+        mMmTelListener.onIncomingCall(mock(IImsCallSession.class), Bundle.EMPTY);
+        verify(mImsPhone, times(1)).notifyNewRingingConnection((Connection) any());
+        verify(mImsPhone, times(1)).notifyIncomingRing();
+        assertEquals(PhoneConstants.State.RINGING, mCTUT.getState());
+        assertTrue(mCTUT.mRingingCall.isRinging());
+        assertEquals(1, mCTUT.mRingingCall.getConnections().size());
+        ImsPhoneConnection connection =
+                (ImsPhoneConnection) mCTUT.mRingingCall.getConnections().get(0);
+        connection.addListener(mImsPhoneConnectionListener);
+
+        // Move the connection to the handover state.
+        mCTUT.notifySrvccState(Call.SrvccState.COMPLETED);
+        assertEquals(1, mCTUT.mHandoverCall.getConnections().size());
+
+        // Make sure the tracker states it's idle.
+        assertEquals(PhoneConstants.State.IDLE, mCTUT.getState());
+    }
+
+    private void assertVtDataUsageUpdated(int expectedToken, long rxBytes, long txBytes)
+            throws RemoteException {
+        final ArgumentCaptor<NetworkStats> ifaceStatsCaptor = ArgumentCaptor.forClass(
+                NetworkStats.class);
+        final ArgumentCaptor<NetworkStats> uidStatsCaptor = ArgumentCaptor.forClass(
+                NetworkStats.class);
+
+        verify(mVtDataUsageProviderCb).notifyStatsUpdated(eq(expectedToken),
+                ifaceStatsCaptor.capture(), uidStatsCaptor.capture());
+
+        // Default dialer's package uid is not set during test, thus the uid stats looks the same
+        // as iface stats and the records are always merged into the same entry.
+        // TODO: Mock different dialer's uid and verify uid stats has corresponding uid in the
+        //  records.
+        NetworkStats expectedStats = new NetworkStats(0L, 0);
+
+        if (rxBytes != 0 || txBytes != 0) {
+            expectedStats = expectedStats.addEntry(
+                    new Entry(NetworkStats.IFACE_VT, UID_ALL, SET_FOREGROUND,
+                            TAG_NONE, METERED_YES, ROAMING_NO, DEFAULT_NETWORK_YES, rxBytes, 0L,
+                            txBytes, 0L, 0L));
+        }
+        assertNetworkStatsEquals(expectedStats, ifaceStatsCaptor.getValue());
+        assertNetworkStatsEquals(expectedStats, uidStatsCaptor.getValue());
+    }
+
+    private ImsPhoneConnection placeCallAndMakeActive() {
         try {
             doAnswer(new Answer<ImsCall>() {
                 @Override
@@ -992,6 +1236,7 @@
                 new ImsStreamMediaProfile());
         imsCall.getImsCallSessionListenerProxy().callSessionStarted(imsCall.getSession(),
                 new ImsCallProfile());
+        return connection;
     }
 }
 
diff --git a/tests/telephonytests/src/com/android/internal/telephony/imsphone/ImsPhoneConnectionTest.java b/tests/telephonytests/src/com/android/internal/telephony/imsphone/ImsPhoneConnectionTest.java
index b7a4ff0..205b92d 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/imsphone/ImsPhoneConnectionTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/imsphone/ImsPhoneConnectionTest.java
@@ -15,35 +15,8 @@
  */
 package com.android.internal.telephony.imsphone;
 
-import android.os.AsyncResult;
-import android.os.Bundle;
-import android.os.Handler;
-import android.os.Looper;
-import android.os.Message;
-import android.telephony.DisconnectCause;
-import android.telephony.PhoneNumberUtils;
-import android.telephony.ServiceState;
-import android.telephony.ims.ImsCallProfile;
-import android.test.suitebuilder.annotation.MediumTest;
-import android.test.suitebuilder.annotation.SmallTest;
-
-import com.android.internal.telephony.Call;
-import com.android.internal.telephony.Connection;
-import com.android.internal.telephony.GsmCdmaCall;
-import com.android.internal.telephony.PhoneConstants;
-import com.android.internal.telephony.TelephonyTest;
-
-import org.junit.After;
-import org.junit.Assert;
-import org.junit.Before;
-import org.junit.Test;
-import org.mockito.Mock;
-import org.mockito.invocation.InvocationOnMock;
-import org.mockito.stubbing.Answer;
-
-import java.lang.reflect.Field;
-
 import static com.android.internal.telephony.TelephonyTestUtils.waitForMs;
+
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotEquals;
@@ -57,6 +30,40 @@
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 
+import android.os.AsyncResult;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.telephony.DisconnectCause;
+import android.telephony.PhoneNumberUtils;
+import android.telephony.ServiceState;
+import android.telephony.TelephonyManager;
+import android.telephony.ims.ImsCallProfile;
+import android.test.suitebuilder.annotation.MediumTest;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+
+import com.android.internal.telephony.Call;
+import com.android.internal.telephony.Connection;
+import com.android.internal.telephony.GsmCdmaCall;
+import com.android.internal.telephony.PhoneConstants;
+import com.android.internal.telephony.TelephonyTest;
+
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
+
+import java.lang.reflect.Field;
+
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper
 public class ImsPhoneConnectionTest extends TelephonyTest {
     private ImsPhoneConnection mConnectionUT;
     private Bundle mBundle = new Bundle();
@@ -70,7 +77,7 @@
     @Before
     public void setUp() throws Exception {
         super.setUp(getClass().getSimpleName());
-        replaceInstance(Handler.class, "mLooper", mImsCT, Looper.getMainLooper());
+        replaceInstance(Handler.class, "mLooper", mImsCT, Looper.myLooper());
         replaceInstance(ImsPhoneCallTracker.class, "mForegroundCall", mImsCT, mForeGroundCall);
         replaceInstance(ImsPhoneCallTracker.class, "mBackgroundCall", mImsCT, mBackGroundCall);
         replaceInstance(ImsPhoneCallTracker.class, "mRingingCall", mImsCT, mRingGroundCall);
@@ -87,7 +94,7 @@
 
     @Test
     @SmallTest
-    public void testImsConnectionSanity() {
+    public void testImsIncomingConnectionCorrectness() {
         logd("Testing initial state of MT ImsPhoneConnection");
         mConnectionUT = new ImsPhoneConnection(mImsPhone, mImsCall, mImsCT, mForeGroundCall, false);
 
@@ -100,6 +107,8 @@
         assertNull(mConnectionUT.getOrigDialString());
         assertFalse(mConnectionUT.isMultiparty());
         assertFalse(mConnectionUT.isConferenceHost());
+        assertEquals(android.telecom.Connection.VERIFICATION_STATUS_PASSED,
+                mConnectionUT.getNumberVerificationStatus());
         verify(mForeGroundCall, times(1)).attach((Connection) any(),
                 eq(ImsPhoneCall.State.INCOMING));
 
@@ -178,6 +187,7 @@
     public void testConnectionDisconnect() {
         //Mock we have an active connection
         testImsUpdateStateForeGround();
+        // tested using System.currentTimeMillis()
         waitForMs(50);
         mConnectionUT.onDisconnect(DisconnectCause.LOCAL);
         assertEquals(DisconnectCause.LOCAL, mConnectionUT.getDisconnectCause());
@@ -205,7 +215,7 @@
         assertTrue(mConnectionUT.update(mImsCall, Call.State.ACTIVE));
         assertEquals(Connection.PostDialState.WAIT, mConnectionUT.getPostDialState());
         mConnectionUT.proceedAfterWaitChar();
-        waitForMs(50);
+        processAllMessages();
         assertEquals(Connection.PostDialState.COMPLETE, mConnectionUT.getPostDialState());
     }
 
@@ -231,22 +241,36 @@
         try {
             Field field = ImsPhoneConnection.class.getDeclaredField("PAUSE_DELAY_MILLIS");
             field.setAccessible(true);
-            waitForMs((Integer) field.get(null) + 50);
+            moveTimeForward((Integer) field.get(null));
         } catch (Exception ex) {
             Assert.fail("unexpected exception thrown" + ex.getMessage());
         }
+        processAllMessages();
         assertEquals(Connection.PostDialState.COMPLETE, mConnectionUT.getPostDialState());
     }
 
     @Test
     @SmallTest
+    public void testSetWifiDeprecated() {
+        mConnectionUT = new ImsPhoneConnection(mImsPhone, mImsCall, mImsCT, mForeGroundCall, false);
+        assertFalse(mConnectionUT.isWifi());
+        // ImsCall.getRadioTechnology is tested elsewhere
+        doReturn(TelephonyManager.NETWORK_TYPE_IWLAN).when(mImsCall).getNetworkType();
+        mBundle.putString(ImsCallProfile.EXTRA_CALL_RAT_TYPE,
+                ServiceState.RIL_RADIO_TECHNOLOGY_IWLAN + "");
+        assertTrue(mConnectionUT.update(mImsCall, Call.State.ACTIVE));
+        assertTrue(mConnectionUT.isWifi());
+    }
+
+    @Test
+    @SmallTest
     public void testSetWifi() {
         mConnectionUT = new ImsPhoneConnection(mImsPhone, mImsCall, mImsCT, mForeGroundCall, false);
         assertFalse(mConnectionUT.isWifi());
         // ImsCall.getRadioTechnology is tested elsewhere
-        doReturn(ServiceState.RIL_RADIO_TECHNOLOGY_IWLAN).when(mImsCall).getRadioTechnology();
-        mBundle.putString(ImsCallProfile.EXTRA_CALL_RAT_TYPE,
-                ServiceState.RIL_RADIO_TECHNOLOGY_IWLAN + "");
+        doReturn(TelephonyManager.NETWORK_TYPE_IWLAN).when(mImsCall).getNetworkType();
+        mBundle.putString(ImsCallProfile.EXTRA_CALL_NETWORK_TYPE,
+                TelephonyManager.NETWORK_TYPE_IWLAN + "");
         assertTrue(mConnectionUT.update(mImsCall, Call.State.ACTIVE));
         assertTrue(mConnectionUT.isWifi());
     }
@@ -257,7 +281,7 @@
         mConnectionUT = new ImsPhoneConnection(mImsPhone, mImsCall, mImsCT, mForeGroundCall, false);
         assertFalse(mConnectionUT.isWifi());
         // ImsCall.getRadioTechnology is tested elsewhere
-        doReturn(ServiceState.RIL_RADIO_TECHNOLOGY_IWLAN).when(mImsCall).getRadioTechnology();
+        doReturn(TelephonyManager.NETWORK_TYPE_IWLAN).when(mImsCall).getNetworkType();
         // Tests to make sure that the EXTRA_CALL_RAT_TYPE_ALT string is set correctly for newer
         // devices.
         mBundle.putString(ImsCallProfile.EXTRA_CALL_RAT_TYPE_ALT,
@@ -268,13 +292,26 @@
 
     @Test
     @SmallTest
+    public void testSetLTEDeprecated() {
+        mConnectionUT = new ImsPhoneConnection(mImsPhone, mImsCall, mImsCT, mForeGroundCall, false);
+        assertNotEquals(mConnectionUT.getCallRadioTech(), ServiceState.RIL_RADIO_TECHNOLOGY_LTE);
+        // ImsCall.getRadioTechnology is tested elsewhere
+        doReturn(TelephonyManager.NETWORK_TYPE_LTE).when(mImsCall).getNetworkType();
+        mBundle.putString(ImsCallProfile.EXTRA_CALL_RAT_TYPE,
+                ServiceState.RIL_RADIO_TECHNOLOGY_LTE + "");
+        assertTrue(mConnectionUT.update(mImsCall, Call.State.ACTIVE));
+        assertEquals(mConnectionUT.getCallRadioTech(), ServiceState.RIL_RADIO_TECHNOLOGY_LTE);
+    }
+
+    @Test
+    @SmallTest
     public void testSetLTE() {
         mConnectionUT = new ImsPhoneConnection(mImsPhone, mImsCall, mImsCT, mForeGroundCall, false);
         assertNotEquals(mConnectionUT.getCallRadioTech(), ServiceState.RIL_RADIO_TECHNOLOGY_LTE);
         // ImsCall.getRadioTechnology is tested elsewhere
-        doReturn(ServiceState.RIL_RADIO_TECHNOLOGY_LTE).when(mImsCall).getRadioTechnology();
-        mBundle.putString(ImsCallProfile.EXTRA_CALL_RAT_TYPE,
-                ServiceState.RIL_RADIO_TECHNOLOGY_LTE + "");
+        doReturn(TelephonyManager.NETWORK_TYPE_LTE).when(mImsCall).getNetworkType();
+        mBundle.putString(ImsCallProfile.EXTRA_CALL_NETWORK_TYPE,
+                TelephonyManager.NETWORK_TYPE_LTE + "");
         assertTrue(mConnectionUT.update(mImsCall, Call.State.ACTIVE));
         assertEquals(mConnectionUT.getCallRadioTech(), ServiceState.RIL_RADIO_TECHNOLOGY_LTE);
     }
@@ -285,7 +322,7 @@
         mConnectionUT = new ImsPhoneConnection(mImsPhone, mImsCall, mImsCT, mForeGroundCall, false);
         assertNotEquals(mConnectionUT.getCallRadioTech(), ServiceState.RIL_RADIO_TECHNOLOGY_LTE);
         // ImsCall.getRadioTechnology is tested elsewhere
-        doReturn(ServiceState.RIL_RADIO_TECHNOLOGY_LTE).when(mImsCall).getRadioTechnology();
+        doReturn(TelephonyManager.NETWORK_TYPE_LTE).when(mImsCall).getNetworkType();
         // Tests to make sure that the EXTRA_CALL_RAT_TYPE_ALT string is set correctly for newer
         // devices.
         mBundle.putString(ImsCallProfile.EXTRA_CALL_RAT_TYPE_ALT,
@@ -338,4 +375,20 @@
         mConnectionUT.updateAddressDisplay(mImsCall);
         assertEquals(inputAddress, mConnectionUT.getAddress());
     }
+
+    @Test
+    @SmallTest
+    public void testConvertVerificationStatus() {
+        assertEquals(android.telecom.Connection.VERIFICATION_STATUS_FAILED,
+                ImsPhoneConnection.toTelecomVerificationStatus(
+                        ImsCallProfile.VERIFICATION_STATUS_FAILED));
+        assertEquals(android.telecom.Connection.VERIFICATION_STATUS_PASSED,
+                ImsPhoneConnection.toTelecomVerificationStatus(
+                        ImsCallProfile.VERIFICATION_STATUS_PASSED));
+        assertEquals(android.telecom.Connection.VERIFICATION_STATUS_NOT_VERIFIED,
+                ImsPhoneConnection.toTelecomVerificationStatus(
+                        ImsCallProfile.VERIFICATION_STATUS_NOT_VERIFIED));
+        assertEquals(android.telecom.Connection.VERIFICATION_STATUS_NOT_VERIFIED,
+                ImsPhoneConnection.toTelecomVerificationStatus(90210));
+    }
 }
diff --git a/tests/telephonytests/src/com/android/internal/telephony/imsphone/ImsPhoneFactoryTest.java b/tests/telephonytests/src/com/android/internal/telephony/imsphone/ImsPhoneFactoryTest.java
index c31541e..5514853 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/imsphone/ImsPhoneFactoryTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/imsphone/ImsPhoneFactoryTest.java
@@ -15,8 +15,16 @@
  */
 package com.android.internal.telephony.imsphone;
 
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
 import android.os.HandlerThread;
 import android.test.suitebuilder.annotation.SmallTest;
+
 import com.android.internal.telephony.PhoneNotifier;
 import com.android.internal.telephony.TelephonyTest;
 
@@ -24,11 +32,8 @@
 import org.junit.Before;
 import org.junit.Test;
 import org.mockito.Mock;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertEquals;
-import static org.mockito.Mockito.eq;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
+
+import java.util.concurrent.Executor;
 
 public class ImsPhoneFactoryTest extends TelephonyTest {
 
@@ -37,6 +42,13 @@
     private ImsPhone mImsPhoneUT;
     private ImsPhoneFactoryHandler mImsPhoneFactoryHandler;
 
+    private Executor mExecutor = new Executor() {
+        @Override
+        public void execute(Runnable r) {
+            r.run();
+        }
+    };
+
     private class ImsPhoneFactoryHandler extends HandlerThread {
 
         private ImsPhoneFactoryHandler(String name) {
@@ -52,6 +64,8 @@
     @Before
     public void setUp() throws Exception {
         super.setUp(this.getClass().getSimpleName());
+        doReturn(mExecutor).when(mContext).getMainExecutor();
+
         mImsPhoneFactoryHandler = new ImsPhoneFactoryHandler(this.getClass().getSimpleName());
         mImsPhoneFactoryHandler.start();
 
@@ -61,6 +75,7 @@
     @After
     public void tearDown() throws Exception {
         mImsPhoneFactoryHandler.quit();
+        mImsPhoneFactoryHandler.join();
         super.tearDown();
     }
 
diff --git a/tests/telephonytests/src/com/android/internal/telephony/imsphone/ImsPhoneTest.java b/tests/telephonytests/src/com/android/internal/telephony/imsphone/ImsPhoneTest.java
index 5bdc61c..0531d72 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/imsphone/ImsPhoneTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/imsphone/ImsPhoneTest.java
@@ -18,10 +18,11 @@
 
 import static com.android.internal.telephony.CommandsInterface.CF_ACTION_ENABLE;
 import static com.android.internal.telephony.CommandsInterface.CF_REASON_UNCONDITIONAL;
-import static com.android.internal.telephony.TelephonyTestUtils.waitForMs;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.Matchers.any;
 import static org.mockito.Matchers.anyChar;
@@ -29,6 +30,7 @@
 import static org.mockito.Matchers.anyLong;
 import static org.mockito.Matchers.nullable;
 import static org.mockito.Mockito.atLeast;
+import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.doNothing;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.doThrow;
@@ -39,27 +41,30 @@
 import static org.mockito.Mockito.verify;
 
 import android.app.Activity;
-import android.app.IApplicationThread;
 import android.content.BroadcastReceiver;
-import android.content.IIntentReceiver;
 import android.content.Intent;
+import android.content.res.Resources;
 import android.os.AsyncResult;
 import android.os.Bundle;
 import android.os.Handler;
-import android.os.HandlerThread;
 import android.os.Message;
 import android.os.PersistableBundle;
-import android.os.SystemProperties;
+import android.sysprop.TelephonyProperties;
 import android.telephony.CarrierConfigManager;
 import android.telephony.ServiceState;
+import android.telephony.TelephonyManager;
 import android.telephony.ims.ImsCallProfile;
 import android.telephony.ims.ImsReasonInfo;
+import android.telephony.ims.RegistrationManager;
+import android.telephony.ims.stub.ImsRegistrationImplBase;
+import android.telephony.ims.stub.ImsUtImplBase;
 import android.test.suitebuilder.annotation.SmallTest;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
 
 import androidx.test.filters.FlakyTest;
 
 import com.android.ims.ImsEcbmStateListener;
-import com.android.ims.ImsManager;
 import com.android.ims.ImsUtInterface;
 import com.android.internal.telephony.Call;
 import com.android.internal.telephony.CallStateException;
@@ -68,7 +73,6 @@
 import com.android.internal.telephony.Phone;
 import com.android.internal.telephony.PhoneConstants;
 import com.android.internal.telephony.TelephonyIntents;
-import com.android.internal.telephony.TelephonyProperties;
 import com.android.internal.telephony.TelephonyTest;
 import com.android.internal.telephony.gsm.SuppServiceNotification;
 
@@ -76,11 +80,18 @@
 import org.junit.Before;
 import org.junit.Ignore;
 import org.junit.Test;
+import org.junit.runner.RunWith;
 import org.mockito.ArgumentCaptor;
 import org.mockito.Mock;
 
 import java.util.List;
+import java.util.concurrent.Executor;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.TimeUnit;
+import java.util.function.Consumer;
 
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper
 public class ImsPhoneTest extends TelephonyTest {
     @Mock
     private ImsPhoneCall mForegroundCall;
@@ -95,26 +106,22 @@
     @Mock
     ImsUtInterface mImsUtInterface;
 
+    private Executor mExecutor = new Executor() {
+        @Override
+        public void execute(Runnable r) {
+            r.run();
+        }
+    };
+
     private ImsPhone mImsPhoneUT;
-    private ImsPhoneTestHandler mImsPhoneTestHandler;
+    private PersistableBundle mBundle;
     private boolean mDoesRilSendMultipleCallRing;
     private static final int EVENT_SUPP_SERVICE_NOTIFICATION = 1;
     private static final int EVENT_SUPP_SERVICE_FAILED = 2;
     private static final int EVENT_INCOMING_RING = 3;
     private static final int EVENT_EMERGENCY_CALLBACK_MODE_EXIT = 4;
 
-    private class ImsPhoneTestHandler extends HandlerThread {
-
-        private ImsPhoneTestHandler(String name) {
-            super(name);
-        }
-
-        @Override
-        public void onLooperPrepared() {
-            mImsPhoneUT = new ImsPhone(mContext, mNotifier, mPhone, true);
-            setReady(true);
-        }
-    }
+    private boolean mIsPhoneUtInEcm = false;
 
     @Before
     public void setUp() throws Exception {
@@ -126,15 +133,14 @@
         doReturn(Call.State.IDLE).when(mForegroundCall).getState();
         doReturn(Call.State.IDLE).when(mBackgroundCall).getState();
         doReturn(Call.State.IDLE).when(mRingingCall).getState();
+        doReturn(mExecutor).when(mContext).getMainExecutor();
 
-        mContextFixture.putBooleanResource(com.android.internal.R.bool.config_voice_capable, true);
+        doReturn(true).when(mTelephonyManager).isVoiceCapable();
 
-        mImsPhoneTestHandler = new ImsPhoneTestHandler(TAG);
-        mImsPhoneTestHandler.start();
-        waitUntilReady();
+        mImsPhoneUT = new ImsPhone(mContext, mNotifier, mPhone, true);
 
-        mDoesRilSendMultipleCallRing = SystemProperties.getBoolean(
-                TelephonyProperties.PROPERTY_RIL_SENDS_MULTIPLE_CALL_RING, true);
+        mDoesRilSendMultipleCallRing = TelephonyProperties.ril_sends_multiple_call_ring()
+                .orElse(true);
         replaceInstance(Handler.class, "mLooper", mTestHandler, mImsPhoneUT.getLooper());
         replaceInstance(Phone.class, "mLooper", mPhone, mImsPhoneUT.getLooper());
         mImsPhoneUT.registerForSuppServiceNotification(mTestHandler,
@@ -143,14 +149,24 @@
                 EVENT_SUPP_SERVICE_FAILED, null);
         mImsPhoneUT.registerForIncomingRing(mTestHandler,
                 EVENT_INCOMING_RING, null);
+        mImsPhoneUT.setVoiceCallSessionStats(mVoiceCallSessionStats);
         doReturn(mImsUtInterface).when(mImsCT).getUtInterface();
+        // When the mock GsmCdmaPhone gets setIsInEcbm called, ensure isInEcm matches.
+        doAnswer(invocation -> {
+            mIsPhoneUtInEcm = (Boolean) invocation.getArguments()[0];
+            return null;
+        }).when(mPhone).setIsInEcm(anyBoolean());
+        doAnswer(invocation -> mIsPhoneUtInEcm).when(mPhone).isInEcm();
+
+        mBundle = mContextFixture.getCarrierConfigBundle();
+        mBundle.putBoolean(CarrierConfigManager.KEY_CARRIER_CONFIG_APPLIED_BOOL, true);
+        processAllMessages();
     }
 
 
     @After
     public void tearDown() throws Exception {
         mImsPhoneUT = null;
-        mImsPhoneTestHandler.quit();
         super.tearDown();
     }
 
@@ -216,11 +232,25 @@
         assertEquals(Phone.SuppService.SEPARATE,
                 ((AsyncResult) messageArgumentCaptor.getValue().obj).result);
 
-        // ringing call is idle
+        // ringing call is idle, only an active call present
+        doReturn(Call.State.ACTIVE).when(mForegroundCall).getState();
         assertEquals(true, mImsPhoneUT.handleInCallMmiCommands("2"));
         verify(mImsCT).holdActiveCall();
 
+        // background call is holding
+        doReturn(Call.State.HOLDING).when(mBackgroundCall).getState();
+        doReturn(Call.State.IDLE).when(mForegroundCall).getState();
+        assertEquals(true, mImsPhoneUT.handleInCallMmiCommands("2"));
+        verify(mImsCT).unholdHeldCall();
+
+        // background call is holding and there's an active foreground call
+        doReturn(Call.State.ACTIVE).when(mForegroundCall).getState();
+        assertEquals(true, mImsPhoneUT.handleInCallMmiCommands("2"));
+        verify(mImsCT, times(2)).holdActiveCall();
+
         // ringing call is not idle
+        doReturn(Call.State.IDLE).when(mForegroundCall).getState();
+        doReturn(Call.State.IDLE).when(mBackgroundCall).getState();
         doReturn(Call.State.INCOMING).when(mRingingCall).getState();
         assertEquals(true, mImsPhoneUT.handleInCallMmiCommands("2"));
         verify(mImsCT).acceptCall(ImsCallProfile.CALL_TYPE_VOICE);
@@ -241,7 +271,7 @@
 
     @Test
     @SmallTest
-    public void testHandleInCallMmiCommandCallEct() {
+    public void testHandleInCallMmiCommandCallEct() throws Exception {
         doReturn(Call.State.ACTIVE).when(mForegroundCall).getState();
 
         // dial string length > 1
@@ -249,11 +279,7 @@
 
         // dial string length == 1
         assertEquals(true, mImsPhoneUT.handleInCallMmiCommands("4"));
-        ArgumentCaptor<Message> messageArgumentCaptor = ArgumentCaptor.forClass(Message.class);
-        verify(mTestHandler).sendMessageAtTime(messageArgumentCaptor.capture(), anyLong());
-        assertEquals(EVENT_SUPP_SERVICE_FAILED, messageArgumentCaptor.getValue().what);
-        assertEquals(Phone.SuppService.TRANSFER,
-                ((AsyncResult) messageArgumentCaptor.getValue().obj).result);
+        verify(mImsCT).explicitCallTransfer();
     }
 
     @Test
@@ -433,7 +459,7 @@
     public void testIncomingRing() {
         doReturn(PhoneConstants.State.IDLE).when(mImsCT).getState();
         mImsPhoneUT.notifyIncomingRing();
-        waitForMs(100);
+        processAllMessages();
         ArgumentCaptor<Message> messageArgumentCaptor = ArgumentCaptor.forClass(Message.class);
         verify(mTestHandler, times(1)).sendMessageAtTime(messageArgumentCaptor.capture(),
                 anyLong());
@@ -517,102 +543,74 @@
                 CommandsInterface.SERVICE_CLASS_NONE);
 
         ArgumentCaptor<Message> messageArgumentCaptor = ArgumentCaptor.forClass(Message.class);
-        verify(mImsUtInterface).queryCallBarring(eq(ImsUtInterface.CB_BAOC),
+        verify(mImsUtInterface).queryCallBarring(eq(ImsUtImplBase.CALL_BARRING_ALL_OUTGOING),
                 messageArgumentCaptor.capture(), eq(CommandsInterface.SERVICE_CLASS_NONE));
         assertEquals(msg, messageArgumentCaptor.getValue().obj);
 
         mImsPhoneUT.setCallBarring(CommandsInterface.CB_FACILITY_BAOIC, true, "abc", msg,
                 CommandsInterface.SERVICE_CLASS_NONE);
-        verify(mImsUtInterface).updateCallBarring(eq(ImsUtInterface.CB_BOIC),
+        verify(mImsUtInterface).updateCallBarring(eq(ImsUtImplBase.CALL_BARRING_OUTGOING_INTL),
                 eq(CommandsInterface.CF_ACTION_ENABLE), messageArgumentCaptor.capture(),
-                (String[]) eq(null), eq(CommandsInterface.SERVICE_CLASS_NONE));
+                (String[]) eq(null), eq(CommandsInterface.SERVICE_CLASS_NONE), eq("abc"));
         assertEquals(msg, messageArgumentCaptor.getValue().obj);
 
         mImsPhoneUT.setCallBarring(CommandsInterface.CB_FACILITY_BAOICxH, false, "abc", msg,
                 CommandsInterface.SERVICE_CLASS_NONE);
-        verify(mImsUtInterface).updateCallBarring(eq(ImsUtInterface.CB_BOIC_EXHC),
+        verify(mImsUtInterface).updateCallBarring(
+                eq(ImsUtImplBase.CALL_BARRING_OUTGOING_INTL_EXCL_HOME),
                 eq(CommandsInterface.CF_ACTION_DISABLE), messageArgumentCaptor.capture(),
-                (String[])eq(null), eq(CommandsInterface.SERVICE_CLASS_NONE));
+                (String[]) eq(null), eq(CommandsInterface.SERVICE_CLASS_NONE), eq("abc"));
         assertEquals(msg, messageArgumentCaptor.getValue().obj);
     }
 
-    @FlakyTest
     @Test
-    @Ignore
     public void testEcbm() throws Exception {
-        ImsEcbmStateListener imsEcbmStateListener = mImsPhoneUT.getImsEcbmStateListener();
-
-        // verify handling of emergency callback mode
-        imsEcbmStateListener.onECBMEntered();
-
-        // verify ACTION_EMERGENCY_CALLBACK_MODE_CHANGED
-        ArgumentCaptor<Intent> intentArgumentCaptor = ArgumentCaptor.forClass(Intent.class);
-        verify(mIActivityManager, atLeast(1)).broadcastIntent(eq((IApplicationThread)null),
-                intentArgumentCaptor.capture(),
-                eq((String)null),
-                eq((IIntentReceiver)null),
-                eq(Activity.RESULT_OK),
-                eq((String)null),
-                eq((Bundle)null),
-                eq((String[])null),
-                anyInt(),
-                eq((Bundle)null),
-                eq(false),
-                eq(true),
-                anyInt());
-
-        Intent intent = intentArgumentCaptor.getValue();
-        assertEquals(TelephonyIntents.ACTION_EMERGENCY_CALLBACK_MODE_CHANGED, intent.getAction());
-        assertEquals(true, intent.getBooleanExtra(PhoneConstants.PHONE_IN_ECM_STATE, false));
-
-        // verify that wakeLock is acquired in ECM
-        assertEquals(true, mImsPhoneUT.getWakeLock().isHeld());
-
         mImsPhoneUT.setOnEcbModeExitResponse(mTestHandler, EVENT_EMERGENCY_CALLBACK_MODE_EXIT,
                 null);
 
-        // verify handling of emergency callback mode exit
+        ImsEcbmStateListener imsEcbmStateListener = mImsPhoneUT.getImsEcbmStateListener();
+        imsEcbmStateListener.onECBMEntered();
+        verify(mPhone).setIsInEcm(true);
+
+        verifyEcbmIntentWasSent(1 /*times*/, true /*inEcm*/);
+        // verify that wakeLock is acquired in ECM
+        assertTrue(mImsPhoneUT.getWakeLock().isHeld());
+
         imsEcbmStateListener.onECBMExited();
+        verify(mPhone).setIsInEcm(false);
 
-        // verify ACTION_EMERGENCY_CALLBACK_MODE_CHANGED
-        verify(mIActivityManager, atLeast(2)).broadcastIntent(eq((IApplicationThread)null),
-                intentArgumentCaptor.capture(),
-                eq((String)null),
-                eq((IIntentReceiver)null),
-                eq(Activity.RESULT_OK),
-                eq((String)null),
-                eq((Bundle)null),
-                eq((String[])null),
-                anyInt(),
-                eq((Bundle)null),
-                eq(false),
-                eq(true),
-                anyInt());
-
-        intent = intentArgumentCaptor.getValue();
-        assertEquals(TelephonyIntents.ACTION_EMERGENCY_CALLBACK_MODE_CHANGED, intent.getAction());
-        assertEquals(false, intent.getBooleanExtra(PhoneConstants.PHONE_IN_ECM_STATE, true));
+        verifyEcbmIntentWasSent(2/*times*/, false /*inEcm*/);
 
         ArgumentCaptor<Message> messageArgumentCaptor = ArgumentCaptor.forClass(Message.class);
-
         // verify EcmExitRespRegistrant is notified
         verify(mTestHandler).sendMessageAtTime(messageArgumentCaptor.capture(),
                 anyLong());
         assertEquals(EVENT_EMERGENCY_CALLBACK_MODE_EXIT, messageArgumentCaptor.getValue().what);
 
         // verify wakeLock released
-        assertEquals(false, mImsPhoneUT.getWakeLock().isHeld());
+        assertFalse(mImsPhoneUT.getWakeLock().isHeld());
     }
 
-    @FlakyTest
+    private void verifyEcbmIntentWasSent(int times, boolean isInEcm) throws Exception {
+        // verify ACTION_EMERGENCY_CALLBACK_MODE_CHANGED
+        ArgumentCaptor<Intent> intentArgumentCaptor = ArgumentCaptor.forClass(Intent.class);
+        verify(mContext, atLeast(times)).sendStickyBroadcastAsUser(intentArgumentCaptor.capture(),
+                any());
+
+        Intent intent = intentArgumentCaptor.getValue();
+        assertNotNull(intent);
+        assertEquals(TelephonyIntents.ACTION_EMERGENCY_CALLBACK_MODE_CHANGED, intent.getAction());
+        assertEquals(isInEcm, intent.getBooleanExtra(
+                TelephonyManager.EXTRA_PHONE_IN_ECM_STATE, false));
+    }
+
     @Test
     @SmallTest
-    @Ignore
     public void testProcessDisconnectReason() throws Exception {
         // set up CarrierConfig
-        PersistableBundle bundle = mContextFixture.getCarrierConfigBundle();
-        bundle.putStringArray(CarrierConfigManager.KEY_WFC_OPERATOR_ERROR_CODES_STRING_ARRAY,
+        mBundle.putStringArray(CarrierConfigManager.KEY_WFC_OPERATOR_ERROR_CODES_STRING_ARRAY,
                 new String[]{"REG09|0"});
+        doReturn(true).when(mImsManager).isWfcEnabledByUser();
 
         // set up overlays
         String title = "title";
@@ -629,24 +627,81 @@
         mImsPhoneUT.processDisconnectReason(
                 new ImsReasonInfo(ImsReasonInfo.CODE_REGISTRATION_ERROR, 0, "REG09"));
 
-        // TODO: Verify that WFC has been turned off (can't do it right now because
-        // setWfcSetting is static).
-        //verify(mImsManager).setWfcSetting(any(), eq(false));
-
         ArgumentCaptor<Intent> intent = ArgumentCaptor.forClass(Intent.class);
         verify(mContext).sendOrderedBroadcast(
                 intent.capture(), nullable(String.class), any(BroadcastReceiver.class),
                 nullable(Handler.class), eq(Activity.RESULT_OK), nullable(String.class),
                 nullable(Bundle.class));
-        assertEquals(ImsManager.ACTION_IMS_REGISTRATION_ERROR, intent.getValue().getAction());
-        assertEquals(title, intent.getValue().getStringExtra(Phone.EXTRA_KEY_ALERT_TITLE));
-        assertEquals(messageAlert, intent.getValue().getStringExtra(Phone.EXTRA_KEY_ALERT_MESSAGE));
+        assertEquals(android.telephony.ims.ImsManager.ACTION_WFC_IMS_REGISTRATION_ERROR,
+                intent.getValue().getAction());
+        assertEquals(title, intent.getValue().getStringExtra(
+                android.telephony.ims.ImsManager.EXTRA_WFC_REGISTRATION_FAILURE_TITLE));
+        assertEquals(messageAlert, intent.getValue().getStringExtra(
+                android.telephony.ims.ImsManager.EXTRA_WFC_REGISTRATION_FAILURE_MESSAGE));
         assertEquals(messageNotification,
                 intent.getValue().getStringExtra(Phone.EXTRA_KEY_NOTIFICATION_MESSAGE));
     }
 
     @Test
     @SmallTest
+    public void testImsRegistered() throws Exception {
+        mImsPhoneUT.setServiceState(ServiceState.STATE_IN_SERVICE);
+        mImsPhoneUT.setImsRegistrationState(RegistrationManager.REGISTRATION_STATE_REGISTERED);
+        assertTrue(mImsPhoneUT.isImsRegistered());
+
+        LinkedBlockingQueue<Integer> result = new LinkedBlockingQueue<>(1);
+        mImsPhoneUT.getImsRegistrationState(result::offer);
+        Integer regResult = result.poll(1000, TimeUnit.MILLISECONDS);
+        assertNotNull(regResult);
+        assertEquals(RegistrationManager.REGISTRATION_STATE_REGISTERED, regResult.intValue());
+    }
+
+    @Test
+    @SmallTest
+    public void testImsRegistering() throws Exception {
+        mImsPhoneUT.setServiceState(ServiceState.STATE_OUT_OF_SERVICE);
+        mImsPhoneUT.setImsRegistrationState(RegistrationManager.REGISTRATION_STATE_REGISTERING);
+        assertFalse(mImsPhoneUT.isImsRegistered());
+
+        LinkedBlockingQueue<Integer> result = new LinkedBlockingQueue<>(1);
+        mImsPhoneUT.getImsRegistrationState(result::offer);
+        Integer regResult = result.poll(1000, TimeUnit.MILLISECONDS);
+        assertNotNull(regResult);
+        assertEquals(RegistrationManager.REGISTRATION_STATE_REGISTERING, regResult.intValue());
+    }
+
+    @Test
+    @SmallTest
+    public void testImsDeregistered() throws Exception {
+        mImsPhoneUT.setServiceState(ServiceState.STATE_OUT_OF_SERVICE);
+        mImsPhoneUT.setImsRegistrationState(RegistrationManager.REGISTRATION_STATE_NOT_REGISTERED);
+        assertFalse(mImsPhoneUT.isImsRegistered());
+
+        LinkedBlockingQueue<Integer> result = new LinkedBlockingQueue<>(1);
+        mImsPhoneUT.getImsRegistrationState(result::offer);
+        Integer regResult = result.poll(1000, TimeUnit.MILLISECONDS);
+        assertNotNull(regResult);
+        assertEquals(RegistrationManager.REGISTRATION_STATE_NOT_REGISTERED, regResult.intValue());
+    }
+
+    @Test
+    @SmallTest
+    public void testGetImsRegistrationTech() throws Exception {
+        LinkedBlockingQueue<Integer> queue = new LinkedBlockingQueue<>(1);
+        Consumer<Integer> regTechCallback = queue::offer;
+        doAnswer(invocation -> {
+            Consumer<Integer> c = (Consumer<Integer>) invocation.getArguments()[0];
+            c.accept(ImsRegistrationImplBase.REGISTRATION_TECH_IWLAN);
+            return null;
+        }).when(mImsCT).getImsRegistrationTech(eq(regTechCallback));
+        mImsPhoneUT.getImsRegistrationTech(regTechCallback);
+        Integer regTechResult = queue.poll(1000, TimeUnit.MILLISECONDS);
+        assertNotNull(regTechResult);
+        assertEquals(ImsRegistrationImplBase.REGISTRATION_TECH_IWLAN, regTechResult.intValue());
+    }
+
+    @Test
+    @SmallTest
     public void testRoamingDuplicateMessages() throws Exception {
         doReturn(PhoneConstants.State.IDLE).when(mImsCT).getState();
 
@@ -772,6 +827,54 @@
         assertNotNull(mImsPhoneUT.getServiceStateTracker());
     }
 
+    @Test
+    @SmallTest
+    public void testSendUssdAllowUssdOverImsInOutOfService() throws Exception {
+        Resources resources = mContext.getResources();
+
+        doReturn(true).when(resources).getBoolean(
+                com.android.internal.R.bool.config_allow_ussd_over_ims);
+        doReturn(ServiceState.STATE_OUT_OF_SERVICE).when(mSST.mSS).getState();
+
+        mImsPhoneUT.dial("*135#", new ImsPhone.ImsDialArgs.Builder().build());
+        verify(mImsCT).sendUSSD(eq("*135#"), any());
+    }
+
+    @Test
+    @SmallTest
+    public void testSendUssdAllowUssdOverImsInService() throws Exception {
+        String errorCode = "";
+        Resources resources = mContext.getResources();
+
+        doReturn(true).when(resources).getBoolean(
+                com.android.internal.R.bool.config_allow_ussd_over_ims);
+        doReturn(ServiceState.STATE_IN_SERVICE).when(mSST.mSS).getState();
+
+        try {
+            mImsPhoneUT.dial("*135#", new ImsPhone.ImsDialArgs.Builder().build());
+        } catch (CallStateException e) {
+            errorCode = e.getMessage();
+        }
+        assertEquals(Phone.CS_FALLBACK, errorCode);
+    }
+
+    @Test
+    @SmallTest
+    public void testSendUssdNotAllowUssdOverIms() throws Exception {
+        String errorCode = "";
+        Resources resources = mContext.getResources();
+
+        doReturn(false).when(resources).getBoolean(
+                com.android.internal.R.bool.config_allow_ussd_over_ims);
+
+        try {
+            mImsPhoneUT.dial("*135#", new ImsPhone.ImsDialArgs.Builder().build());
+        } catch (CallStateException e) {
+            errorCode = e.getMessage();
+        }
+        assertEquals(Phone.CS_FALLBACK, errorCode);
+    }
+
     private ServiceState getServiceStateDataAndVoice(int rat, int regState, boolean isRoaming) {
         ServiceState ss = new ServiceState();
         ss.setStateOutOfService();
diff --git a/tests/telephonytests/src/com/android/internal/telephony/imsphone/ImsRegistrationCallbackHelperTest.java b/tests/telephonytests/src/com/android/internal/telephony/imsphone/ImsRegistrationCallbackHelperTest.java
new file mode 100644
index 0000000..f2f69db
--- /dev/null
+++ b/tests/telephonytests/src/com/android/internal/telephony/imsphone/ImsRegistrationCallbackHelperTest.java
@@ -0,0 +1,177 @@
+/*
+ * Copyright (C) 2020 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.imsphone;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Matchers.anyInt;
+import static org.mockito.Mockito.verify;
+
+import android.net.Uri;
+import android.telephony.AccessNetworkConstants.AccessNetworkType;
+import android.telephony.ims.ImsReasonInfo;
+import android.telephony.ims.RegistrationManager;
+import android.telephony.ims.RegistrationManager.RegistrationCallback;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import com.android.internal.telephony.TelephonyTest;
+import com.android.internal.telephony.imsphone.ImsRegistrationCallbackHelper.ImsRegistrationUpdate;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+
+public class ImsRegistrationCallbackHelperTest extends TelephonyTest {
+
+    @Mock
+    private ImsRegistrationUpdate mMockRegistrationUpdate;
+    private ImsRegistrationCallbackHelper mRegistrationCallbackHelper;
+
+    @Before
+    public void setUp() throws Exception {
+        super.setUp(getClass().getSimpleName());
+
+        mRegistrationCallbackHelper = new ImsRegistrationCallbackHelper(mMockRegistrationUpdate,
+                Runnable::run);
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        super.tearDown();
+        mRegistrationCallbackHelper = null;
+    }
+
+    @Test
+    @SmallTest
+    public void testRegistrationStateReset() {
+        // When reset is called
+        mRegistrationCallbackHelper.reset();
+
+        // The registration state should be equal to REGISTRATION_STATE_NOT_REGISTERED
+        assertEquals(RegistrationManager.REGISTRATION_STATE_NOT_REGISTERED,
+                mRegistrationCallbackHelper.getImsRegistrationState());
+    }
+
+    @Test
+    @SmallTest
+    public void testRegistrationStateUpdate() {
+        // Verify Registration state can be update to NOT registered correctly.
+        int state = RegistrationManager.REGISTRATION_STATE_NOT_REGISTERED;
+        mRegistrationCallbackHelper.updateRegistrationState(state);
+
+        assertEquals(RegistrationManager.REGISTRATION_STATE_NOT_REGISTERED,
+                mRegistrationCallbackHelper.getImsRegistrationState());
+
+        // Verify Registration state can be update to registering correctly.
+        state = RegistrationManager.REGISTRATION_STATE_REGISTERING;
+        mRegistrationCallbackHelper.updateRegistrationState(state);
+
+        assertEquals(RegistrationManager.REGISTRATION_STATE_REGISTERING,
+                mRegistrationCallbackHelper.getImsRegistrationState());
+
+        // Verify Registration state can be update to registered correctly.
+        state = RegistrationManager.REGISTRATION_STATE_REGISTERED;
+        mRegistrationCallbackHelper.updateRegistrationState(state);
+
+        assertEquals(RegistrationManager.REGISTRATION_STATE_REGISTERED,
+                mRegistrationCallbackHelper.getImsRegistrationState());
+    }
+
+    @Test
+    @SmallTest
+    public void testIsImsRegistered() {
+        // When the registration state is not registered
+        mRegistrationCallbackHelper.updateRegistrationState(
+                RegistrationManager.REGISTRATION_STATE_NOT_REGISTERED);
+
+        // The result of isImsRegistered should be false
+        assertFalse(mRegistrationCallbackHelper.isImsRegistered());
+
+        // When the registration state is not registered
+        mRegistrationCallbackHelper.updateRegistrationState(
+                RegistrationManager.REGISTRATION_STATE_REGISTERED);
+
+        // The result of isImsRegistered should be true
+        assertTrue(mRegistrationCallbackHelper.isImsRegistered());
+    }
+
+    @Test
+    @SmallTest
+    public void testImsOnRegistered() {
+        // Verify the RegistrationCallback should not be null
+        RegistrationCallback callback = mRegistrationCallbackHelper.getCallback();
+        assertNotNull(callback);
+
+        // When onRegistered is called, the registration state should be
+        // REGISTRATION_STATE_REGISTERED
+        callback.onRegistered(AccessNetworkType.IWLAN);
+
+        assertEquals(RegistrationManager.REGISTRATION_STATE_REGISTERED,
+                mRegistrationCallbackHelper.getImsRegistrationState());
+        verify(mMockRegistrationUpdate).handleImsRegistered(anyInt());
+    }
+
+    @Test
+    @SmallTest
+    public void testImsOnRegistering() {
+        // Verify the RegistrationCallback should not be null
+        RegistrationCallback callback = mRegistrationCallbackHelper.getCallback();
+        assertNotNull(callback);
+
+        // When onRegistering is called, the registration state should be
+        // REGISTRATION_STATE_REGISTERING
+        callback.onRegistering(AccessNetworkType.IWLAN);
+        // The registration state should be REGISTRATION_STATE_REGISTERING
+        assertEquals(RegistrationManager.REGISTRATION_STATE_REGISTERING,
+                mRegistrationCallbackHelper.getImsRegistrationState());
+        verify(mMockRegistrationUpdate).handleImsRegistering(anyInt());
+    }
+
+    @Test
+    @SmallTest
+    public void testImsUnRegistered() {
+        // Verify the RegistrationCallback should not be null
+        RegistrationCallback callback = mRegistrationCallbackHelper.getCallback();
+        assertNotNull(callback);
+
+        // When onUnregistered is called, the registration state should be
+        // REGISTRATION_STATE_NOT_REGISTERED
+        ImsReasonInfo reasonInfo = new ImsReasonInfo(ImsReasonInfo.CODE_REGISTRATION_ERROR, 0);
+        callback.onUnregistered(reasonInfo);
+        // The registration state should be REGISTRATION_STATE_NOT_REGISTERED
+        assertEquals(RegistrationManager.REGISTRATION_STATE_NOT_REGISTERED,
+                mRegistrationCallbackHelper.getImsRegistrationState());
+        verify(mMockRegistrationUpdate).handleImsUnregistered(reasonInfo);
+    }
+
+    @Test
+    @SmallTest
+    public void testSubscriberAssociatedUriChanged() {
+        // Verify the RegistrationCallback should not be null
+        RegistrationCallback callback = mRegistrationCallbackHelper.getCallback();
+        assertNotNull(callback);
+
+        // When onSubscriberAssociatedUriChanged is called
+        Uri[] uris = new Uri[0];
+        callback.onSubscriberAssociatedUriChanged(uris);
+        // The handleImsSubscriberAssociatedUriChanged should be called
+        verify(mMockRegistrationUpdate).handleImsSubscriberAssociatedUriChanged(uris);
+    }
+}
diff --git a/tests/telephonytests/src/com/android/internal/telephony/imsphone/ImsRttTextHandlerTest.java b/tests/telephonytests/src/com/android/internal/telephony/imsphone/ImsRttTextHandlerTest.java
index 82a3c0c..00230c5 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/imsphone/ImsRttTextHandlerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/imsphone/ImsRttTextHandlerTest.java
@@ -16,6 +16,8 @@
 
 package com.android.internal.telephony.imsphone;
 
+import static com.android.internal.telephony.TelephonyTestUtils.waitForMs;
+
 import android.os.HandlerThread;
 import android.os.ParcelFileDescriptor;
 import android.telecom.Connection;
@@ -114,8 +116,8 @@
         // make sure at it hasn't been sent.
         Assert.assertEquals("", mNetworkWriter.getContents());
         // Wait for 300ms
-        waitForHandlerActionDelayed(mRttTextHandler, TEST_TIMEOUT,
-                ImsRttTextHandler.MAX_BUFFERING_DELAY_MILLIS + 100);
+        waitForMs(ImsRttTextHandler.MAX_BUFFERING_DELAY_MILLIS + 100);
+        waitForHandlerAction(mRttTextHandler, TEST_TIMEOUT);
         // make sure that it has been sent and check that it's correct
         Assert.assertEquals("abcd", mNetworkWriter.getContents());
     }
@@ -141,7 +143,7 @@
         Assert.assertEquals("", mNetworkWriter.getContents());
 
         // Send the second part
-        Thread.sleep(10);
+        waitForMs(10);
         // Register a read notifier
         readNotifier = new CountDownLatch(1);
         mRttTextHandler.setReadNotifier(readNotifier);
@@ -176,7 +178,7 @@
         for (char c : characters) {
             mPipeToHandler.write(String.valueOf(c));
             mPipeToHandler.flush();
-            Thread.sleep(10);
+            waitForMs(10);
         }
         waitForHandlerAction(mRttTextHandler, TEST_TIMEOUT);
         waitForHandlerAction(mRttTextHandler, TEST_TIMEOUT);
@@ -194,19 +196,19 @@
             String toSend = new String(characters, i, Math.min(3, characters.length - i));
             mPipeToHandler.write(toSend);
             mPipeToHandler.flush();
-            Thread.sleep(10);
+            waitForMs(10);
         }
         waitForHandlerAction(mRttTextHandler, TEST_TIMEOUT);
         waitForHandlerAction(mRttTextHandler, TEST_TIMEOUT);
 
         // Wait one second and see how many characters are sent in that time.
         int numCharsSoFar = mNetworkWriter.getContents().length();
-        Thread.sleep(1000);
+        waitForMs(1000);
         int numCharsInOneSec = mNetworkWriter.getContents().length() - numCharsSoFar;
         Assert.assertTrue(numCharsInOneSec <= ImsRttTextHandler.MAX_CODEPOINTS_PER_SECOND);
 
         // Wait 5 seconds for all the chars to make it through
-        Thread.sleep(5000);
+        waitForMs(5000);
         Assert.assertEquals(LONG_TEXT, mNetworkWriter.getContents());
     }
 
@@ -228,6 +230,7 @@
         mRttTextHandler.tearDown();
         waitForHandlerAction(mRttTextHandler, TEST_TIMEOUT);
         mHandlerThread.quit();
+        mHandlerThread.join();
         super.tearDown();
     }
 
diff --git a/tests/telephonytests/src/com/android/internal/telephony/metrics/CallQualityMetricsTest.java b/tests/telephonytests/src/com/android/internal/telephony/metrics/CallQualityMetricsTest.java
new file mode 100644
index 0000000..e530425
--- /dev/null
+++ b/tests/telephonytests/src/com/android/internal/telephony/metrics/CallQualityMetricsTest.java
@@ -0,0 +1,342 @@
+/*
+ * Copyright (C) 2019 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.metrics;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.Mockito.when;
+
+import android.telephony.CallQuality;
+import android.telephony.CellSignalStrengthCdma;
+import android.telephony.CellSignalStrengthGsm;
+import android.telephony.CellSignalStrengthLte;
+import android.telephony.CellSignalStrengthNr;
+import android.telephony.CellSignalStrengthTdscdma;
+import android.telephony.CellSignalStrengthWcdma;
+import android.telephony.SignalStrength;
+
+import com.android.internal.telephony.TelephonyTest;
+import com.android.internal.telephony.nano.TelephonyProto.TelephonyCallSession.Event.CallQualitySummary;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+public class CallQualityMetricsTest extends TelephonyTest {
+
+    private CallQualityMetrics mCallQualityMetrics;
+
+    @Before
+    public void setUp() throws Exception {
+        super.setUp(getClass().getSimpleName());
+        mCallQualityMetrics = new CallQualityMetrics(mPhone);
+
+        // the ImsPhone does not return a ServiceStateTracker, so CallQualityMetrics gets the
+        // default phone from the ImsPhone and uses that to get the ServiceStateTracker, therefore
+        // we need to mock the default phone as well.
+        when(mPhone.getDefaultPhone()).thenReturn(mPhone);
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        super.tearDown();
+    }
+
+    private CallQuality constructCallQuality(int dlQuality, int ulQuality, int durationMs) {
+        return new CallQuality(
+                dlQuality,
+                ulQuality,
+                durationMs,
+                0, 0, 0, 0, 0, 0, 0, 0); // packets, jitter and codec (okay to ignore for testing)
+    }
+
+    /**
+     * Verify that good/bad quality and total duration stats are correct.
+     */
+    @Test
+    public void testTotalDurations() {
+        // Call quality in the following sequence:
+        //
+        // DL: GOOD       GOOD      BAD
+        // UL: GOOD       BAD       GOOD
+        // |----------|----------|--------|
+        // 0          5          10       14
+        //
+        // 0s = Start of call. Assumed to be good quality
+        // 5s = Switches to UL bad quality
+        // 10s = Switches to UL good quality, DL bad quality
+        // 14s = End of call. Switches to UL bad quality, DL good quality
+        CallQuality cq1 = constructCallQuality(CallQuality.CALL_QUALITY_EXCELLENT,
+                CallQuality.CALL_QUALITY_BAD, 5000);
+        CallQuality cq2 = constructCallQuality(CallQuality.CALL_QUALITY_BAD,
+                CallQuality.CALL_QUALITY_EXCELLENT, 10000);
+        CallQuality cq3 = constructCallQuality(CallQuality.CALL_QUALITY_EXCELLENT,
+                CallQuality.CALL_QUALITY_BAD, 14000);
+
+        mCallQualityMetrics.saveCallQuality(cq1);
+        mCallQualityMetrics.saveCallQuality(cq2);
+        mCallQualityMetrics.saveCallQuality(cq3);
+
+        // verify UL quality durations
+        CallQualitySummary dlSummary = mCallQualityMetrics.getCallQualitySummaryDl();
+        assertEquals(9, dlSummary.totalGoodQualityDurationInSeconds);
+        assertEquals(5, dlSummary.totalBadQualityDurationInSeconds);
+        assertEquals(14, dlSummary.totalDurationWithQualityInformationInSeconds);
+
+        // verify DL quality durations
+        CallQualitySummary ulSummary = mCallQualityMetrics.getCallQualitySummaryUl();
+        assertEquals(5, ulSummary.totalGoodQualityDurationInSeconds);
+        assertEquals(9, ulSummary.totalBadQualityDurationInSeconds);
+        assertEquals(14, ulSummary.totalDurationWithQualityInformationInSeconds);
+    }
+
+    /**
+     * Verify that good/bad quality and total duration stats are correct.
+     *
+     * Similar to testTotalDurations, but getCallQualitySummaryUl/Dl will be called multiple times,
+     * so verify that it continues to work after the first call.
+     */
+    @Test
+    public void testTotalDurations_MultipleChecks() {
+        // Call quality in the following sequence:
+        //
+        // DL: GOOD       GOOD      BAD
+        // UL: GOOD       BAD       GOOD
+        // |----------|----------|--------|
+        // 0          5          10       14
+        //
+        // 0s = Start of call. Assumed to be good quality
+        // 5s = Switches to UL bad quality
+        // 10s = Switches to UL good quality, DL bad quality
+        // 14s = End of call. Switches to UL bad quality, DL good quality
+        CallQuality cq1 = constructCallQuality(CallQuality.CALL_QUALITY_EXCELLENT,
+                CallQuality.CALL_QUALITY_BAD, 5000);
+        CallQuality cq2 = constructCallQuality(CallQuality.CALL_QUALITY_BAD,
+                CallQuality.CALL_QUALITY_EXCELLENT, 10000);
+        CallQuality cq3 = constructCallQuality(CallQuality.CALL_QUALITY_EXCELLENT,
+                CallQuality.CALL_QUALITY_BAD, 14000);
+
+        mCallQualityMetrics.saveCallQuality(cq1);
+        mCallQualityMetrics.saveCallQuality(cq2);
+        mCallQualityMetrics.saveCallQuality(cq3);
+
+        // verify UL quality durations
+        CallQualitySummary dlSummary = mCallQualityMetrics.getCallQualitySummaryDl();
+        assertEquals(9, dlSummary.totalGoodQualityDurationInSeconds);
+        assertEquals(5, dlSummary.totalBadQualityDurationInSeconds);
+        assertEquals(14, dlSummary.totalDurationWithQualityInformationInSeconds);
+
+        // verify DL quality durations
+        CallQualitySummary ulSummary = mCallQualityMetrics.getCallQualitySummaryUl();
+        assertEquals(5, ulSummary.totalGoodQualityDurationInSeconds);
+        assertEquals(9, ulSummary.totalBadQualityDurationInSeconds);
+        assertEquals(14, ulSummary.totalDurationWithQualityInformationInSeconds);
+
+        // verify UL quality durations
+        dlSummary = mCallQualityMetrics.getCallQualitySummaryDl();
+        assertEquals(9, dlSummary.totalGoodQualityDurationInSeconds);
+        assertEquals(5, dlSummary.totalBadQualityDurationInSeconds);
+        assertEquals(14, dlSummary.totalDurationWithQualityInformationInSeconds);
+
+        // verify DL quality durations
+        ulSummary = mCallQualityMetrics.getCallQualitySummaryUl();
+        assertEquals(5, ulSummary.totalGoodQualityDurationInSeconds);
+        assertEquals(9, ulSummary.totalBadQualityDurationInSeconds);
+        assertEquals(14, ulSummary.totalDurationWithQualityInformationInSeconds);
+    }
+
+
+    /**
+     * Verify that good/bad quality and total duration stats are correct.
+     *
+     * Similar to testTotalDurations but we report the call quality out of order.
+     */
+    @Test
+    public void testTotalDurations_ReportedOutOfOrder() {
+        // Call quality in the following sequence:
+        //
+        // DL: GOOD       GOOD      BAD
+        // UL: GOOD       BAD       GOOD
+        // |----------|----------|--------|
+        // 0          5          10       14
+        //
+        // 0s = Start of call. Assumed to be good quality
+        // 5s = Switches to UL bad quality
+        // 10s = Switches to UL good quality, DL bad quality
+        // 14s = End of call. Switches to UL bad quality, DL good quality
+        CallQuality cq1 = constructCallQuality(CallQuality.CALL_QUALITY_EXCELLENT,
+                CallQuality.CALL_QUALITY_BAD, 5000);
+        CallQuality cq2 = constructCallQuality(CallQuality.CALL_QUALITY_BAD,
+                CallQuality.CALL_QUALITY_EXCELLENT, 10000);
+        CallQuality cq3 = constructCallQuality(CallQuality.CALL_QUALITY_EXCELLENT,
+                CallQuality.CALL_QUALITY_BAD, 14000);
+
+        mCallQualityMetrics.saveCallQuality(cq1);
+        mCallQualityMetrics.saveCallQuality(cq3);
+        mCallQualityMetrics.saveCallQuality(cq2);
+
+        // verify UL quality durations
+        CallQualitySummary dlSummary = mCallQualityMetrics.getCallQualitySummaryDl();
+        assertEquals(9, dlSummary.totalGoodQualityDurationInSeconds);
+        assertEquals(5, dlSummary.totalBadQualityDurationInSeconds);
+        assertEquals(14, dlSummary.totalDurationWithQualityInformationInSeconds);
+
+        // verify DL quality durations
+        CallQualitySummary ulSummary = mCallQualityMetrics.getCallQualitySummaryUl();
+        assertEquals(5, ulSummary.totalGoodQualityDurationInSeconds);
+        assertEquals(9, ulSummary.totalBadQualityDurationInSeconds);
+        assertEquals(14, ulSummary.totalDurationWithQualityInformationInSeconds);
+    }
+
+    /**
+     * Verify that a new CallQualityMetrics object is able to return empty summaries if no
+     * CallQuality is reported for the duration of a call.
+     */
+    @Test
+    public void testNoQualityReported() {
+        // getting the summary for a new CallQualityMetrics object should not fail, and all
+        // durations should be 0
+        CallQualitySummary dlSummary = mCallQualityMetrics.getCallQualitySummaryDl();
+        assertEquals(0, dlSummary.totalGoodQualityDurationInSeconds);
+        assertEquals(0, dlSummary.totalBadQualityDurationInSeconds);
+        assertEquals(0, dlSummary.totalDurationWithQualityInformationInSeconds);
+        CallQualitySummary ulSummary = mCallQualityMetrics.getCallQualitySummaryUl();
+        assertEquals(0, ulSummary.totalGoodQualityDurationInSeconds);
+        assertEquals(0, ulSummary.totalBadQualityDurationInSeconds);
+        assertEquals(0, ulSummary.totalDurationWithQualityInformationInSeconds);
+    }
+
+    /**
+     * Verify that if either UL or DL call quality level is not available, the CallQuality update is
+     * ignored.
+     */
+    @Test
+    public void testNotAvailableIsIgnored() {
+        // CallQuality updates from the IMS service with CALL_QUALITY_NOT_AVAILABLE should be
+        // ignored
+        CallQuality cq1 = constructCallQuality(CallQuality.CALL_QUALITY_NOT_AVAILABLE,
+                CallQuality.CALL_QUALITY_BAD, 5000);
+        CallQuality cq2 = constructCallQuality(CallQuality.CALL_QUALITY_BAD,
+                CallQuality.CALL_QUALITY_NOT_AVAILABLE, 10000);
+        mCallQualityMetrics.saveCallQuality(cq1);
+        mCallQualityMetrics.saveCallQuality(cq2);
+
+        CallQualitySummary dlSummary = mCallQualityMetrics.getCallQualitySummaryDl();
+        assertEquals(0, dlSummary.totalGoodQualityDurationInSeconds);
+        assertEquals(0, dlSummary.totalBadQualityDurationInSeconds);
+        assertEquals(0, dlSummary.totalDurationWithQualityInformationInSeconds);
+        CallQualitySummary ulSummary = mCallQualityMetrics.getCallQualitySummaryUl();
+        assertEquals(0, ulSummary.totalGoodQualityDurationInSeconds);
+        assertEquals(0, ulSummary.totalBadQualityDurationInSeconds);
+        assertEquals(0, ulSummary.totalDurationWithQualityInformationInSeconds);
+    }
+
+    /**
+     * Test that the best and worst SignalStrength (currently just LTE RSSNR) is correctly kept
+     * track of. CallQualityMetrics should log the best and worst SS for good and bad quality, but
+     * this just tests for good quality since the logic is the same.
+     */
+    @Test
+    public void testBestAndWorstSs() {
+        // save good quality with high rssnr
+        CallQuality cq1 = constructCallQuality(CallQuality.CALL_QUALITY_EXCELLENT,
+                CallQuality.CALL_QUALITY_EXCELLENT, 5000);
+        int rssnr1 = 30;
+        // ignore everything except rssnr
+        CellSignalStrengthLte lteSs1 = new CellSignalStrengthLte(0, 0, 0, rssnr1, 0, 0);
+        SignalStrength ss1 = new SignalStrength(
+                new CellSignalStrengthCdma(),
+                new CellSignalStrengthGsm(),
+                new CellSignalStrengthWcdma(),
+                new CellSignalStrengthTdscdma(),
+                lteSs1,
+                new CellSignalStrengthNr());
+        when(mSST.getSignalStrength()).thenReturn(ss1);
+        mCallQualityMetrics.saveCallQuality(cq1);
+
+        // save good quality with low rssnr
+        CallQuality cq2 = constructCallQuality(CallQuality.CALL_QUALITY_EXCELLENT,
+                CallQuality.CALL_QUALITY_EXCELLENT, 10000);
+        int rssnr2 = -20;
+        // ignore everything except rssnr
+        CellSignalStrengthLte lteSs2 = new CellSignalStrengthLte(0, 0, 0, rssnr2, 0, 0);
+        SignalStrength ss2 = new SignalStrength(
+                new CellSignalStrengthCdma(),
+                new CellSignalStrengthGsm(),
+                new CellSignalStrengthWcdma(),
+                new CellSignalStrengthTdscdma(),
+                lteSs2,
+                new CellSignalStrengthNr());
+        when(mSST.getSignalStrength()).thenReturn(ss2);
+        mCallQualityMetrics.saveCallQuality(cq1);
+
+        CallQualitySummary dlSummary = mCallQualityMetrics.getCallQualitySummaryDl();
+        assertEquals(rssnr1, dlSummary.bestSsWithGoodQuality.lteSnr);
+        assertEquals(rssnr2, dlSummary.worstSsWithGoodQuality.lteSnr);
+    }
+
+    /**
+     * Verifies that the snapshot of the end (the last reported call quality) is correct.
+     * Currently this just checks the duration since the logic is all the same and it doesn't seem
+     * likely that one field would be preserved and others would be lost.
+     */
+    @Test
+    public void testSnapshotOfEndDuration() {
+        CallQuality cq1 = constructCallQuality(CallQuality.CALL_QUALITY_EXCELLENT,
+                CallQuality.CALL_QUALITY_BAD, 5000);
+        CallQuality cq2 = constructCallQuality(CallQuality.CALL_QUALITY_BAD,
+                CallQuality.CALL_QUALITY_EXCELLENT, 10000);
+        CallQuality cq3 = constructCallQuality(CallQuality.CALL_QUALITY_EXCELLENT,
+                CallQuality.CALL_QUALITY_BAD, 14000);
+
+        mCallQualityMetrics.saveCallQuality(cq1);
+        mCallQualityMetrics.saveCallQuality(cq2);
+        mCallQualityMetrics.saveCallQuality(cq3);
+
+        // verify snapshot of end
+        CallQualitySummary dlSummary = mCallQualityMetrics.getCallQualitySummaryDl();
+        assertEquals(14, dlSummary.snapshotOfEnd.durationInSeconds);
+        CallQualitySummary ulSummary = mCallQualityMetrics.getCallQualitySummaryUl();
+        assertEquals(14, ulSummary.snapshotOfEnd.durationInSeconds);
+    }
+
+    /**
+     * Verifies that the snapshot of the end (the last reported call quality) is correct.
+     * Currently this just checks the duration since the logic is all the same and it doesn't seem
+     * likely that one field would be preserved and others would be lost.
+     *
+     * Similar to testSnapshotOfEndDuration but we report the call quality out of order
+     */
+    @Test
+    public void testSnapshotOfEndDuration_ReportedOutOfOrder() {
+        CallQuality cq1 = constructCallQuality(CallQuality.CALL_QUALITY_EXCELLENT,
+                CallQuality.CALL_QUALITY_BAD, 5000);
+        CallQuality cq2 = constructCallQuality(CallQuality.CALL_QUALITY_BAD,
+                CallQuality.CALL_QUALITY_EXCELLENT, 10000);
+        CallQuality cq3 = constructCallQuality(CallQuality.CALL_QUALITY_EXCELLENT,
+                CallQuality.CALL_QUALITY_BAD, 14000);
+
+        mCallQualityMetrics.saveCallQuality(cq1);
+        mCallQualityMetrics.saveCallQuality(cq3);
+        mCallQualityMetrics.saveCallQuality(cq2);
+
+        // verify snapshot of end
+        CallQualitySummary dlSummary = mCallQualityMetrics.getCallQualitySummaryDl();
+        assertEquals(14, dlSummary.snapshotOfEnd.durationInSeconds);
+        CallQualitySummary ulSummary = mCallQualityMetrics.getCallQualitySummaryUl();
+        assertEquals(14, ulSummary.snapshotOfEnd.durationInSeconds);
+    }
+}
diff --git a/tests/telephonytests/src/com/android/internal/telephony/metrics/MetricsCollectorTest.java b/tests/telephonytests/src/com/android/internal/telephony/metrics/MetricsCollectorTest.java
new file mode 100644
index 0000000..103cae1
--- /dev/null
+++ b/tests/telephonytests/src/com/android/internal/telephony/metrics/MetricsCollectorTest.java
@@ -0,0 +1,306 @@
+/*
+ * Copyright (C) 2020 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.metrics;
+
+import static com.android.internal.telephony.TelephonyStatsLog.SIM_SLOT_STATE;
+import static com.android.internal.telephony.TelephonyStatsLog.SUPPORTED_RADIO_ACCESS_FAMILY;
+import static com.android.internal.telephony.TelephonyStatsLog.VOICE_CALL_RAT_USAGE;
+import static com.android.internal.telephony.TelephonyStatsLog.VOICE_CALL_SESSION;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.anyLong;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+
+import android.app.StatsManager;
+import android.telephony.TelephonyManager;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.util.StatsEvent;
+
+import com.android.internal.telephony.Phone;
+import com.android.internal.telephony.PhoneFactory;
+import com.android.internal.telephony.TelephonyTest;
+import com.android.internal.telephony.nano.PersistAtomsProto.RawVoiceCallRatUsage;
+import com.android.internal.telephony.nano.PersistAtomsProto.VoiceCallSession;
+import com.android.internal.telephony.uicc.IccCardStatus.CardState;
+import com.android.internal.telephony.uicc.UiccCard;
+import com.android.internal.telephony.uicc.UiccController;
+import com.android.internal.telephony.uicc.UiccSlot;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class MetricsCollectorTest extends TelephonyTest {
+    private static final StatsManager.PullAtomMetadata POLICY_PULL_DAILY =
+            new StatsManager.PullAtomMetadata.Builder()
+                    .setCoolDownMillis(24L * 3600L * 1000L)
+                    .build();
+    private static final long MIN_COOLDOWN_MILLIS = 23L * 3600L * 1000L;
+    private static final long MIN_CALLS_PER_BUCKET = 5L;
+
+    // NOTE: these fields are currently 32-bit internally and padded to 64-bit by TelephonyManager
+    private static final int SUPPORTED_RAF_1 =
+            (int) TelephonyManager.NETWORK_TYPE_BITMASK_GSM
+                    | (int) TelephonyManager.NETWORK_TYPE_BITMASK_LTE
+                    | (int) TelephonyManager.NETWORK_TYPE_BITMASK_NR;
+    private static final int SUPPORTED_RAF_2 =
+            (int) TelephonyManager.NETWORK_TYPE_BITMASK_GSM
+                    | (int) TelephonyManager.NETWORK_TYPE_BITMASK_1xRTT
+                    | (int) TelephonyManager.NETWORK_TYPE_BITMASK_UMTS;
+    private static final int SUPPORTED_RAF_BOTH = SUPPORTED_RAF_1 | SUPPORTED_RAF_2;
+
+    // TODO: if we want to check puller registration by mocking StatsManager, we will have to enable
+    // inline mocking since the StatsManager class is final
+
+    // b/153195691: we cannot verify the contents of StatsEvent as its getters are marked with @hide
+
+    @Mock private Phone mSecondPhone;
+    @Mock private UiccSlot mPhysicalSlot;
+    @Mock private UiccSlot mEsimSlot;
+    @Mock private UiccCard mActiveCard;
+
+    private MetricsCollector mMetricsCollector;
+
+    @Before
+    public void setUp() throws Exception {
+        super.setUp(getClass().getSimpleName());
+        mMetricsCollector = new MetricsCollector(mContext);
+        mMetricsCollector.setPersistAtomsStorage(mPersistAtomsStorage);
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        super.tearDown();
+    }
+
+    @Test
+    @SmallTest
+    public void onPullAtom_simSlotState_bothSimPresent() {
+        // these have been tested extensively in SimSlotStateTest, here we verify atom generation
+        doReturn(true).when(mPhysicalSlot).isActive();
+        doReturn(CardState.CARDSTATE_PRESENT).when(mPhysicalSlot).getCardState();
+        doReturn(false).when(mPhysicalSlot).isEuicc();
+        doReturn(true).when(mEsimSlot).isActive();
+        doReturn(CardState.CARDSTATE_PRESENT).when(mEsimSlot).getCardState();
+        doReturn(true).when(mEsimSlot).isEuicc();
+        doReturn(mActiveCard).when(mEsimSlot).getUiccCard();
+        doReturn(4).when(mActiveCard).getNumApplications();
+        doReturn(new UiccSlot[] {mPhysicalSlot, mEsimSlot}).when(mUiccController).getUiccSlots();
+        doReturn(mPhysicalSlot).when(mUiccController).getUiccSlot(eq(0));
+        doReturn(mEsimSlot).when(mUiccController).getUiccSlot(eq(1));
+        StatsEvent expectedAtom =
+                StatsEvent.newBuilder()
+                        .setAtomId(SIM_SLOT_STATE)
+                        .writeInt(2)
+                        .writeInt(2)
+                        .writeInt(1)
+                        .build();
+        List<StatsEvent> actualAtoms = new ArrayList<>();
+
+        int result = mMetricsCollector.onPullAtom(SIM_SLOT_STATE, actualAtoms);
+
+        assertThat(actualAtoms).hasSize(1);
+        assertThat(result).isEqualTo(StatsManager.PULL_SUCCESS);
+        // TODO(b/153196254): verify atom contents
+    }
+
+    @Test
+    @SmallTest
+    public void onPullAtom_simSlotState_beforeUiccControllerReady() throws Exception {
+        // there is a slight chance that MetricsCollector gets pulled after registration while
+        // PhoneFactory havne't made UiccController yet, RuntimeException will be thrown
+        replaceInstance(UiccController.class, "mInstance", mUiccController, null);
+        List<StatsEvent> actualAtoms = new ArrayList<>();
+
+        int result = mMetricsCollector.onPullAtom(SIM_SLOT_STATE, actualAtoms);
+
+        assertThat(actualAtoms).hasSize(0);
+        assertThat(result).isEqualTo(StatsManager.PULL_SKIP);
+    }
+
+    @Test
+    @SmallTest
+    public void onPullAtom_supportedRadioAccessFamily_singlePhone() {
+        doReturn(SUPPORTED_RAF_1).when(mPhone).getRadioAccessFamily();
+        StatsEvent expectedAtom =
+                StatsEvent.newBuilder()
+                        .setAtomId(SUPPORTED_RADIO_ACCESS_FAMILY)
+                        .writeLong(SUPPORTED_RAF_1)
+                        .build();
+        List<StatsEvent> actualAtoms = new ArrayList<>();
+
+        int result = mMetricsCollector.onPullAtom(SUPPORTED_RADIO_ACCESS_FAMILY, actualAtoms);
+
+        assertThat(actualAtoms).hasSize(1);
+        assertThat(result).isEqualTo(StatsManager.PULL_SUCCESS);
+        // TODO(b/153196254): verify atom contents
+    }
+
+    @Test
+    @SmallTest
+    public void onPullAtom_supportedRadioAccessFamily_dualPhones() {
+        doReturn(SUPPORTED_RAF_1).when(mPhone).getRadioAccessFamily();
+        doReturn(SUPPORTED_RAF_2).when(mSecondPhone).getRadioAccessFamily();
+        mPhones = new Phone[] {mPhone, mSecondPhone};
+        StatsEvent expectedAtom =
+                StatsEvent.newBuilder()
+                        .setAtomId(SUPPORTED_RADIO_ACCESS_FAMILY)
+                        .writeLong(SUPPORTED_RAF_BOTH)
+                        .build();
+        List<StatsEvent> actualAtoms = new ArrayList<>();
+
+        int result = mMetricsCollector.onPullAtom(SUPPORTED_RADIO_ACCESS_FAMILY, actualAtoms);
+
+        assertThat(actualAtoms).hasSize(1);
+        assertThat(result).isEqualTo(StatsManager.PULL_SUCCESS);
+        // TODO(b/153196254): verify atom contents
+    }
+
+    @Test
+    @SmallTest
+    public void onPullAtom_supportedRadioAccessFamily_dualPhonesWithUnknownRaf() {
+        doReturn(SUPPORTED_RAF_1).when(mPhone).getRadioAccessFamily();
+        doReturn((int) TelephonyManager.NETWORK_TYPE_BITMASK_UNKNOWN)
+                .when(mSecondPhone)
+                .getRadioAccessFamily();
+        mPhones = new Phone[] {mPhone, mSecondPhone};
+        StatsEvent expectedAtom =
+                StatsEvent.newBuilder()
+                        .setAtomId(SUPPORTED_RADIO_ACCESS_FAMILY)
+                        .writeLong(SUPPORTED_RAF_1)
+                        .build();
+        List<StatsEvent> actualAtoms = new ArrayList<>();
+
+        int result = mMetricsCollector.onPullAtom(SUPPORTED_RADIO_ACCESS_FAMILY, actualAtoms);
+
+        assertThat(actualAtoms).hasSize(1);
+        assertThat(result).isEqualTo(StatsManager.PULL_SUCCESS);
+        // TODO(b/153196254): verify atom contents
+    }
+
+    @Test
+    @SmallTest
+    public void onPullAtom_supportedRadioAccessFamily_beforePhoneReady() throws Exception {
+        replaceInstance(PhoneFactory.class, "sMadeDefaults", true, false);
+        List<StatsEvent> actualAtoms = new ArrayList<>();
+
+        int result = mMetricsCollector.onPullAtom(SUPPORTED_RADIO_ACCESS_FAMILY, actualAtoms);
+
+        assertThat(actualAtoms).hasSize(0);
+        assertThat(result).isEqualTo(StatsManager.PULL_SKIP);
+    }
+
+    @Test
+    @SmallTest
+    public void onPullAtom_voiceCallRatUsage_empty() throws Exception {
+        doReturn(new RawVoiceCallRatUsage[0])
+                .when(mPersistAtomsStorage)
+                .getVoiceCallRatUsages(anyLong());
+        List<StatsEvent> actualAtoms = new ArrayList<>();
+
+        int result = mMetricsCollector.onPullAtom(VOICE_CALL_RAT_USAGE, actualAtoms);
+
+        assertThat(actualAtoms).hasSize(0);
+        assertThat(result).isEqualTo(StatsManager.PULL_SUCCESS);
+    }
+
+    @Test
+    @SmallTest
+    public void onPullAtom_voiceCallRatUsage_tooFrequent() throws Exception {
+        doReturn(null).when(mPersistAtomsStorage).getVoiceCallRatUsages(anyLong());
+        List<StatsEvent> actualAtoms = new ArrayList<>();
+
+        int result = mMetricsCollector.onPullAtom(VOICE_CALL_RAT_USAGE, actualAtoms);
+
+        assertThat(actualAtoms).hasSize(0);
+        assertThat(result).isEqualTo(StatsManager.PULL_SKIP);
+        verify(mPersistAtomsStorage, times(1)).getVoiceCallRatUsages(eq(MIN_COOLDOWN_MILLIS));
+        verifyNoMoreInteractions(mPersistAtomsStorage);
+    }
+
+    @Test
+    @SmallTest
+    public void onPullAtom_voiceCallRatUsage_bucketWithTooFewCalls() throws Exception {
+        RawVoiceCallRatUsage usage1 = new RawVoiceCallRatUsage();
+        usage1.callCount = MIN_CALLS_PER_BUCKET;
+        RawVoiceCallRatUsage usage2 = new RawVoiceCallRatUsage();
+        usage2.callCount = MIN_CALLS_PER_BUCKET - 1L;
+        doReturn(new RawVoiceCallRatUsage[] {usage1, usage1, usage1, usage2})
+                .when(mPersistAtomsStorage)
+                .getVoiceCallRatUsages(anyLong());
+        List<StatsEvent> actualAtoms = new ArrayList<>();
+
+        int result = mMetricsCollector.onPullAtom(VOICE_CALL_RAT_USAGE, actualAtoms);
+
+        assertThat(actualAtoms).hasSize(3); // usage 2 should be dropped
+        assertThat(result).isEqualTo(StatsManager.PULL_SUCCESS);
+        // TODO(b/153196254): verify atom contents
+    }
+
+    @Test
+    @SmallTest
+    public void onPullAtom_voiceCallSession_empty() throws Exception {
+        doReturn(new VoiceCallSession[0])
+                .when(mPersistAtomsStorage)
+                .getVoiceCallSessions(anyLong());
+        List<StatsEvent> actualAtoms = new ArrayList<>();
+
+        int result = mMetricsCollector.onPullAtom(VOICE_CALL_SESSION, actualAtoms);
+
+        assertThat(actualAtoms).hasSize(0);
+        assertThat(result).isEqualTo(StatsManager.PULL_SUCCESS);
+    }
+
+    @Test
+    @SmallTest
+    public void onPullAtom_voiceCallSession_tooFrequent() throws Exception {
+        doReturn(null).when(mPersistAtomsStorage).getVoiceCallSessions(anyLong());
+        List<StatsEvent> actualAtoms = new ArrayList<>();
+
+        int result = mMetricsCollector.onPullAtom(VOICE_CALL_SESSION, actualAtoms);
+
+        assertThat(actualAtoms).hasSize(0);
+        assertThat(result).isEqualTo(StatsManager.PULL_SKIP);
+        verify(mPersistAtomsStorage, times(1)).getVoiceCallSessions(eq(MIN_COOLDOWN_MILLIS));
+        verifyNoMoreInteractions(mPersistAtomsStorage);
+    }
+
+    @Test
+    @SmallTest
+    public void onPullAtom_voiceCallSession_multipleCalls() throws Exception {
+        VoiceCallSession call = new VoiceCallSession();
+        doReturn(new VoiceCallSession[] {call, call, call, call})
+                .when(mPersistAtomsStorage)
+                .getVoiceCallSessions(anyLong());
+        List<StatsEvent> actualAtoms = new ArrayList<>();
+
+        int result = mMetricsCollector.onPullAtom(VOICE_CALL_SESSION, actualAtoms);
+
+        assertThat(actualAtoms).hasSize(4);
+        assertThat(result).isEqualTo(StatsManager.PULL_SUCCESS);
+        // TODO(b/153196254): verify atom contents
+    }
+}
diff --git a/tests/telephonytests/src/com/android/internal/telephony/metrics/PersistAtomsStorageTest.java b/tests/telephonytests/src/com/android/internal/telephony/metrics/PersistAtomsStorageTest.java
new file mode 100644
index 0000000..ac1d73f
--- /dev/null
+++ b/tests/telephonytests/src/com/android/internal/telephony/metrics/PersistAtomsStorageTest.java
@@ -0,0 +1,773 @@
+/*
+ * Copyright (C) 2020 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.metrics;
+
+import static com.android.internal.telephony.TelephonyStatsLog.VOICE_CALL_SESSION__BEARER_AT_END__CALL_BEARER_CS;
+import static com.android.internal.telephony.TelephonyStatsLog.VOICE_CALL_SESSION__BEARER_AT_END__CALL_BEARER_IMS;
+import static com.android.internal.telephony.TelephonyStatsLog.VOICE_CALL_SESSION__DIRECTION__CALL_DIRECTION_MO;
+import static com.android.internal.telephony.TelephonyStatsLog.VOICE_CALL_SESSION__DIRECTION__CALL_DIRECTION_MT;
+import static com.android.internal.telephony.TelephonyStatsLog.VOICE_CALL_SESSION__SETUP_DURATION__CALL_SETUP_DURATION_EXTREMELY_FAST;
+import static com.android.internal.telephony.TelephonyStatsLog.VOICE_CALL_SESSION__SETUP_DURATION__CALL_SETUP_DURATION_VERY_FAST;
+import static com.android.internal.telephony.TelephonyStatsLog.VOICE_CALL_SESSION__SETUP_DURATION__CALL_SETUP_DURATION_VERY_SLOW;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.anyInt;
+import static org.mockito.Mockito.anyString;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.inOrder;
+import static org.mockito.Mockito.times;
+
+import android.annotation.Nullable;
+import android.content.Context;
+import android.telephony.DisconnectCause;
+import android.telephony.TelephonyManager;
+import android.telephony.ims.ImsReasonInfo;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import com.android.internal.telephony.TelephonyTest;
+import com.android.internal.telephony.nano.PersistAtomsProto.PersistAtoms;
+import com.android.internal.telephony.nano.PersistAtomsProto.RawVoiceCallRatUsage;
+import com.android.internal.telephony.nano.PersistAtomsProto.VoiceCallSession;
+import com.android.internal.telephony.nano.TelephonyProto.TelephonyCallSession.Event.AudioCodec;
+import com.android.internal.telephony.protobuf.nano.MessageNano;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+import org.mockito.ArgumentCaptor;
+import org.mockito.InOrder;
+import org.mockito.Mock;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.nio.charset.StandardCharsets;
+import java.util.Arrays;
+import java.util.Comparator;
+
+public class PersistAtomsStorageTest extends TelephonyTest {
+    private static final String TEST_FILE = "PersistAtomsStorageTest.pb";
+    private static final int MAX_NUM_CALL_SESSIONS = 50;
+    private static final long START_TIME_MILLIS = 2000L;
+    private static final int CARRIER1_ID = 1;
+    private static final int CARRIER2_ID = 1187;
+    private static final int CARRIER3_ID = 1435;
+
+    @Mock private FileOutputStream mTestFileOutputStream;
+
+    @Rule public TemporaryFolder mFolder = new TemporaryFolder();
+
+    private File mTestFile;
+
+    // call with SRVCC
+    private VoiceCallSession mCall1Proto;
+
+    // call held after another incoming call, ended before the other call
+    private VoiceCallSession mCall2Proto;
+    private VoiceCallSession mCall3Proto;
+
+    // failed call
+    private VoiceCallSession mCall4Proto;
+
+    private RawVoiceCallRatUsage mCarrier1LteUsageProto;
+    private RawVoiceCallRatUsage mCarrier1UmtsUsageProto;
+    private RawVoiceCallRatUsage mCarrier2LteUsageProto;
+    private RawVoiceCallRatUsage mCarrier3LteUsageProto;
+    private RawVoiceCallRatUsage mCarrier3GsmUsageProto;
+
+    private VoiceCallSession[] mVoiceCallSessions;
+    private RawVoiceCallRatUsage[] mVoiceCallRatUsages;
+
+    private void makeTestData() {
+        // MO call with SRVCC (LTE to UMTS)
+        mCall1Proto = new VoiceCallSession();
+        mCall1Proto.bearerAtStart = VOICE_CALL_SESSION__BEARER_AT_END__CALL_BEARER_IMS;
+        mCall1Proto.bearerAtEnd = VOICE_CALL_SESSION__BEARER_AT_END__CALL_BEARER_CS;
+        mCall1Proto.direction = VOICE_CALL_SESSION__DIRECTION__CALL_DIRECTION_MO;
+        mCall1Proto.setupDuration =
+                VOICE_CALL_SESSION__SETUP_DURATION__CALL_SETUP_DURATION_VERY_FAST;
+        mCall1Proto.setupFailed = false;
+        mCall1Proto.disconnectReasonCode = DisconnectCause.LOCAL;
+        mCall1Proto.disconnectExtraCode = 0;
+        mCall1Proto.disconnectExtraMessage = "";
+        mCall1Proto.ratAtStart = TelephonyManager.NETWORK_TYPE_LTE;
+        mCall1Proto.ratAtEnd = TelephonyManager.NETWORK_TYPE_UMTS;
+        mCall1Proto.ratSwitchCount = 1L;
+        mCall1Proto.codecBitmask =
+                (1 << AudioCodec.AUDIO_CODEC_EVS_SWB) | (1 << AudioCodec.AUDIO_CODEC_AMR);
+        mCall1Proto.concurrentCallCountAtStart = 0;
+        mCall1Proto.concurrentCallCountAtEnd = 0;
+        mCall1Proto.simSlotIndex = 0;
+        mCall1Proto.isMultiSim = false;
+        mCall1Proto.isEsim = false;
+        mCall1Proto.carrierId = CARRIER1_ID;
+        mCall1Proto.srvccCompleted = true;
+        mCall1Proto.srvccFailureCount = 0L;
+        mCall1Proto.srvccCancellationCount = 0L;
+        mCall1Proto.rttEnabled = false;
+        mCall1Proto.isEmergency = false;
+        mCall1Proto.isRoaming = false;
+
+        // VoLTE MT call on DSDS/eSIM, hanged up by remote
+        // concurrent with mCall3Proto, started first and ended first
+        mCall2Proto = new VoiceCallSession();
+        mCall2Proto.bearerAtStart = VOICE_CALL_SESSION__BEARER_AT_END__CALL_BEARER_IMS;
+        mCall2Proto.bearerAtEnd = VOICE_CALL_SESSION__BEARER_AT_END__CALL_BEARER_IMS;
+        mCall2Proto.direction = VOICE_CALL_SESSION__DIRECTION__CALL_DIRECTION_MT;
+        mCall2Proto.setupDuration =
+                VOICE_CALL_SESSION__SETUP_DURATION__CALL_SETUP_DURATION_VERY_FAST;
+        mCall2Proto.setupFailed = false;
+        mCall2Proto.disconnectReasonCode = ImsReasonInfo.CODE_USER_TERMINATED_BY_REMOTE;
+        mCall2Proto.disconnectExtraCode = 0;
+        mCall2Proto.disconnectExtraMessage = "normal call clearing";
+        mCall2Proto.ratAtStart = TelephonyManager.NETWORK_TYPE_LTE;
+        mCall2Proto.ratAtEnd = TelephonyManager.NETWORK_TYPE_LTE;
+        mCall2Proto.ratSwitchCount = 0L;
+        mCall2Proto.codecBitmask = (1 << AudioCodec.AUDIO_CODEC_EVS_SWB);
+        mCall2Proto.concurrentCallCountAtStart = 0;
+        mCall2Proto.concurrentCallCountAtEnd = 1;
+        mCall2Proto.simSlotIndex = 1;
+        mCall2Proto.isMultiSim = true;
+        mCall2Proto.isEsim = true;
+        mCall2Proto.carrierId = CARRIER2_ID;
+        mCall2Proto.srvccCompleted = false;
+        mCall2Proto.srvccFailureCount = 0L;
+        mCall2Proto.srvccCancellationCount = 0L;
+        mCall2Proto.rttEnabled = false;
+        mCall2Proto.isEmergency = false;
+        mCall2Proto.isRoaming = false;
+
+        // VoLTE MT call on DSDS/eSIM, hanged up by local, with RTT
+        // concurrent with mCall2Proto, started last and ended last
+        mCall3Proto = new VoiceCallSession();
+        mCall3Proto.bearerAtStart = VOICE_CALL_SESSION__BEARER_AT_END__CALL_BEARER_IMS;
+        mCall3Proto.bearerAtEnd = VOICE_CALL_SESSION__BEARER_AT_END__CALL_BEARER_IMS;
+        mCall3Proto.direction = VOICE_CALL_SESSION__DIRECTION__CALL_DIRECTION_MT;
+        mCall3Proto.setupDuration =
+                VOICE_CALL_SESSION__SETUP_DURATION__CALL_SETUP_DURATION_EXTREMELY_FAST;
+        mCall3Proto.setupFailed = false;
+        mCall3Proto.disconnectReasonCode = ImsReasonInfo.CODE_USER_TERMINATED;
+        mCall3Proto.disconnectExtraCode = 0;
+        mCall3Proto.disconnectExtraMessage = "normal call clearing";
+        mCall3Proto.ratAtStart = TelephonyManager.NETWORK_TYPE_LTE;
+        mCall3Proto.ratAtEnd = TelephonyManager.NETWORK_TYPE_LTE;
+        mCall3Proto.ratSwitchCount = 0L;
+        mCall3Proto.codecBitmask = (1 << AudioCodec.AUDIO_CODEC_EVS_SWB);
+        mCall3Proto.concurrentCallCountAtStart = 1;
+        mCall3Proto.concurrentCallCountAtEnd = 0;
+        mCall3Proto.simSlotIndex = 1;
+        mCall3Proto.isMultiSim = true;
+        mCall3Proto.isEsim = true;
+        mCall3Proto.carrierId = CARRIER2_ID;
+        mCall3Proto.srvccCompleted = false;
+        mCall3Proto.srvccFailureCount = 0L;
+        mCall3Proto.srvccCancellationCount = 0L;
+        mCall3Proto.rttEnabled = true;
+        mCall3Proto.isEmergency = false;
+        mCall3Proto.isRoaming = false;
+
+        // CS MO call while camped on LTE
+        mCall4Proto = new VoiceCallSession();
+        mCall4Proto.bearerAtStart = VOICE_CALL_SESSION__BEARER_AT_END__CALL_BEARER_CS;
+        mCall4Proto.bearerAtEnd = VOICE_CALL_SESSION__BEARER_AT_END__CALL_BEARER_CS;
+        mCall4Proto.direction = VOICE_CALL_SESSION__DIRECTION__CALL_DIRECTION_MO;
+        mCall4Proto.setupDuration =
+                VOICE_CALL_SESSION__SETUP_DURATION__CALL_SETUP_DURATION_VERY_SLOW;
+        mCall4Proto.setupFailed = true;
+        mCall4Proto.disconnectReasonCode = DisconnectCause.NORMAL;
+        mCall4Proto.disconnectExtraCode = 0;
+        mCall4Proto.disconnectExtraMessage = "";
+        mCall4Proto.ratAtStart = TelephonyManager.NETWORK_TYPE_LTE;
+        mCall4Proto.ratAtEnd = TelephonyManager.NETWORK_TYPE_GSM;
+        mCall4Proto.ratSwitchCount = 1L;
+        mCall4Proto.codecBitmask = (1 << AudioCodec.AUDIO_CODEC_AMR);
+        mCall4Proto.concurrentCallCountAtStart = 0;
+        mCall4Proto.concurrentCallCountAtEnd = 0;
+        mCall4Proto.simSlotIndex = 0;
+        mCall4Proto.isMultiSim = true;
+        mCall4Proto.isEsim = false;
+        mCall4Proto.carrierId = CARRIER3_ID;
+        mCall4Proto.srvccCompleted = false;
+        mCall4Proto.srvccFailureCount = 0L;
+        mCall4Proto.srvccCancellationCount = 0L;
+        mCall4Proto.rttEnabled = false;
+        mCall4Proto.isEmergency = false;
+        mCall4Proto.isRoaming = true;
+
+        mCarrier1LteUsageProto = new RawVoiceCallRatUsage();
+        mCarrier1LteUsageProto.carrierId = CARRIER1_ID;
+        mCarrier1LteUsageProto.rat = TelephonyManager.NETWORK_TYPE_LTE;
+        mCarrier1LteUsageProto.callCount = 1L;
+        mCarrier1LteUsageProto.totalDurationMillis = 8000L;
+
+        mCarrier1UmtsUsageProto = new RawVoiceCallRatUsage();
+        mCarrier1UmtsUsageProto.carrierId = CARRIER1_ID;
+        mCarrier1UmtsUsageProto.rat = TelephonyManager.NETWORK_TYPE_UMTS;
+        mCarrier1UmtsUsageProto.callCount = 1L;
+        mCarrier1UmtsUsageProto.totalDurationMillis = 6000L;
+
+        mCarrier2LteUsageProto = new RawVoiceCallRatUsage();
+        mCarrier2LteUsageProto.carrierId = CARRIER2_ID;
+        mCarrier2LteUsageProto.rat = TelephonyManager.NETWORK_TYPE_LTE;
+        mCarrier2LteUsageProto.callCount = 2L;
+        mCarrier2LteUsageProto.totalDurationMillis = 20000L;
+
+        mCarrier3LteUsageProto = new RawVoiceCallRatUsage();
+        mCarrier3LteUsageProto.carrierId = CARRIER3_ID;
+        mCarrier3LteUsageProto.rat = TelephonyManager.NETWORK_TYPE_LTE;
+        mCarrier3LteUsageProto.callCount = 1L;
+        mCarrier3LteUsageProto.totalDurationMillis = 1000L;
+
+        mCarrier3GsmUsageProto = new RawVoiceCallRatUsage();
+        mCarrier3GsmUsageProto.carrierId = CARRIER3_ID;
+        mCarrier3GsmUsageProto.rat = TelephonyManager.NETWORK_TYPE_GSM;
+        mCarrier3GsmUsageProto.callCount = 1L;
+        mCarrier3GsmUsageProto.totalDurationMillis = 100000L;
+
+        mVoiceCallRatUsages =
+                new RawVoiceCallRatUsage[] {
+                    mCarrier1UmtsUsageProto,
+                    mCarrier1LteUsageProto,
+                    mCarrier2LteUsageProto,
+                    mCarrier3LteUsageProto,
+                    mCarrier3GsmUsageProto
+                };
+        mVoiceCallSessions =
+                new VoiceCallSession[] {mCall1Proto, mCall2Proto, mCall3Proto, mCall4Proto};
+    }
+
+    private static class TestablePersistAtomsStorage extends PersistAtomsStorage {
+        private long mTimeMillis = START_TIME_MILLIS;
+
+        TestablePersistAtomsStorage(Context context) {
+            super(context);
+        }
+
+        @Override
+        protected long getWallTimeMillis() {
+            // NOTE: super class constructor will be executed before private field is set, which
+            // gives the wrong start time (mTimeMillis will have its default value of 0L)
+            return mTimeMillis == 0L ? START_TIME_MILLIS : mTimeMillis;
+        }
+
+        private void setTimeMillis(long timeMillis) {
+            mTimeMillis = timeMillis;
+        }
+
+        private void incTimeMillis(long timeMillis) {
+            mTimeMillis += timeMillis;
+        }
+
+        private PersistAtoms getAtomsProto() {
+            // NOTE: not guarded by mLock as usual, should be fine since the test is single-threaded
+            return mAtoms;
+        }
+    }
+
+    private TestablePersistAtomsStorage mPersistAtomsStorage;
+
+    private static final Comparator<MessageNano> sProtoComparator =
+            new Comparator<>() {
+                @Override
+                public int compare(MessageNano o1, MessageNano o2) {
+                    if (o1 == o2) {
+                        return 0;
+                    }
+                    if (o1 == null) {
+                        return -1;
+                    }
+                    if (o2 == null) {
+                        return 1;
+                    }
+                    assertEquals(o1.getClass(), o2.getClass());
+                    return o1.toString().compareTo(o2.toString());
+                }
+            };
+
+    @Before
+    public void setUp() throws Exception {
+        super.setUp(getClass().getSimpleName());
+        makeTestData();
+
+        // by default, test loading with real file IO and saving with mocks
+        mTestFile = mFolder.newFile(TEST_FILE);
+        doReturn(mTestFileOutputStream).when(mContext).openFileOutput(anyString(), anyInt());
+        doReturn(mTestFile).when(mContext).getFileStreamPath(anyString());
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        mTestFile.delete();
+        super.tearDown();
+    }
+
+    @Test
+    @SmallTest
+    public void loadAtoms_fileNotExist() throws Exception {
+        mTestFile.delete();
+
+        mPersistAtomsStorage = new TestablePersistAtomsStorage(mContext);
+        mPersistAtomsStorage.incTimeMillis(100L);
+
+        // no exception should be thrown, storage should be empty, pull time should be start time
+        assertEquals(
+                START_TIME_MILLIS,
+                mPersistAtomsStorage.getAtomsProto().rawVoiceCallRatUsagePullTimestampMillis);
+        assertEquals(
+                START_TIME_MILLIS,
+                mPersistAtomsStorage.getAtomsProto().voiceCallSessionPullTimestampMillis);
+        RawVoiceCallRatUsage[] voiceCallRatUsage = mPersistAtomsStorage.getVoiceCallRatUsages(0L);
+        VoiceCallSession[] voiceCallSession = mPersistAtomsStorage.getVoiceCallSessions(0L);
+        assertNotNull(voiceCallRatUsage);
+        assertEquals(0, voiceCallRatUsage.length);
+        assertNotNull(voiceCallSession);
+        assertEquals(0, voiceCallSession.length);
+    }
+
+    @Test
+    @SmallTest
+    public void loadAtoms_unreadable() throws Exception {
+        createEmptyTestFile();
+        mTestFile.setReadable(false);
+
+        mPersistAtomsStorage = new TestablePersistAtomsStorage(mContext);
+        mPersistAtomsStorage.incTimeMillis(100L);
+
+        // no exception should be thrown, storage should be empty, pull time should be start time
+        assertEquals(
+                START_TIME_MILLIS,
+                mPersistAtomsStorage.getAtomsProto().rawVoiceCallRatUsagePullTimestampMillis);
+        assertEquals(
+                START_TIME_MILLIS,
+                mPersistAtomsStorage.getAtomsProto().voiceCallSessionPullTimestampMillis);
+        RawVoiceCallRatUsage[] voiceCallRatUsage = mPersistAtomsStorage.getVoiceCallRatUsages(0L);
+        VoiceCallSession[] voiceCallSession = mPersistAtomsStorage.getVoiceCallSessions(0L);
+        assertNotNull(voiceCallRatUsage);
+        assertEquals(0, voiceCallRatUsage.length);
+        assertNotNull(voiceCallSession);
+        assertEquals(0, voiceCallSession.length);
+    }
+
+    @Test
+    @SmallTest
+    public void loadAtoms_emptyProto() throws Exception {
+        createEmptyTestFile();
+
+        mPersistAtomsStorage = new TestablePersistAtomsStorage(mContext);
+        mPersistAtomsStorage.incTimeMillis(100L);
+
+        // no exception should be thrown, storage should be empty, pull time should be start time
+        assertEquals(
+                START_TIME_MILLIS,
+                mPersistAtomsStorage.getAtomsProto().rawVoiceCallRatUsagePullTimestampMillis);
+        assertEquals(
+                START_TIME_MILLIS,
+                mPersistAtomsStorage.getAtomsProto().voiceCallSessionPullTimestampMillis);
+        RawVoiceCallRatUsage[] voiceCallRatUsage = mPersistAtomsStorage.getVoiceCallRatUsages(0L);
+        VoiceCallSession[] voiceCallSession = mPersistAtomsStorage.getVoiceCallSessions(0L);
+        assertNotNull(voiceCallRatUsage);
+        assertEquals(0, voiceCallRatUsage.length);
+        assertNotNull(voiceCallSession);
+        assertEquals(0, voiceCallSession.length);
+    }
+
+    @Test
+    @SmallTest
+    public void loadAtoms_malformedFile() throws Exception {
+        FileOutputStream stream = new FileOutputStream(mTestFile);
+        stream.write("This is not a proto file.".getBytes(StandardCharsets.UTF_8));
+        stream.close();
+
+        mPersistAtomsStorage = new TestablePersistAtomsStorage(mContext);
+        mPersistAtomsStorage.incTimeMillis(100L);
+
+        // no exception should be thrown, storage should be empty, pull time should be start time
+        assertEquals(
+                START_TIME_MILLIS,
+                mPersistAtomsStorage.getAtomsProto().rawVoiceCallRatUsagePullTimestampMillis);
+        assertEquals(
+                START_TIME_MILLIS,
+                mPersistAtomsStorage.getAtomsProto().voiceCallSessionPullTimestampMillis);
+        RawVoiceCallRatUsage[] voiceCallRatUsage = mPersistAtomsStorage.getVoiceCallRatUsages(0L);
+        VoiceCallSession[] voiceCallSession = mPersistAtomsStorage.getVoiceCallSessions(0L);
+        assertNotNull(voiceCallRatUsage);
+        assertEquals(0, voiceCallRatUsage.length);
+        assertNotNull(voiceCallSession);
+        assertEquals(0, voiceCallSession.length);
+    }
+
+    @Test
+    @SmallTest
+    public void loadAtoms_pullTimeMissing() throws Exception {
+        createTestFile(0L);
+
+        mPersistAtomsStorage = new TestablePersistAtomsStorage(mContext);
+        mPersistAtomsStorage.incTimeMillis(100L);
+
+        // no exception should be thrown, storage should be match, pull time should be start time
+        assertEquals(
+                START_TIME_MILLIS,
+                mPersistAtomsStorage.getAtomsProto().rawVoiceCallRatUsagePullTimestampMillis);
+        assertEquals(
+                START_TIME_MILLIS,
+                mPersistAtomsStorage.getAtomsProto().voiceCallSessionPullTimestampMillis);
+        RawVoiceCallRatUsage[] voiceCallRatUsage = mPersistAtomsStorage.getVoiceCallRatUsages(0L);
+        VoiceCallSession[] voiceCallSession = mPersistAtomsStorage.getVoiceCallSessions(0L);
+        assertProtoArrayEquals(mVoiceCallRatUsages, voiceCallRatUsage);
+        assertProtoArrayEquals(mVoiceCallSessions, voiceCallSession);
+    }
+
+    @Test
+    @SmallTest
+    public void loadAtoms_validContents() throws Exception {
+        createTestFile(100L);
+
+        mPersistAtomsStorage = new TestablePersistAtomsStorage(mContext);
+
+        // no exception should be thrown, storage and pull time should match
+        assertEquals(
+                100L, mPersistAtomsStorage.getAtomsProto().rawVoiceCallRatUsagePullTimestampMillis);
+        assertEquals(
+                100L, mPersistAtomsStorage.getAtomsProto().voiceCallSessionPullTimestampMillis);
+        RawVoiceCallRatUsage[] voiceCallRatUsage = mPersistAtomsStorage.getVoiceCallRatUsages(0L);
+        VoiceCallSession[] voiceCallSession = mPersistAtomsStorage.getVoiceCallSessions(0L);
+        assertProtoArrayEquals(mVoiceCallRatUsages, voiceCallRatUsage);
+        assertProtoArrayEquals(mVoiceCallSessions, voiceCallSession);
+    }
+
+    @Test
+    @SmallTest
+    public void addVoiceCallSession_emptyProto() throws Exception {
+        createEmptyTestFile();
+
+        mPersistAtomsStorage = new TestablePersistAtomsStorage(mContext);
+        mPersistAtomsStorage.addVoiceCallSession(mCall1Proto);
+        mPersistAtomsStorage.incTimeMillis(100L);
+
+        // call should be added successfully, there should be no RAT usage, changes should be saved
+        RawVoiceCallRatUsage[] voiceCallRatUsage = mPersistAtomsStorage.getVoiceCallRatUsages(0L);
+        VoiceCallSession[] voiceCallSession = mPersistAtomsStorage.getVoiceCallSessions(0L);
+        assertNotNull(voiceCallRatUsage);
+        assertEquals(0, voiceCallRatUsage.length);
+        assertProtoArrayEquals(new VoiceCallSession[] {mCall1Proto}, voiceCallSession);
+        InOrder inOrder = inOrder(mTestFileOutputStream);
+        inOrder.verify(mTestFileOutputStream, times(1))
+                .write(eq(PersistAtoms.toByteArray(mPersistAtomsStorage.getAtomsProto())));
+        inOrder.verify(mTestFileOutputStream, times(1)).close();
+        inOrder.verifyNoMoreInteractions();
+    }
+
+    @Test
+    @SmallTest
+    public void addVoiceCallSession_withExistingCalls() throws Exception {
+        createTestFile(100L);
+
+        mPersistAtomsStorage = new TestablePersistAtomsStorage(mContext);
+        mPersistAtomsStorage.addVoiceCallSession(mCall1Proto);
+        mPersistAtomsStorage.incTimeMillis(100L);
+
+        // call should be added successfully, RAT usages should not change, changes should be saved
+        RawVoiceCallRatUsage[] voiceCallRatUsage = mPersistAtomsStorage.getVoiceCallRatUsages(0L);
+        VoiceCallSession[] voiceCallSession = mPersistAtomsStorage.getVoiceCallSessions(0L);
+        assertNotNull(voiceCallRatUsage);
+        assertEquals(mVoiceCallRatUsages.length, voiceCallRatUsage.length);
+        assertNotNull(voiceCallSession);
+        // call lists are randomized, but sorted version should be identical
+        VoiceCallSession[] expectedVoiceCallSessions =
+                new VoiceCallSession[] {
+                    mCall1Proto, mCall1Proto, mCall2Proto, mCall3Proto, mCall4Proto
+                };
+        Arrays.sort(expectedVoiceCallSessions, sProtoComparator);
+        Arrays.sort(voiceCallSession, sProtoComparator);
+        assertProtoArrayEquals(expectedVoiceCallSessions, voiceCallSession);
+        InOrder inOrder = inOrder(mTestFileOutputStream);
+        inOrder.verify(mTestFileOutputStream, times(1))
+                .write(eq(PersistAtoms.toByteArray(mPersistAtomsStorage.getAtomsProto())));
+        inOrder.verify(mTestFileOutputStream, times(1)).close();
+        inOrder.verifyNoMoreInteractions();
+    }
+
+    @Test
+    @SmallTest
+    public void addVoiceCallSession_tooManyCalls() throws Exception {
+        createEmptyTestFile();
+
+        mPersistAtomsStorage = new TestablePersistAtomsStorage(mContext);
+        addRepeatedCalls(mPersistAtomsStorage, mCall1Proto, 50);
+        mPersistAtomsStorage.addVoiceCallSession(mCall2Proto);
+        mPersistAtomsStorage.incTimeMillis(100L);
+
+        // one previous call should be evicted, the new call should be added
+        VoiceCallSession[] calls = mPersistAtomsStorage.getVoiceCallSessions(0L);
+        assertHasCall(calls, mCall1Proto, 49);
+        assertHasCall(calls, mCall2Proto, 1);
+    }
+
+    @Test
+    @SmallTest
+    public void addVoiceCallRatUsage_emptyProto() throws Exception {
+        createEmptyTestFile();
+        VoiceCallRatTracker ratTracker = VoiceCallRatTracker.fromProto(mVoiceCallRatUsages);
+
+        mPersistAtomsStorage = new TestablePersistAtomsStorage(mContext);
+        mPersistAtomsStorage.addVoiceCallRatUsage(ratTracker);
+        mPersistAtomsStorage.incTimeMillis(100L);
+
+        // RAT should be added successfully, calls should not change, changes should be saved
+        RawVoiceCallRatUsage[] voiceCallRatUsage = mPersistAtomsStorage.getVoiceCallRatUsages(0L);
+        VoiceCallSession[] voiceCallSession = mPersistAtomsStorage.getVoiceCallSessions(0L);
+        RawVoiceCallRatUsage[] expectedVoiceCallRatUsage = mVoiceCallRatUsages.clone();
+        Arrays.sort(expectedVoiceCallRatUsage, sProtoComparator);
+        Arrays.sort(voiceCallRatUsage, sProtoComparator);
+        assertProtoArrayEquals(expectedVoiceCallRatUsage, voiceCallRatUsage);
+        assertNotNull(voiceCallSession);
+        assertEquals(0, voiceCallSession.length);
+        InOrder inOrder = inOrder(mTestFileOutputStream);
+        inOrder.verify(mTestFileOutputStream, times(1))
+                .write(eq(PersistAtoms.toByteArray(mPersistAtomsStorage.getAtomsProto())));
+        inOrder.verify(mTestFileOutputStream, times(1)).close();
+        inOrder.verifyNoMoreInteractions();
+    }
+
+    @Test
+    @SmallTest
+    public void addVoiceCallRatUsage_withExistingUsages() throws Exception {
+        createTestFile(100L);
+        VoiceCallRatTracker ratTracker = VoiceCallRatTracker.fromProto(mVoiceCallRatUsages);
+
+        mPersistAtomsStorage = new TestablePersistAtomsStorage(mContext);
+        mPersistAtomsStorage.addVoiceCallRatUsage(ratTracker);
+        mPersistAtomsStorage.incTimeMillis(100L);
+
+        // RAT should be added successfully, calls should not change, changes should be saved
+        RawVoiceCallRatUsage[] voiceCallRatUsage = mPersistAtomsStorage.getVoiceCallRatUsages(0L);
+        VoiceCallSession[] voiceCallSession = mPersistAtomsStorage.getVoiceCallSessions(0L);
+        // call count and duration should become doubled since mVoiceCallRatUsages applied through
+        // both file and addVoiceCallRatUsage()
+        RawVoiceCallRatUsage[] expectedVoiceCallRatUsage =
+                multiplyVoiceCallRatUsage(mVoiceCallRatUsages, 2);
+        Arrays.sort(expectedVoiceCallRatUsage, sProtoComparator);
+        Arrays.sort(voiceCallRatUsage, sProtoComparator);
+        assertProtoArrayEquals(expectedVoiceCallRatUsage, voiceCallRatUsage);
+        assertNotNull(voiceCallSession);
+        assertEquals(mVoiceCallSessions.length, voiceCallSession.length);
+        InOrder inOrder = inOrder(mTestFileOutputStream);
+        inOrder.verify(mTestFileOutputStream, times(1))
+                .write(eq(PersistAtoms.toByteArray(mPersistAtomsStorage.getAtomsProto())));
+        inOrder.verify(mTestFileOutputStream, times(1)).close();
+        inOrder.verifyNoMoreInteractions();
+    }
+
+    @Test
+    @SmallTest
+    public void addVoiceCallRatUsage_empty() throws Exception {
+        createEmptyTestFile();
+        VoiceCallRatTracker ratTracker = VoiceCallRatTracker.fromProto(new RawVoiceCallRatUsage[0]);
+
+        mPersistAtomsStorage = new TestablePersistAtomsStorage(mContext);
+        mPersistAtomsStorage.addVoiceCallRatUsage(ratTracker);
+        mPersistAtomsStorage.incTimeMillis(100L);
+
+        // RAT should be added successfully, calls should not change
+        // in this case it does not necessarily need to save
+        RawVoiceCallRatUsage[] voiceCallRatUsage = mPersistAtomsStorage.getVoiceCallRatUsages(0L);
+        VoiceCallSession[] voiceCallSession = mPersistAtomsStorage.getVoiceCallSessions(0L);
+        assertNotNull(voiceCallRatUsage);
+        assertEquals(0, voiceCallRatUsage.length);
+        assertNotNull(voiceCallSession);
+        assertEquals(0, voiceCallSession.length);
+    }
+
+    @Test
+    @SmallTest
+    public void getVoiceCallRatUsages_tooFrequent() throws Exception {
+        createTestFile(START_TIME_MILLIS);
+
+        mPersistAtomsStorage = new TestablePersistAtomsStorage(mContext);
+        mPersistAtomsStorage.incTimeMillis(50L); // pull interval less than minimum
+        RawVoiceCallRatUsage[] voiceCallRatUsage = mPersistAtomsStorage.getVoiceCallRatUsages(100L);
+
+        // should be denied
+        assertNull(voiceCallRatUsage);
+    }
+
+    @Test
+    @SmallTest
+    public void getVoiceCallRatUsages_withSavedAtoms() throws Exception {
+        createTestFile(START_TIME_MILLIS);
+
+        mPersistAtomsStorage = new TestablePersistAtomsStorage(mContext);
+        mPersistAtomsStorage.incTimeMillis(100L);
+        RawVoiceCallRatUsage[] voiceCallRatUsage1 = mPersistAtomsStorage.getVoiceCallRatUsages(50L);
+        mPersistAtomsStorage.incTimeMillis(100L);
+        RawVoiceCallRatUsage[] voiceCallRatUsage2 = mPersistAtomsStorage.getVoiceCallRatUsages(50L);
+        long voiceCallSessionPullTimestampMillis =
+                mPersistAtomsStorage.getAtomsProto().voiceCallSessionPullTimestampMillis;
+        VoiceCallSession[] voiceCallSession = mPersistAtomsStorage.getVoiceCallSessions(50L);
+
+        // first set of results should equal to file contents, second should be empty, corresponding
+        // pull timestamp should be updated and saved, other fields should be unaffected
+        assertProtoArrayEquals(mVoiceCallRatUsages, voiceCallRatUsage1);
+        assertProtoArrayEquals(new RawVoiceCallRatUsage[0], voiceCallRatUsage2);
+        assertEquals(
+                START_TIME_MILLIS + 200L,
+                mPersistAtomsStorage.getAtomsProto().rawVoiceCallRatUsagePullTimestampMillis);
+        assertProtoArrayEquals(mVoiceCallSessions, voiceCallSession);
+        assertEquals(START_TIME_MILLIS, voiceCallSessionPullTimestampMillis);
+        InOrder inOrder = inOrder(mTestFileOutputStream);
+        assertEquals(
+                START_TIME_MILLIS + 100L,
+                getAtomsWritten(inOrder).rawVoiceCallRatUsagePullTimestampMillis);
+        assertEquals(
+                START_TIME_MILLIS + 200L,
+                getAtomsWritten(inOrder).rawVoiceCallRatUsagePullTimestampMillis);
+        assertEquals(
+                START_TIME_MILLIS + 200L,
+                getAtomsWritten(inOrder).voiceCallSessionPullTimestampMillis);
+        inOrder.verifyNoMoreInteractions();
+    }
+
+    @Test
+    @SmallTest
+    public void getVoiceCallSessions_tooFrequent() throws Exception {
+        createTestFile(START_TIME_MILLIS);
+
+        mPersistAtomsStorage = new TestablePersistAtomsStorage(mContext);
+        mPersistAtomsStorage.incTimeMillis(50L); // pull interval less than minimum
+        VoiceCallSession[] voiceCallSession = mPersistAtomsStorage.getVoiceCallSessions(100L);
+
+        // should be denied
+        assertNull(voiceCallSession);
+    }
+
+    @Test
+    @SmallTest
+    public void getVoiceCallSessions_withSavedAtoms() throws Exception {
+        createTestFile(START_TIME_MILLIS);
+
+        mPersistAtomsStorage = new TestablePersistAtomsStorage(mContext);
+        mPersistAtomsStorage.incTimeMillis(100L);
+        VoiceCallSession[] voiceCallSession1 = mPersistAtomsStorage.getVoiceCallSessions(50L);
+        mPersistAtomsStorage.incTimeMillis(100L);
+        VoiceCallSession[] voiceCallSession2 = mPersistAtomsStorage.getVoiceCallSessions(50L);
+        long voiceCallRatUsagePullTimestampMillis =
+                mPersistAtomsStorage.getAtomsProto().rawVoiceCallRatUsagePullTimestampMillis;
+        RawVoiceCallRatUsage[] voiceCallRatUsage = mPersistAtomsStorage.getVoiceCallRatUsages(50L);
+
+        // first set of results should equal to file contents, second should be empty, corresponding
+        // pull timestamp should be updated and saved, other fields should be unaffected
+        assertProtoArrayEquals(mVoiceCallSessions, voiceCallSession1);
+        assertProtoArrayEquals(new VoiceCallSession[0], voiceCallSession2);
+        assertEquals(
+                START_TIME_MILLIS + 200L,
+                mPersistAtomsStorage.getAtomsProto().voiceCallSessionPullTimestampMillis);
+        assertProtoArrayEquals(mVoiceCallRatUsages, voiceCallRatUsage);
+        assertEquals(START_TIME_MILLIS, voiceCallRatUsagePullTimestampMillis);
+        InOrder inOrder = inOrder(mTestFileOutputStream);
+        assertEquals(
+                START_TIME_MILLIS + 100L,
+                getAtomsWritten(inOrder).voiceCallSessionPullTimestampMillis);
+        assertEquals(
+                START_TIME_MILLIS + 200L,
+                getAtomsWritten(inOrder).voiceCallSessionPullTimestampMillis);
+        assertEquals(
+                START_TIME_MILLIS + 200L,
+                getAtomsWritten(inOrder).rawVoiceCallRatUsagePullTimestampMillis);
+        inOrder.verifyNoMoreInteractions();
+    }
+
+    private void createEmptyTestFile() throws Exception {
+        PersistAtoms atoms = new PersistAtoms();
+        FileOutputStream stream = new FileOutputStream(mTestFile);
+        stream.write(PersistAtoms.toByteArray(atoms));
+        stream.close();
+    }
+
+    private void createTestFile(long lastPullTimeMillis) throws Exception {
+        PersistAtoms atoms = new PersistAtoms();
+        atoms.rawVoiceCallRatUsagePullTimestampMillis = lastPullTimeMillis;
+        atoms.voiceCallSessionPullTimestampMillis = lastPullTimeMillis;
+        atoms.rawVoiceCallRatUsage = mVoiceCallRatUsages;
+        atoms.voiceCallSession = mVoiceCallSessions;
+        FileOutputStream stream = new FileOutputStream(mTestFile);
+        stream.write(PersistAtoms.toByteArray(atoms));
+        stream.close();
+    }
+
+    private PersistAtoms getAtomsWritten(@Nullable InOrder inOrder) throws Exception {
+        if (inOrder == null) {
+            inOrder = inOrder(mTestFileOutputStream);
+        }
+        ArgumentCaptor bytesCaptor = ArgumentCaptor.forClass(Object.class);
+        inOrder.verify(mTestFileOutputStream, times(1)).write((byte[]) bytesCaptor.capture());
+        PersistAtoms savedAtoms = PersistAtoms.parseFrom((byte[]) bytesCaptor.getValue());
+        inOrder.verify(mTestFileOutputStream, times(1)).close();
+        return savedAtoms;
+    }
+
+    private static void addRepeatedCalls(
+            PersistAtomsStorage storage, VoiceCallSession call, int count) {
+        for (int i = 0; i < count; i++) {
+            storage.addVoiceCallSession(call);
+        }
+    }
+
+    private static RawVoiceCallRatUsage[] multiplyVoiceCallRatUsage(
+            RawVoiceCallRatUsage[] usages, int times) {
+        RawVoiceCallRatUsage[] multipliedUsages = new RawVoiceCallRatUsage[usages.length];
+        for (int i = 0; i < usages.length; i++) {
+            multipliedUsages[i] = new RawVoiceCallRatUsage();
+            multipliedUsages[i].carrierId = usages[i].carrierId;
+            multipliedUsages[i].rat = usages[i].rat;
+            multipliedUsages[i].callCount = usages[i].callCount * 2;
+            multipliedUsages[i].totalDurationMillis = usages[i].totalDurationMillis * 2;
+        }
+        return multipliedUsages;
+    }
+
+    private static void assertProtoArrayEquals(MessageNano[] expected, MessageNano[] actual) {
+        assertNotNull(expected);
+        assertNotNull(actual);
+        assertEquals(expected.length, actual.length);
+        for (int i = 0; i < expected.length; i++) {
+            assertTrue(
+                    String.format(
+                            "Message %d of %d differs:\n=== expected ===\n%s=== got ===\n%s",
+                            i + 1, expected.length, expected[i].toString(), actual[i].toString()),
+                    MessageNano.messageNanoEquals(expected[i], actual[i]));
+        }
+    }
+
+    private static void assertHasCall(
+            VoiceCallSession[] calls, @Nullable VoiceCallSession expectedCall, int expectedCount) {
+        assertNotNull(calls);
+        int actualCount = 0;
+        for (VoiceCallSession call : calls) {
+            if (call != null && expectedCall != null) {
+                if (MessageNano.messageNanoEquals(call, expectedCall)) {
+                    actualCount++;
+                }
+            }
+        }
+        assertEquals(expectedCount, actualCount);
+    }
+}
diff --git a/tests/telephonytests/src/com/android/internal/telephony/metrics/SimSlotStateTest.java b/tests/telephonytests/src/com/android/internal/telephony/metrics/SimSlotStateTest.java
new file mode 100644
index 0000000..0f1196f
--- /dev/null
+++ b/tests/telephonytests/src/com/android/internal/telephony/metrics/SimSlotStateTest.java
@@ -0,0 +1,321 @@
+/*
+ * Copyright (C) 2020 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.metrics;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.eq;
+
+import android.test.suitebuilder.annotation.SmallTest;
+
+import com.android.internal.telephony.TelephonyTest;
+import com.android.internal.telephony.uicc.IccCardStatus.CardState;
+import com.android.internal.telephony.uicc.UiccCard;
+import com.android.internal.telephony.uicc.UiccSlot;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+
+public class SimSlotStateTest extends TelephonyTest {
+    @Mock private UiccSlot mInactiveSlot;
+    @Mock private UiccSlot mEmptySlot;
+    @Mock private UiccSlot mPhysicalSlot;
+    @Mock private UiccSlot mEsimSlot;
+
+    @Mock private UiccCard mInactiveCard;
+    @Mock private UiccCard mActiveCard;
+
+    @Before
+    public void setUp() throws Exception {
+        super.setUp(getClass().getSimpleName());
+
+        doReturn(false).when(mInactiveSlot).isActive();
+
+        doReturn(true).when(mEmptySlot).isActive();
+        doReturn(CardState.CARDSTATE_ABSENT).when(mEmptySlot).getCardState();
+
+        doReturn(true).when(mPhysicalSlot).isActive();
+        doReturn(CardState.CARDSTATE_PRESENT).when(mPhysicalSlot).getCardState();
+        doReturn(false).when(mPhysicalSlot).isEuicc();
+
+        doReturn(true).when(mEsimSlot).isActive();
+        doReturn(CardState.CARDSTATE_PRESENT).when(mEsimSlot).getCardState();
+        doReturn(true).when(mEsimSlot).isEuicc();
+
+        doReturn(0).when(mInactiveCard).getNumApplications();
+        doReturn(4).when(mActiveCard).getNumApplications();
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        super.tearDown();
+    }
+
+    @Test
+    @SmallTest
+    public void testEmptySlots() {
+        doReturn(new UiccSlot[] {}).when(mUiccController).getUiccSlots();
+
+        SimSlotState state = SimSlotState.getCurrentState();
+
+        assertEquals(0, state.numActiveSlots);
+        assertEquals(0, state.numActiveSims);
+        assertEquals(0, state.numActiveEsims);
+    }
+
+    @Test
+    @SmallTest
+    public void testSingleSim_nullSlot() {
+        setupSingleSim(null);
+
+        SimSlotState state = SimSlotState.getCurrentState();
+
+        assertEquals(0, state.numActiveSlots);
+        assertEquals(0, state.numActiveSims);
+        assertEquals(0, state.numActiveEsims);
+    }
+
+    @Test
+    @SmallTest
+    public void testSingleSim_inactiveSlot() {
+        setupSingleSim(mInactiveSlot);
+
+        SimSlotState state = SimSlotState.getCurrentState();
+
+        assertEquals(0, state.numActiveSlots);
+        assertEquals(0, state.numActiveSims);
+        assertEquals(0, state.numActiveEsims);
+    }
+
+    @Test
+    @SmallTest
+    public void testSingleSim_noSimCard() {
+        setupSingleSim(mEmptySlot);
+
+        SimSlotState state = SimSlotState.getCurrentState();
+
+        assertEquals(1, state.numActiveSlots);
+        assertEquals(0, state.numActiveSims);
+        assertEquals(0, state.numActiveEsims);
+    }
+
+    @Test
+    @SmallTest
+    public void testSingleSim_physicalSimCard() {
+        // NOTE: card in slot can be null in this case
+        setupSingleSim(mPhysicalSlot);
+
+        SimSlotState state = SimSlotState.getCurrentState();
+
+        assertEquals(1, state.numActiveSlots);
+        assertEquals(1, state.numActiveSims);
+        assertEquals(0, state.numActiveEsims);
+    }
+
+    @Test
+    @SmallTest
+    public void testSingleSim_physicalSimCardInErrorState() {
+        doReturn(CardState.CARDSTATE_ERROR).when(mPhysicalSlot).getCardState();
+        setupSingleSim(mPhysicalSlot);
+
+        SimSlotState state = SimSlotState.getCurrentState();
+
+        assertEquals(1, state.numActiveSlots);
+        assertEquals(0, state.numActiveSims);
+        assertEquals(0, state.numActiveEsims);
+    }
+
+    @Test
+    @SmallTest
+    public void testSingleSim_physicalSimCardInRestrictedState() {
+        doReturn(CardState.CARDSTATE_RESTRICTED).when(mPhysicalSlot).getCardState();
+        setupSingleSim(mPhysicalSlot);
+
+        SimSlotState state = SimSlotState.getCurrentState();
+
+        // the metrics should not count restricted cards since they cannot be used
+        assertEquals(1, state.numActiveSlots);
+        assertEquals(0, state.numActiveSims);
+        assertEquals(0, state.numActiveEsims);
+    }
+
+    @Test
+    @SmallTest
+    public void testSingleSim_esimCardWithNullCard() {
+        doReturn(null).when(mEsimSlot).getUiccCard();
+        setupSingleSim(mEsimSlot);
+
+        SimSlotState state = SimSlotState.getCurrentState();
+
+        assertEquals(1, state.numActiveSlots);
+        assertEquals(0, state.numActiveSims);
+        assertEquals(0, state.numActiveEsims);
+    }
+
+    @Test
+    @SmallTest
+    public void testSingleSim_esimCardWithoutProfile() {
+        doReturn(mInactiveCard).when(mEsimSlot).getUiccCard();
+        setupSingleSim(mEsimSlot);
+
+        SimSlotState state = SimSlotState.getCurrentState();
+
+        assertEquals(1, state.numActiveSlots);
+        assertEquals(0, state.numActiveSims);
+        assertEquals(0, state.numActiveEsims);
+    }
+
+    @Test
+    @SmallTest
+    public void testSingleSim_esimCardWithProfile() {
+        doReturn(mActiveCard).when(mEsimSlot).getUiccCard();
+        setupSingleSim(mEsimSlot);
+
+        SimSlotState state = SimSlotState.getCurrentState();
+
+        assertEquals(1, state.numActiveSlots);
+        assertEquals(1, state.numActiveSims);
+        assertEquals(1, state.numActiveEsims);
+    }
+
+    @Test
+    @SmallTest
+    public void testDsdsSingleSimMode_noSimCard() {
+        setupDualSim(mEmptySlot, mInactiveSlot);
+
+        SimSlotState state = SimSlotState.getCurrentState();
+
+        assertEquals(1, state.numActiveSlots);
+        assertEquals(0, state.numActiveSims);
+        assertEquals(0, state.numActiveEsims);
+    }
+
+    @Test
+    @SmallTest
+    public void testDsdsSingleSimMode_physicalSimCard() {
+        setupDualSim(mPhysicalSlot, mInactiveSlot);
+
+        SimSlotState state = SimSlotState.getCurrentState();
+
+        assertEquals(1, state.numActiveSlots);
+        assertEquals(1, state.numActiveSims);
+        assertEquals(0, state.numActiveEsims);
+    }
+
+    @Test
+    @SmallTest
+    public void testDsdsSingleSimMode_esimCardWithoutProfile() {
+        doReturn(mInactiveCard).when(mEsimSlot).getUiccCard();
+        setupDualSim(mInactiveSlot, mEsimSlot);
+
+        SimSlotState state = SimSlotState.getCurrentState();
+
+        assertEquals(1, state.numActiveSlots);
+        assertEquals(0, state.numActiveSims);
+        assertEquals(0, state.numActiveEsims);
+    }
+
+    @Test
+    @SmallTest
+    public void testDsdsSingleSimMode_esimCardWithProfile() {
+        doReturn(mActiveCard).when(mEsimSlot).getUiccCard();
+        setupDualSim(mInactiveSlot, mEsimSlot);
+
+        SimSlotState state = SimSlotState.getCurrentState();
+
+        assertEquals(1, state.numActiveSlots);
+        assertEquals(1, state.numActiveSims);
+        assertEquals(1, state.numActiveEsims);
+    }
+
+    @Test
+    @SmallTest
+    public void testDsds_noActiveSimProfile() {
+        doReturn(mInactiveCard).when(mEsimSlot).getUiccCard();
+        setupDualSim(mEmptySlot, mEsimSlot);
+
+        SimSlotState state = SimSlotState.getCurrentState();
+
+        assertEquals(2, state.numActiveSlots);
+        assertEquals(0, state.numActiveSims);
+        assertEquals(0, state.numActiveEsims);
+    }
+
+    @Test
+    @SmallTest
+    public void testDsds_physicalSimCard() {
+        doReturn(mInactiveCard).when(mEsimSlot).getUiccCard();
+        setupDualSim(mPhysicalSlot, mEsimSlot);
+
+        SimSlotState state = SimSlotState.getCurrentState();
+
+        assertEquals(2, state.numActiveSlots);
+        assertEquals(1, state.numActiveSims);
+        assertEquals(0, state.numActiveEsims);
+    }
+
+    @Test
+    @SmallTest
+    public void testDsds_esimCardWithProfile() {
+        doReturn(mActiveCard).when(mEsimSlot).getUiccCard();
+        setupDualSim(mEmptySlot, mEsimSlot);
+
+        SimSlotState state = SimSlotState.getCurrentState();
+
+        assertEquals(2, state.numActiveSlots);
+        assertEquals(1, state.numActiveSims);
+        assertEquals(1, state.numActiveEsims);
+    }
+
+    @Test
+    @SmallTest
+    public void testDsds_physicalAndEsimCardWithProfile() {
+        doReturn(mActiveCard).when(mEsimSlot).getUiccCard();
+        setupDualSim(mPhysicalSlot, mEsimSlot);
+
+        SimSlotState state = SimSlotState.getCurrentState();
+
+        assertEquals(2, state.numActiveSlots);
+        assertEquals(2, state.numActiveSims);
+        assertEquals(1, state.numActiveEsims);
+    }
+
+    @Test
+    @SmallTest
+    public void testDsds_dualPhysicalSimCards() {
+        setupDualSim(mPhysicalSlot, mPhysicalSlot);
+
+        SimSlotState state = SimSlotState.getCurrentState();
+
+        assertEquals(2, state.numActiveSlots);
+        assertEquals(2, state.numActiveSims);
+        assertEquals(0, state.numActiveEsims);
+    }
+
+    private void setupSingleSim(UiccSlot slot0) {
+        doReturn(new UiccSlot[] {slot0}).when(mUiccController).getUiccSlots();
+        doReturn(slot0).when(mUiccController).getUiccSlot(eq(0));
+    }
+
+    private void setupDualSim(UiccSlot slot0, UiccSlot slot1) {
+        doReturn(new UiccSlot[] {slot0, slot1}).when(mUiccController).getUiccSlots();
+        doReturn(slot0).when(mUiccController).getUiccSlot(eq(0));
+        doReturn(slot1).when(mUiccController).getUiccSlot(eq(1));
+    }
+}
diff --git a/tests/telephonytests/src/com/android/internal/telephony/metrics/TelephonyMetricsTest.java b/tests/telephonytests/src/com/android/internal/telephony/metrics/TelephonyMetricsTest.java
index 46001c7..44c9e84 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/metrics/TelephonyMetricsTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/metrics/TelephonyMetricsTest.java
@@ -16,6 +16,8 @@
 
 package com.android.internal.telephony.metrics;
 
+import static android.telephony.NetworkRegistrationInfo.NR_STATE_NONE;
+import static android.telephony.ServiceState.FREQUENCY_RANGE_UNKNOWN;
 import static android.telephony.ServiceState.RIL_RADIO_TECHNOLOGY_LTE;
 import static android.telephony.ServiceState.ROAMING_TYPE_DOMESTIC;
 
@@ -35,8 +37,9 @@
 import static org.junit.Assert.assertTrue;
 import static org.mockito.Mockito.doReturn;
 
+import android.net.InetAddresses;
 import android.net.LinkAddress;
-import android.net.NetworkUtils;
+import android.net.NetworkCapabilities;
 import android.telephony.ServiceState;
 import android.telephony.TelephonyManager;
 import android.telephony.data.ApnSetting;
@@ -67,6 +70,8 @@
 import com.android.internal.telephony.nano.TelephonyProto.TelephonyEvent;
 import com.android.internal.telephony.nano.TelephonyProto.TelephonyLog;
 import com.android.internal.telephony.nano.TelephonyProto.TelephonyServiceState;
+import com.android.internal.telephony.nano.TelephonyProto.TelephonyServiceState.FrequencyRange;
+import com.android.internal.telephony.nano.TelephonyProto.TelephonyServiceState.NrState;
 import com.android.internal.telephony.nano.TelephonyProto.TelephonyServiceState.RoamingType;
 
 import org.junit.After;
@@ -99,6 +104,7 @@
     public void setUp() throws Exception {
         super.setUp(getClass().getSimpleName());
         mMetrics = new TelephonyMetrics();
+        mMetrics.setContext(mContext);
         mUusInfo = new UUSInfo(1, 2, new byte[]{1, 2});
         doReturn("123").when(mImsCallSession).getCallId();
         mImsReasonInfo = new ImsReasonInfo();
@@ -108,14 +114,13 @@
 
         doReturn(ROAMING_TYPE_DOMESTIC).when(mServiceState).getVoiceRoamingType();
         doReturn(ROAMING_TYPE_DOMESTIC).when(mServiceState).getDataRoamingType();
-        doReturn("voiceshort").when(mServiceState).getVoiceOperatorAlphaShort();
-        doReturn("voicelong").when(mServiceState).getVoiceOperatorAlphaLong();
-        doReturn("datashort").when(mServiceState).getDataOperatorAlphaShort();
-        doReturn("datalong").when(mServiceState).getDataOperatorAlphaLong();
-        doReturn("123456").when(mServiceState).getVoiceOperatorNumeric();
-        doReturn("123456").when(mServiceState).getDataOperatorNumeric();
+        doReturn("short").when(mServiceState).getOperatorAlphaShort();
+        doReturn("long").when(mServiceState).getOperatorAlphaLong();
+        doReturn("123456").when(mServiceState).getOperatorNumeric();
         doReturn(RIL_RADIO_TECHNOLOGY_LTE).when(mServiceState).getRilVoiceRadioTechnology();
         doReturn(RIL_RADIO_TECHNOLOGY_LTE).when(mServiceState).getRilDataRadioTechnology();
+        doReturn(FREQUENCY_RANGE_UNKNOWN).when(mServiceState).getNrFrequencyRange();
+        doReturn(NR_STATE_NONE).when(mServiceState).getNrState();
     }
 
     @After
@@ -280,7 +285,8 @@
                 EmergencyNumber.EMERGENCY_NUMBER_SOURCE_NETWORK_SIGNALING,
                 EmergencyNumber.EMERGENCY_CALL_ROUTING_NORMAL);
 
-        mMetrics.writeEmergencyNumberUpdateEvent(mPhone.getPhoneId(), number);
+        mMetrics.writeEmergencyNumberUpdateEvent(mPhone.getPhoneId(), number,
+                TelephonyManager.INVALID_EMERGENCY_NUMBER_DB_VERSION);
         TelephonyLog log = buildProto();
 
         assertEquals(1, log.events.length);
@@ -298,6 +304,34 @@
                 log.events[0].updatedEmergencyNumber.numberSourcesBitmask);
         assertEquals(EmergencyNumber.EMERGENCY_CALL_ROUTING_NORMAL,
                 log.events[0].updatedEmergencyNumber.routing);
+        assertEquals(TelephonyManager.INVALID_EMERGENCY_NUMBER_DB_VERSION,
+                log.events[0].emergencyNumberDatabaseVersion);
+    }
+
+    // Test write Network Capabilities changed event
+    @Test
+    @SmallTest
+    public void testWriteNetworkCapabilitiesChangedEvent() throws Exception {
+        NetworkCapabilities caps = new NetworkCapabilities();
+        caps.addCapability(NetworkCapabilities.NET_CAPABILITY_TEMPORARILY_NOT_METERED);
+        mMetrics.writeNetworkCapabilitiesChangedEvent(mPhone.getPhoneId(), caps);
+
+        caps.removeCapability(NetworkCapabilities.NET_CAPABILITY_TEMPORARILY_NOT_METERED);
+        mMetrics.writeNetworkCapabilitiesChangedEvent(mPhone.getPhoneId(), caps);
+
+        TelephonyLog log = buildProto();
+
+        assertEquals(2, log.events.length);
+        assertEquals(0, log.callSessions.length);
+        assertEquals(0, log.smsSessions.length);
+
+        assertEquals(mPhone.getPhoneId(), log.events[0].phoneId);
+        assertEquals(TelephonyEvent.Type.NETWORK_CAPABILITIES_CHANGED, log.events[0].type);
+        assertTrue(log.events[0].networkCapabilities.isNetworkUnmetered);
+
+        assertEquals(mPhone.getPhoneId(), log.events[1].phoneId);
+        assertEquals(TelephonyEvent.Type.NETWORK_CAPABILITIES_CHANGED, log.events[1].type);
+        assertFalse(log.events[1].networkCapabilities.isNetworkUnmetered);
     }
 
     // Test write on IMS call start
@@ -470,19 +504,22 @@
     @Test
     @SmallTest
     public void testWriteOnSetupDataCallResponse() throws Exception {
-        DataCallResponse response = new DataCallResponse(
-                5, /* status */
-                6, /* suggestedRetryTime */
-                7, /* cid */
-                8, /* active */
-                ApnSetting.PROTOCOL_IPV4V6, /* protocolType */
-                FAKE_IFNAME, /* ifname */
-                Arrays.asList(new LinkAddress(
-                       NetworkUtils.numericToInetAddress(FAKE_ADDRESS), 0)), /* addresses */
-                Arrays.asList(NetworkUtils.numericToInetAddress(FAKE_DNS)), /* dnses */
-                Arrays.asList(NetworkUtils.numericToInetAddress(FAKE_GATEWAY)), /* gateways */
-                Arrays.asList(NetworkUtils.numericToInetAddress(FAKE_PCSCF_ADDRESS)), /* pcscfs */
-                1440 /* mtu */);
+        DataCallResponse response = new DataCallResponse.Builder()
+                .setCause(5)
+                .setSuggestedRetryTime(6)
+                .setId(7)
+                .setLinkStatus(8)
+                .setProtocolType(ApnSetting.PROTOCOL_IPV4V6)
+                .setInterfaceName(FAKE_IFNAME)
+                .setAddresses(Arrays.asList(
+                        new LinkAddress(InetAddresses.parseNumericAddress(FAKE_ADDRESS), 0)))
+                .setDnsAddresses(Arrays.asList(InetAddresses.parseNumericAddress(FAKE_DNS)))
+                .setGatewayAddresses(Arrays.asList(InetAddresses.parseNumericAddress(FAKE_GATEWAY)))
+                .setPcscfAddresses(
+                        Arrays.asList(InetAddresses.parseNumericAddress(FAKE_PCSCF_ADDRESS)))
+                .setMtuV4(1440)
+                .setMtuV6(1440)
+                .build();
 
         mMetrics.writeOnRilSolicitedResponse(mPhone.getPhoneId(), 1, 2,
                 RIL_REQUEST_SETUP_DATA_CALL, response);
@@ -675,17 +712,21 @@
 
         assertEquals(RoamingType.ROAMING_TYPE_DOMESTIC, state.dataRoamingType);
 
-        assertEquals("voicelong", state.voiceOperator.alphaLong);
+        assertEquals("long", state.voiceOperator.alphaLong);
 
-        assertEquals("voiceshort", state.voiceOperator.alphaShort);
+        assertEquals("short", state.voiceOperator.alphaShort);
 
         assertEquals("123456", state.voiceOperator.numeric);
 
-        assertEquals("datalong", state.dataOperator.alphaLong);
+        assertEquals("long", state.dataOperator.alphaLong);
 
-        assertEquals("datashort", state.dataOperator.alphaShort);
+        assertEquals("short", state.dataOperator.alphaShort);
 
         assertEquals("123456", state.dataOperator.numeric);
+
+        assertEquals(FrequencyRange.FREQUENCY_RANGE_UNKNOWN, state.nrFrequencyRange);
+
+        assertEquals(NrState.NR_STATE_NONE, state.nrState);
     }
 
     // Test reset scenario
@@ -719,15 +760,15 @@
 
         assertEquals(RoamingType.ROAMING_TYPE_DOMESTIC, state.dataRoamingType);
 
-        assertEquals("voicelong", state.voiceOperator.alphaLong);
+        assertEquals("long", state.voiceOperator.alphaLong);
 
-        assertEquals("voiceshort", state.voiceOperator.alphaShort);
+        assertEquals("short", state.voiceOperator.alphaShort);
 
         assertEquals("123456", state.voiceOperator.numeric);
 
-        assertEquals("datalong", state.dataOperator.alphaLong);
+        assertEquals("long", state.dataOperator.alphaLong);
 
-        assertEquals("datashort", state.dataOperator.alphaShort);
+        assertEquals("short", state.dataOperator.alphaShort);
 
         assertEquals("123456", state.dataOperator.numeric);
     }
diff --git a/tests/telephonytests/src/com/android/internal/telephony/metrics/VoiceCallSessionStatsTest.java b/tests/telephonytests/src/com/android/internal/telephony/metrics/VoiceCallSessionStatsTest.java
new file mode 100644
index 0000000..d8db445
--- /dev/null
+++ b/tests/telephonytests/src/com/android/internal/telephony/metrics/VoiceCallSessionStatsTest.java
@@ -0,0 +1,2010 @@
+/*
+ * Copyright (C) 2020 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.metrics;
+
+import static com.android.internal.telephony.TelephonyStatsLog.VOICE_CALL_SESSION__BEARER_AT_END__CALL_BEARER_CS;
+import static com.android.internal.telephony.TelephonyStatsLog.VOICE_CALL_SESSION__BEARER_AT_END__CALL_BEARER_IMS;
+import static com.android.internal.telephony.TelephonyStatsLog.VOICE_CALL_SESSION__DIRECTION__CALL_DIRECTION_MO;
+import static com.android.internal.telephony.TelephonyStatsLog.VOICE_CALL_SESSION__DIRECTION__CALL_DIRECTION_MT;
+import static com.android.internal.telephony.TelephonyStatsLog.VOICE_CALL_SESSION__SETUP_DURATION__CALL_SETUP_DURATION_EXTREMELY_FAST;
+import static com.android.internal.telephony.TelephonyStatsLog.VOICE_CALL_SESSION__SETUP_DURATION__CALL_SETUP_DURATION_FAST;
+import static com.android.internal.telephony.TelephonyStatsLog.VOICE_CALL_SESSION__SETUP_DURATION__CALL_SETUP_DURATION_ULTRA_FAST;
+import static com.android.internal.telephony.TelephonyStatsLog.VOICE_CALL_SESSION__SETUP_DURATION__CALL_SETUP_DURATION_UNKNOWN;
+import static com.android.internal.telephony.TelephonyStatsLog.VOICE_CALL_SESSION__SETUP_DURATION__CALL_SETUP_DURATION_VERY_FAST;
+import static com.android.internal.telephony.TelephonyStatsLog.VOICE_CALL_SESSION__SETUP_DURATION__CALL_SETUP_DURATION_VERY_SLOW;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+
+import android.telephony.DisconnectCause;
+import android.telephony.PreciseDisconnectCause;
+import android.telephony.ServiceState;
+import android.telephony.SubscriptionInfo;
+import android.telephony.TelephonyManager;
+import android.telephony.ims.ImsReasonInfo;
+import android.telephony.ims.ImsStreamMediaProfile;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import com.android.internal.telephony.Call;
+import com.android.internal.telephony.DriverCall;
+import com.android.internal.telephony.GsmCdmaCall;
+import com.android.internal.telephony.GsmCdmaConnection;
+import com.android.internal.telephony.Phone;
+import com.android.internal.telephony.PhoneConstants;
+import com.android.internal.telephony.PhoneFactory;
+import com.android.internal.telephony.ServiceStateTracker;
+import com.android.internal.telephony.TelephonyTest;
+import com.android.internal.telephony.imsphone.ImsPhoneCall;
+import com.android.internal.telephony.imsphone.ImsPhoneConnection;
+import com.android.internal.telephony.nano.PersistAtomsProto.RawVoiceCallRatUsage;
+import com.android.internal.telephony.nano.PersistAtomsProto.VoiceCallSession;
+import com.android.internal.telephony.nano.TelephonyProto.TelephonyCallSession.Event.AudioCodec;
+import com.android.internal.telephony.protobuf.nano.MessageNano;
+import com.android.internal.telephony.uicc.IccCardStatus.CardState;
+import com.android.internal.telephony.uicc.UiccCard;
+import com.android.internal.telephony.uicc.UiccSlot;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Comparator;
+import java.util.List;
+import java.util.concurrent.atomic.AtomicReference;
+
+public class VoiceCallSessionStatsTest extends TelephonyTest {
+    private static final int CARRIER_ID_SLOT_0 = 1;
+    private static final int CARRIER_ID_SLOT_1 = 1187;
+
+    @Mock private Phone mSecondPhone;
+    @Mock private ServiceStateTracker mSecondServiceStateTracker;
+    @Mock private ServiceState mSecondServiceState;
+
+    @Mock private UiccSlot mPhysicalSlot;
+    @Mock private UiccSlot mEsimSlot;
+    @Mock private UiccSlot mEmptySlot;
+    @Mock private UiccCard mInactiveCard;
+    @Mock private UiccCard mActiveCard;
+
+    @Mock private ImsPhoneConnection mImsConnection0;
+    @Mock private ImsPhoneConnection mImsConnection1;
+    @Mock private GsmCdmaConnection mGsmConnection0;
+    @Mock private GsmCdmaConnection mGsmConnection1;
+
+    @Mock private GsmCdmaCall mCsCall0;
+    @Mock private GsmCdmaCall mCsCall1;
+    @Mock private ImsPhoneCall mImsCall0;
+    @Mock private ImsPhoneCall mImsCall1;
+
+    @Mock private SubscriptionInfo mSubInfo0;
+    @Mock private SubscriptionInfo mSubInfo1;
+    private List<SubscriptionInfo> mSubInfos;
+
+    private static class TestableVoiceCallSessionStats extends VoiceCallSessionStats {
+        private long mTimeMillis = 0L;
+
+        TestableVoiceCallSessionStats(int phoneId, Phone phone) {
+            super(phoneId, phone);
+        }
+
+        @Override
+        protected long getTimeMillis() {
+            return mTimeMillis;
+        }
+
+        private void setTimeMillis(long timeMillis) {
+            mTimeMillis = timeMillis;
+        }
+
+        private void incTimeMillis(long timeMillis) {
+            mTimeMillis += timeMillis;
+        }
+    }
+
+    private TestableVoiceCallSessionStats mVoiceCallSessionStats0;
+    private TestableVoiceCallSessionStats mVoiceCallSessionStats1;
+
+    @Before
+    public void setUp() throws Exception {
+        super.setUp(getClass().getSimpleName());
+
+        replaceInstance(PhoneFactory.class, "sPhones", null, new Phone[] {mPhone, mSecondPhone});
+        // mPhone's mSST/mServiceState has been set up by TelephonyTest
+        doReturn(mSecondServiceStateTracker).when(mSecondPhone).getServiceStateTracker();
+        doReturn(mSecondServiceState).when(mSecondServiceStateTracker).getServiceState();
+
+        doReturn(TelephonyManager.NETWORK_TYPE_UNKNOWN).when(mServiceState).getDataNetworkType();
+        doReturn(TelephonyManager.NETWORK_TYPE_UNKNOWN).when(mServiceState).getVoiceNetworkType();
+        doReturn(false).when(mServiceState).getVoiceRoaming();
+        doReturn(TelephonyManager.NETWORK_TYPE_UNKNOWN)
+                .when(mSecondServiceState)
+                .getDataNetworkType();
+        doReturn(TelephonyManager.NETWORK_TYPE_UNKNOWN)
+                .when(mSecondServiceState)
+                .getVoiceNetworkType();
+        doReturn(false).when(mSecondServiceState).getVoiceRoaming();
+
+        doReturn(true).when(mPhysicalSlot).isActive();
+        doReturn(CardState.CARDSTATE_PRESENT).when(mPhysicalSlot).getCardState();
+        doReturn(false).when(mPhysicalSlot).isEuicc();
+        doReturn(true).when(mEmptySlot).isActive();
+        doReturn(CardState.CARDSTATE_ABSENT).when(mEmptySlot).getCardState();
+        doReturn(true).when(mEsimSlot).isActive();
+        doReturn(CardState.CARDSTATE_PRESENT).when(mEsimSlot).getCardState();
+        doReturn(true).when(mEsimSlot).isEuicc();
+        doReturn(0).when(mInactiveCard).getNumApplications();
+        doReturn(4).when(mActiveCard).getNumApplications();
+
+        doReturn(new UiccSlot[] {mPhysicalSlot}).when(mUiccController).getUiccSlots();
+        doReturn(mPhysicalSlot).when(mUiccController).getUiccSlot(eq(0));
+
+        doReturn(0).when(mUiccController).getSlotIdFromPhoneId(0);
+        doReturn(1).when(mUiccController).getSlotIdFromPhoneId(1);
+
+        doReturn(PhoneConstants.PHONE_TYPE_IMS).when(mImsConnection0).getPhoneType();
+        doReturn(false).when(mImsConnection0).isEmergencyCall();
+        doReturn(PhoneConstants.PHONE_TYPE_IMS).when(mImsConnection1).getPhoneType();
+        doReturn(false).when(mImsConnection1).isEmergencyCall();
+        doReturn(PhoneConstants.PHONE_TYPE_GSM).when(mGsmConnection0).getPhoneType();
+        doReturn(false).when(mGsmConnection0).isEmergencyCall();
+        doReturn(PhoneConstants.PHONE_TYPE_GSM).when(mGsmConnection1).getPhoneType();
+        doReturn(false).when(mGsmConnection1).isEmergencyCall();
+
+        mSubInfos = List.of(mSubInfo0, mSubInfo1);
+        doReturn(0).when(mSubInfo0).getSimSlotIndex();
+        doReturn(CARRIER_ID_SLOT_0).when(mSubInfo0).getCarrierId();
+        doReturn(1).when(mSubInfo1).getSimSlotIndex();
+        doReturn(CARRIER_ID_SLOT_1).when(mSubInfo1).getCarrierId();
+
+        mVoiceCallSessionStats0 = new TestableVoiceCallSessionStats(0, mPhone);
+        mVoiceCallSessionStats0.onActiveSubscriptionInfoChanged(mSubInfos);
+        mVoiceCallSessionStats0.onServiceStateChanged(mServiceState);
+        mVoiceCallSessionStats1 = new TestableVoiceCallSessionStats(1, mSecondPhone);
+        mVoiceCallSessionStats1.onActiveSubscriptionInfoChanged(mSubInfos);
+        mVoiceCallSessionStats1.onServiceStateChanged(mSecondServiceState);
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        super.tearDown();
+    }
+
+    @Test
+    @SmallTest
+    public void singleImsCall_moRejected() {
+        doReturn(TelephonyManager.NETWORK_TYPE_LTE).when(mServiceState).getVoiceNetworkType();
+        doReturn(false).when(mImsConnection0).isIncoming();
+        doReturn(2000L).when(mImsConnection0).getCreateTime();
+        doReturn(mImsCall0).when(mImsConnection0).getCall();
+        doReturn(new ArrayList(List.of(mImsConnection0))).when(mImsCall0).getConnections();
+        VoiceCallSession expectedCall =
+                makeSlot0CallProto(
+                        VOICE_CALL_SESSION__BEARER_AT_END__CALL_BEARER_IMS,
+                        VOICE_CALL_SESSION__DIRECTION__CALL_DIRECTION_MO,
+                        TelephonyManager.NETWORK_TYPE_LTE,
+                        ImsReasonInfo.CODE_REMOTE_CALL_DECLINE);
+        expectedCall.setupDuration =
+                VOICE_CALL_SESSION__SETUP_DURATION__CALL_SETUP_DURATION_VERY_FAST;
+        expectedCall.setupFailed = true;
+        expectedCall.codecBitmask = 1L << AudioCodec.AUDIO_CODEC_EVS_SWB;
+        RawVoiceCallRatUsage expectedRatUsage =
+                makeRatUsageProto(
+                        CARRIER_ID_SLOT_0, TelephonyManager.NETWORK_TYPE_LTE, 2000L, 12000L, 1L);
+        final AtomicReference<RawVoiceCallRatUsage[]> ratUsage = setupRatUsageCapture();
+
+        mVoiceCallSessionStats0.setTimeMillis(2000L);
+        doReturn(Call.State.DIALING).when(mImsCall0).getState();
+        doReturn(Call.State.DIALING).when(mImsConnection0).getState();
+        mVoiceCallSessionStats0.onImsDial(mImsConnection0);
+        mVoiceCallSessionStats0.setTimeMillis(2100L);
+        mVoiceCallSessionStats0.onAudioCodecChanged(
+                mImsConnection0, ImsStreamMediaProfile.AUDIO_QUALITY_EVS_SWB);
+        mVoiceCallSessionStats0.setTimeMillis(2200L);
+        doReturn(Call.State.ALERTING).when(mImsCall0).getState();
+        doReturn(Call.State.ALERTING).when(mImsConnection0).getState();
+        mVoiceCallSessionStats0.onCallStateChanged(mImsCall0);
+        mVoiceCallSessionStats0.setTimeMillis(12000L);
+        mVoiceCallSessionStats0.onImsCallTerminated(
+                mImsConnection0, new ImsReasonInfo(ImsReasonInfo.CODE_REMOTE_CALL_DECLINE, 0));
+
+        ArgumentCaptor<VoiceCallSession> callCaptor =
+                ArgumentCaptor.forClass(VoiceCallSession.class);
+        verify(mPersistAtomsStorage, times(1)).addVoiceCallSession(callCaptor.capture());
+        verify(mPersistAtomsStorage, times(1)).addVoiceCallRatUsage(any());
+        verifyNoMoreInteractions(mPersistAtomsStorage);
+        assertProtoEquals(expectedCall, callCaptor.getValue());
+        assertThat(ratUsage.get()).hasLength(1);
+        assertProtoEquals(expectedRatUsage, ratUsage.get()[0]);
+    }
+
+    @Test
+    @SmallTest
+    public void singleImsCall_moFailed() {
+        doReturn(TelephonyManager.NETWORK_TYPE_LTE).when(mServiceState).getVoiceNetworkType();
+        doReturn(false).when(mImsConnection0).isIncoming();
+        doReturn(2000L).when(mImsConnection0).getCreateTime();
+        doReturn(mImsCall0).when(mImsConnection0).getCall();
+        doReturn(new ArrayList(List.of(mImsConnection0))).when(mImsCall0).getConnections();
+        VoiceCallSession expectedCall =
+                makeSlot0CallProto(
+                        VOICE_CALL_SESSION__BEARER_AT_END__CALL_BEARER_IMS,
+                        VOICE_CALL_SESSION__DIRECTION__CALL_DIRECTION_MO,
+                        TelephonyManager.NETWORK_TYPE_LTE,
+                        ImsReasonInfo.CODE_SIP_FORBIDDEN);
+        expectedCall.setupFailed = true;
+        RawVoiceCallRatUsage expectedRatUsage =
+                makeRatUsageProto(
+                        CARRIER_ID_SLOT_0, TelephonyManager.NETWORK_TYPE_LTE, 2000L, 2200L, 1L);
+        final AtomicReference<RawVoiceCallRatUsage[]> ratUsage = setupRatUsageCapture();
+
+        mVoiceCallSessionStats0.setTimeMillis(2000L);
+        doReturn(Call.State.DIALING).when(mImsCall0).getState();
+        doReturn(Call.State.DIALING).when(mImsConnection0).getState();
+        mVoiceCallSessionStats0.onImsDial(mImsConnection0);
+        mVoiceCallSessionStats0.setTimeMillis(2200L);
+        mVoiceCallSessionStats0.onImsCallTerminated(
+                mImsConnection0, new ImsReasonInfo(ImsReasonInfo.CODE_SIP_FORBIDDEN, 0));
+
+        ArgumentCaptor<VoiceCallSession> callCaptor =
+                ArgumentCaptor.forClass(VoiceCallSession.class);
+        verify(mPersistAtomsStorage, times(1)).addVoiceCallSession(callCaptor.capture());
+        verify(mPersistAtomsStorage, times(1)).addVoiceCallRatUsage(any());
+        verifyNoMoreInteractions(mPersistAtomsStorage);
+        assertProtoEquals(expectedCall, callCaptor.getValue());
+        assertThat(ratUsage.get()).hasLength(1);
+        assertProtoEquals(expectedRatUsage, ratUsage.get()[0]);
+    }
+
+    @Test
+    @SmallTest
+    public void singleImsCall_moAccepted() {
+        doReturn(TelephonyManager.NETWORK_TYPE_LTE).when(mServiceState).getVoiceNetworkType();
+        doReturn(false).when(mImsConnection0).isIncoming();
+        doReturn(2000L).when(mImsConnection0).getCreateTime();
+        doReturn(mImsCall0).when(mImsConnection0).getCall();
+        doReturn(new ArrayList(List.of(mImsConnection0))).when(mImsCall0).getConnections();
+        VoiceCallSession expectedCall =
+                makeSlot0CallProto(
+                        VOICE_CALL_SESSION__BEARER_AT_END__CALL_BEARER_IMS,
+                        VOICE_CALL_SESSION__DIRECTION__CALL_DIRECTION_MO,
+                        TelephonyManager.NETWORK_TYPE_LTE,
+                        ImsReasonInfo.CODE_USER_TERMINATED_BY_REMOTE);
+        expectedCall.setupDuration =
+                VOICE_CALL_SESSION__SETUP_DURATION__CALL_SETUP_DURATION_VERY_FAST;
+        expectedCall.setupFailed = false;
+        expectedCall.codecBitmask = 1L << AudioCodec.AUDIO_CODEC_EVS_SWB;
+        expectedCall.disconnectExtraMessage = "normal call clearing";
+        RawVoiceCallRatUsage expectedRatUsage =
+                makeRatUsageProto(
+                        CARRIER_ID_SLOT_0, TelephonyManager.NETWORK_TYPE_LTE, 2000L, 100000L, 1L);
+        final AtomicReference<RawVoiceCallRatUsage[]> ratUsage = setupRatUsageCapture();
+
+        mVoiceCallSessionStats0.setTimeMillis(2000L);
+        doReturn(Call.State.DIALING).when(mImsCall0).getState();
+        doReturn(Call.State.DIALING).when(mImsConnection0).getState();
+        mVoiceCallSessionStats0.onImsDial(mImsConnection0);
+        mVoiceCallSessionStats0.setTimeMillis(2100L);
+        mVoiceCallSessionStats0.onAudioCodecChanged(
+                mImsConnection0, ImsStreamMediaProfile.AUDIO_QUALITY_EVS_SWB);
+        mVoiceCallSessionStats0.setTimeMillis(2200L);
+        doReturn(Call.State.ALERTING).when(mImsCall0).getState();
+        doReturn(Call.State.ALERTING).when(mImsConnection0).getState();
+        mVoiceCallSessionStats0.onCallStateChanged(mImsCall0);
+        mVoiceCallSessionStats0.setTimeMillis(12000L);
+        doReturn(Call.State.ACTIVE).when(mImsCall0).getState();
+        doReturn(Call.State.ACTIVE).when(mImsConnection0).getState();
+        mVoiceCallSessionStats0.onCallStateChanged(mImsCall0);
+        mVoiceCallSessionStats0.setTimeMillis(100000L);
+        mVoiceCallSessionStats0.onImsCallTerminated(
+                mImsConnection0,
+                new ImsReasonInfo(
+                        ImsReasonInfo.CODE_USER_TERMINATED_BY_REMOTE, 0, "normal call clearing"));
+
+        ArgumentCaptor<VoiceCallSession> callCaptor =
+                ArgumentCaptor.forClass(VoiceCallSession.class);
+        verify(mPersistAtomsStorage, times(1)).addVoiceCallSession(callCaptor.capture());
+        verify(mPersistAtomsStorage, times(1)).addVoiceCallRatUsage(any());
+        verifyNoMoreInteractions(mPersistAtomsStorage);
+        assertProtoEquals(expectedCall, callCaptor.getValue());
+        assertThat(ratUsage.get()).hasLength(1);
+        assertProtoEquals(expectedRatUsage, ratUsage.get()[0]);
+    }
+
+    @Test
+    @SmallTest
+    public void singleImsCall_mtRejected() {
+        doReturn(TelephonyManager.NETWORK_TYPE_LTE).when(mServiceState).getVoiceNetworkType();
+        doReturn(true).when(mImsConnection0).isIncoming();
+        doReturn(2000L).when(mImsConnection0).getCreateTime();
+        doReturn(mImsCall0).when(mImsConnection0).getCall();
+        doReturn(new ArrayList(List.of(mImsConnection0))).when(mImsCall0).getConnections();
+        VoiceCallSession expectedCall =
+                makeSlot0CallProto(
+                        VOICE_CALL_SESSION__BEARER_AT_END__CALL_BEARER_IMS,
+                        VOICE_CALL_SESSION__DIRECTION__CALL_DIRECTION_MT,
+                        TelephonyManager.NETWORK_TYPE_LTE,
+                        ImsReasonInfo.CODE_LOCAL_CALL_DECLINE);
+        expectedCall.setupFailed = true;
+        expectedCall.codecBitmask = 1L << AudioCodec.AUDIO_CODEC_AMR;
+        RawVoiceCallRatUsage expectedRatUsage =
+                makeRatUsageProto(
+                        CARRIER_ID_SLOT_0, TelephonyManager.NETWORK_TYPE_LTE, 2000L, 8000L, 1L);
+        final AtomicReference<RawVoiceCallRatUsage[]> ratUsage = setupRatUsageCapture();
+
+        mVoiceCallSessionStats0.setTimeMillis(2000L);
+        doReturn(Call.State.INCOMING).when(mImsCall0).getState();
+        doReturn(Call.State.INCOMING).when(mImsConnection0).getState();
+        mVoiceCallSessionStats0.onImsCallReceived(mImsConnection0);
+        mVoiceCallSessionStats0.setTimeMillis(2100L);
+        mVoiceCallSessionStats0.onAudioCodecChanged(
+                mImsConnection0, ImsStreamMediaProfile.AUDIO_QUALITY_AMR);
+        mVoiceCallSessionStats0.setTimeMillis(8000L);
+        mVoiceCallSessionStats0.onImsCallTerminated(
+                mImsConnection0, new ImsReasonInfo(ImsReasonInfo.CODE_LOCAL_CALL_DECLINE, 0));
+
+        ArgumentCaptor<VoiceCallSession> callCaptor =
+                ArgumentCaptor.forClass(VoiceCallSession.class);
+        verify(mPersistAtomsStorage, times(1)).addVoiceCallSession(callCaptor.capture());
+        verify(mPersistAtomsStorage, times(1)).addVoiceCallRatUsage(any());
+        verifyNoMoreInteractions(mPersistAtomsStorage);
+        assertProtoEquals(expectedCall, callCaptor.getValue());
+        assertThat(ratUsage.get()).hasLength(1);
+        assertProtoEquals(expectedRatUsage, ratUsage.get()[0]);
+    }
+
+    @Test
+    @SmallTest
+    public void singleImsCall_mtAccepted() {
+        doReturn(TelephonyManager.NETWORK_TYPE_LTE).when(mServiceState).getVoiceNetworkType();
+        doReturn(true).when(mImsConnection0).isIncoming();
+        doReturn(2000L).when(mImsConnection0).getCreateTime();
+        doReturn(mImsCall0).when(mImsConnection0).getCall();
+        doReturn(new ArrayList(List.of(mImsConnection0))).when(mImsCall0).getConnections();
+        VoiceCallSession expectedCall =
+                makeSlot0CallProto(
+                        VOICE_CALL_SESSION__BEARER_AT_END__CALL_BEARER_IMS,
+                        VOICE_CALL_SESSION__DIRECTION__CALL_DIRECTION_MT,
+                        TelephonyManager.NETWORK_TYPE_LTE,
+                        ImsReasonInfo.CODE_USER_TERMINATED);
+        expectedCall.setupDuration =
+                VOICE_CALL_SESSION__SETUP_DURATION__CALL_SETUP_DURATION_ULTRA_FAST;
+        expectedCall.setupFailed = false;
+        expectedCall.codecBitmask = 1L << AudioCodec.AUDIO_CODEC_AMR;
+        RawVoiceCallRatUsage expectedRatUsage =
+                makeRatUsageProto(
+                        CARRIER_ID_SLOT_0, TelephonyManager.NETWORK_TYPE_LTE, 2000L, 12000L, 1L);
+        final AtomicReference<RawVoiceCallRatUsage[]> ratUsage = setupRatUsageCapture();
+
+        mVoiceCallSessionStats0.setTimeMillis(2000L);
+        doReturn(Call.State.INCOMING).when(mImsCall0).getState();
+        doReturn(Call.State.INCOMING).when(mImsConnection0).getState();
+        mVoiceCallSessionStats0.onImsCallReceived(mImsConnection0);
+        mVoiceCallSessionStats0.setTimeMillis(2100L);
+        mVoiceCallSessionStats0.onAudioCodecChanged(
+                mImsConnection0, ImsStreamMediaProfile.AUDIO_QUALITY_AMR);
+        mVoiceCallSessionStats0.setTimeMillis(2200L);
+        mVoiceCallSessionStats0.onImsAcceptCall(List.of(mImsConnection0));
+        mVoiceCallSessionStats0.setTimeMillis(2280L);
+        doReturn(Call.State.ACTIVE).when(mImsCall0).getState();
+        doReturn(Call.State.ACTIVE).when(mImsConnection0).getState();
+        mVoiceCallSessionStats0.onCallStateChanged(mImsCall0);
+        mVoiceCallSessionStats0.setTimeMillis(12000L);
+        mVoiceCallSessionStats0.onImsCallTerminated(
+                mImsConnection0, new ImsReasonInfo(ImsReasonInfo.CODE_USER_TERMINATED, 0));
+
+        ArgumentCaptor<VoiceCallSession> callCaptor =
+                ArgumentCaptor.forClass(VoiceCallSession.class);
+        verify(mPersistAtomsStorage, times(1)).addVoiceCallSession(callCaptor.capture());
+        verify(mPersistAtomsStorage, times(1)).addVoiceCallRatUsage(any());
+        verifyNoMoreInteractions(mPersistAtomsStorage);
+        assertProtoEquals(expectedCall, callCaptor.getValue());
+        assertThat(ratUsage.get()).hasLength(1);
+        assertProtoEquals(expectedRatUsage, ratUsage.get()[0]);
+    }
+
+    @Test
+    @SmallTest
+    public void singleImsCall_dsdsModeSingleSim() {
+        doReturn(mInactiveCard).when(mEsimSlot).getUiccCard();
+        doReturn(new UiccSlot[] {mPhysicalSlot, mEsimSlot}).when(mUiccController).getUiccSlots();
+        doReturn(mEsimSlot).when(mUiccController).getUiccSlot(eq(1));
+        doReturn(TelephonyManager.NETWORK_TYPE_LTE).when(mServiceState).getVoiceNetworkType();
+        doReturn(true).when(mImsConnection0).isIncoming();
+        doReturn(2000L).when(mImsConnection0).getCreateTime();
+        doReturn(mImsCall0).when(mImsConnection0).getCall();
+        doReturn(new ArrayList(List.of(mImsConnection0))).when(mImsCall0).getConnections();
+        VoiceCallSession expectedCall =
+                makeSlot0CallProto(
+                        VOICE_CALL_SESSION__BEARER_AT_END__CALL_BEARER_IMS,
+                        VOICE_CALL_SESSION__DIRECTION__CALL_DIRECTION_MT,
+                        TelephonyManager.NETWORK_TYPE_LTE,
+                        ImsReasonInfo.CODE_LOCAL_CALL_DECLINE);
+        expectedCall.setupFailed = true;
+        expectedCall.codecBitmask = 1L << AudioCodec.AUDIO_CODEC_AMR;
+        expectedCall.isMultiSim = false; // DSDS with one active SIM profile should not count
+
+        mVoiceCallSessionStats0.setTimeMillis(2000L);
+        doReturn(Call.State.INCOMING).when(mImsCall0).getState();
+        doReturn(Call.State.INCOMING).when(mImsConnection0).getState();
+        mVoiceCallSessionStats0.onImsCallReceived(mImsConnection0);
+        mVoiceCallSessionStats0.setTimeMillis(2100L);
+        mVoiceCallSessionStats0.onAudioCodecChanged(
+                mImsConnection0, ImsStreamMediaProfile.AUDIO_QUALITY_AMR);
+        mVoiceCallSessionStats0.setTimeMillis(8000L);
+        mVoiceCallSessionStats0.onImsCallTerminated(
+                mImsConnection0, new ImsReasonInfo(ImsReasonInfo.CODE_LOCAL_CALL_DECLINE, 0));
+
+        ArgumentCaptor<VoiceCallSession> callCaptor =
+                ArgumentCaptor.forClass(VoiceCallSession.class);
+        verify(mPersistAtomsStorage, times(1)).addVoiceCallSession(callCaptor.capture());
+        verify(mPersistAtomsStorage, times(1)).addVoiceCallRatUsage(any());
+        verifyNoMoreInteractions(mPersistAtomsStorage);
+        assertProtoEquals(expectedCall, callCaptor.getValue());
+    }
+
+    @Test
+    @SmallTest
+    public void singleImsCall_dsdsMode() {
+        doReturn(mActiveCard).when(mEsimSlot).getUiccCard();
+        doReturn(new UiccSlot[] {mPhysicalSlot, mEsimSlot}).when(mUiccController).getUiccSlots();
+        doReturn(mEsimSlot).when(mUiccController).getUiccSlot(eq(1));
+        doReturn(TelephonyManager.NETWORK_TYPE_LTE).when(mServiceState).getVoiceNetworkType();
+        doReturn(true).when(mImsConnection0).isIncoming();
+        doReturn(2000L).when(mImsConnection0).getCreateTime();
+        doReturn(mImsCall0).when(mImsConnection0).getCall();
+        doReturn(new ArrayList(List.of(mImsConnection0))).when(mImsCall0).getConnections();
+        VoiceCallSession expectedCall =
+                makeSlot0CallProto(
+                        VOICE_CALL_SESSION__BEARER_AT_END__CALL_BEARER_IMS,
+                        VOICE_CALL_SESSION__DIRECTION__CALL_DIRECTION_MT,
+                        TelephonyManager.NETWORK_TYPE_LTE,
+                        ImsReasonInfo.CODE_LOCAL_CALL_DECLINE);
+        expectedCall.setupFailed = true;
+        expectedCall.codecBitmask = 1L << AudioCodec.AUDIO_CODEC_AMR;
+        expectedCall.isMultiSim = true;
+
+        mVoiceCallSessionStats0.setTimeMillis(2000L);
+        doReturn(Call.State.INCOMING).when(mImsCall0).getState();
+        doReturn(Call.State.INCOMING).when(mImsConnection0).getState();
+        mVoiceCallSessionStats0.onImsCallReceived(mImsConnection0);
+        mVoiceCallSessionStats0.setTimeMillis(2100L);
+        mVoiceCallSessionStats0.onAudioCodecChanged(
+                mImsConnection0, ImsStreamMediaProfile.AUDIO_QUALITY_AMR);
+        mVoiceCallSessionStats0.setTimeMillis(8000L);
+        mVoiceCallSessionStats0.onImsCallTerminated(
+                mImsConnection0, new ImsReasonInfo(ImsReasonInfo.CODE_LOCAL_CALL_DECLINE, 0));
+
+        ArgumentCaptor<VoiceCallSession> callCaptor =
+                ArgumentCaptor.forClass(VoiceCallSession.class);
+        verify(mPersistAtomsStorage, times(1)).addVoiceCallSession(callCaptor.capture());
+        verify(mPersistAtomsStorage, times(1)).addVoiceCallRatUsage(any());
+        verifyNoMoreInteractions(mPersistAtomsStorage);
+        assertProtoEquals(expectedCall, callCaptor.getValue());
+    }
+
+    @Test
+    @SmallTest
+    public void singleImsCall_esim() {
+        doReturn(mActiveCard).when(mEsimSlot).getUiccCard();
+        doReturn(new UiccSlot[] {mPhysicalSlot, mEsimSlot}).when(mUiccController).getUiccSlots();
+        doReturn(mEsimSlot).when(mUiccController).getUiccSlot(eq(1));
+        doReturn(TelephonyManager.NETWORK_TYPE_LTE).when(mSecondServiceState).getVoiceNetworkType();
+        doReturn(true).when(mImsConnection1).isIncoming();
+        doReturn(2000L).when(mImsConnection1).getCreateTime();
+        doReturn(mImsCall1).when(mImsConnection1).getCall();
+        doReturn(new ArrayList(List.of(mImsConnection1))).when(mImsCall1).getConnections();
+        VoiceCallSession expectedCall =
+                makeSlot1CallProto(
+                        VOICE_CALL_SESSION__BEARER_AT_END__CALL_BEARER_IMS,
+                        VOICE_CALL_SESSION__DIRECTION__CALL_DIRECTION_MT,
+                        TelephonyManager.NETWORK_TYPE_LTE,
+                        ImsReasonInfo.CODE_LOCAL_CALL_DECLINE);
+        expectedCall.setupFailed = true;
+        expectedCall.codecBitmask = 1L << AudioCodec.AUDIO_CODEC_AMR;
+
+        mVoiceCallSessionStats1.setTimeMillis(2000L);
+        doReturn(Call.State.INCOMING).when(mImsCall1).getState();
+        doReturn(Call.State.INCOMING).when(mImsConnection1).getState();
+        mVoiceCallSessionStats1.onImsCallReceived(mImsConnection1);
+        mVoiceCallSessionStats1.setTimeMillis(2100L);
+        mVoiceCallSessionStats1.onAudioCodecChanged(
+                mImsConnection1, ImsStreamMediaProfile.AUDIO_QUALITY_AMR);
+        mVoiceCallSessionStats1.setTimeMillis(8000L);
+        mVoiceCallSessionStats1.onImsCallTerminated(
+                mImsConnection1, new ImsReasonInfo(ImsReasonInfo.CODE_LOCAL_CALL_DECLINE, 0));
+
+        ArgumentCaptor<VoiceCallSession> callCaptor =
+                ArgumentCaptor.forClass(VoiceCallSession.class);
+        verify(mPersistAtomsStorage, times(1)).addVoiceCallSession(callCaptor.capture());
+        verify(mPersistAtomsStorage, times(1)).addVoiceCallRatUsage(any());
+        verifyNoMoreInteractions(mPersistAtomsStorage);
+        assertProtoEquals(expectedCall, callCaptor.getValue());
+    }
+
+    @Test
+    @SmallTest
+    public void singleImsCall_emergency() {
+        doReturn(TelephonyManager.NETWORK_TYPE_LTE).when(mServiceState).getVoiceNetworkType();
+        doReturn(true).when(mImsConnection0).isIncoming();
+        doReturn(2000L).when(mImsConnection0).getCreateTime();
+        doReturn(true).when(mImsConnection0).isEmergencyCall();
+        doReturn(mImsCall0).when(mImsConnection0).getCall();
+        doReturn(new ArrayList(List.of(mImsConnection0))).when(mImsCall0).getConnections();
+        VoiceCallSession expectedCall =
+                makeSlot0CallProto(
+                        VOICE_CALL_SESSION__BEARER_AT_END__CALL_BEARER_IMS,
+                        VOICE_CALL_SESSION__DIRECTION__CALL_DIRECTION_MT,
+                        TelephonyManager.NETWORK_TYPE_LTE,
+                        ImsReasonInfo.CODE_LOCAL_CALL_DECLINE);
+        expectedCall.setupFailed = true;
+        expectedCall.codecBitmask = 1L << AudioCodec.AUDIO_CODEC_AMR;
+        expectedCall.isEmergency = true;
+
+        mVoiceCallSessionStats0.setTimeMillis(2000L);
+        doReturn(Call.State.INCOMING).when(mImsCall0).getState();
+        doReturn(Call.State.INCOMING).when(mImsConnection0).getState();
+        mVoiceCallSessionStats0.onImsCallReceived(mImsConnection0);
+        mVoiceCallSessionStats0.setTimeMillis(2100L);
+        mVoiceCallSessionStats0.onAudioCodecChanged(
+                mImsConnection0, ImsStreamMediaProfile.AUDIO_QUALITY_AMR);
+        mVoiceCallSessionStats0.setTimeMillis(8000L);
+        mVoiceCallSessionStats0.onImsCallTerminated(
+                mImsConnection0, new ImsReasonInfo(ImsReasonInfo.CODE_LOCAL_CALL_DECLINE, 0));
+
+        ArgumentCaptor<VoiceCallSession> callCaptor =
+                ArgumentCaptor.forClass(VoiceCallSession.class);
+        verify(mPersistAtomsStorage, times(1)).addVoiceCallSession(callCaptor.capture());
+        verify(mPersistAtomsStorage, times(1)).addVoiceCallRatUsage(any());
+        verifyNoMoreInteractions(mPersistAtomsStorage);
+        assertProtoEquals(expectedCall, callCaptor.getValue());
+    }
+
+    @Test
+    @SmallTest
+    public void singleImsCall_roaming() {
+        doReturn(TelephonyManager.NETWORK_TYPE_LTE).when(mServiceState).getVoiceNetworkType();
+        doReturn(true).when(mServiceState).getVoiceRoaming();
+        doReturn(true).when(mImsConnection0).isIncoming();
+        doReturn(2000L).when(mImsConnection0).getCreateTime();
+        doReturn(mImsCall0).when(mImsConnection0).getCall();
+        doReturn(new ArrayList(List.of(mImsConnection0))).when(mImsCall0).getConnections();
+        VoiceCallSession expectedCall =
+                makeSlot0CallProto(
+                        VOICE_CALL_SESSION__BEARER_AT_END__CALL_BEARER_IMS,
+                        VOICE_CALL_SESSION__DIRECTION__CALL_DIRECTION_MT,
+                        TelephonyManager.NETWORK_TYPE_LTE,
+                        ImsReasonInfo.CODE_LOCAL_CALL_DECLINE);
+        expectedCall.setupFailed = true;
+        expectedCall.codecBitmask = 1L << AudioCodec.AUDIO_CODEC_AMR;
+        expectedCall.isRoaming = true;
+
+        mVoiceCallSessionStats0.setTimeMillis(2000L);
+        doReturn(Call.State.INCOMING).when(mImsCall0).getState();
+        doReturn(Call.State.INCOMING).when(mImsConnection0).getState();
+        mVoiceCallSessionStats0.onImsCallReceived(mImsConnection0);
+        mVoiceCallSessionStats0.setTimeMillis(2100L);
+        mVoiceCallSessionStats0.onAudioCodecChanged(
+                mImsConnection0, ImsStreamMediaProfile.AUDIO_QUALITY_AMR);
+        mVoiceCallSessionStats0.setTimeMillis(8000L);
+        mVoiceCallSessionStats0.onImsCallTerminated(
+                mImsConnection0, new ImsReasonInfo(ImsReasonInfo.CODE_LOCAL_CALL_DECLINE, 0));
+
+        ArgumentCaptor<VoiceCallSession> callCaptor =
+                ArgumentCaptor.forClass(VoiceCallSession.class);
+        verify(mPersistAtomsStorage, times(1)).addVoiceCallSession(callCaptor.capture());
+        verify(mPersistAtomsStorage, times(1)).addVoiceCallRatUsage(any());
+        verifyNoMoreInteractions(mPersistAtomsStorage);
+        assertProtoEquals(expectedCall, callCaptor.getValue());
+    }
+
+    @Test
+    @SmallTest
+    public void singleImsCall_codecSwitch() {
+        doReturn(TelephonyManager.NETWORK_TYPE_LTE).when(mServiceState).getVoiceNetworkType();
+        doReturn(true).when(mImsConnection0).isIncoming();
+        doReturn(2000L).when(mImsConnection0).getCreateTime();
+        doReturn(mImsCall0).when(mImsConnection0).getCall();
+        doReturn(new ArrayList(List.of(mImsConnection0))).when(mImsCall0).getConnections();
+        VoiceCallSession expectedCall =
+                makeSlot0CallProto(
+                        VOICE_CALL_SESSION__BEARER_AT_END__CALL_BEARER_IMS,
+                        VOICE_CALL_SESSION__DIRECTION__CALL_DIRECTION_MT,
+                        TelephonyManager.NETWORK_TYPE_LTE,
+                        ImsReasonInfo.CODE_USER_TERMINATED);
+        expectedCall.setupDuration =
+                VOICE_CALL_SESSION__SETUP_DURATION__CALL_SETUP_DURATION_ULTRA_FAST;
+        expectedCall.setupFailed = false;
+        expectedCall.codecBitmask =
+                1L << AudioCodec.AUDIO_CODEC_AMR | 1L << AudioCodec.AUDIO_CODEC_EVS_SWB;
+
+        mVoiceCallSessionStats0.setTimeMillis(2000L);
+        doReturn(Call.State.INCOMING).when(mImsCall0).getState();
+        doReturn(Call.State.INCOMING).when(mImsConnection0).getState();
+        mVoiceCallSessionStats0.onImsCallReceived(mImsConnection0);
+        mVoiceCallSessionStats0.setTimeMillis(2100L);
+        mVoiceCallSessionStats0.onAudioCodecChanged(
+                mImsConnection0, ImsStreamMediaProfile.AUDIO_QUALITY_AMR);
+        mVoiceCallSessionStats0.setTimeMillis(2200L);
+        mVoiceCallSessionStats0.onImsAcceptCall(List.of(mImsConnection0));
+        mVoiceCallSessionStats0.setTimeMillis(2280L);
+        doReturn(Call.State.ACTIVE).when(mImsCall0).getState();
+        doReturn(Call.State.ACTIVE).when(mImsConnection0).getState();
+        mVoiceCallSessionStats0.onCallStateChanged(mImsCall0);
+        mVoiceCallSessionStats0.setTimeMillis(4000L);
+        mVoiceCallSessionStats0.onAudioCodecChanged(
+                mImsConnection0, ImsStreamMediaProfile.AUDIO_QUALITY_EVS_SWB);
+        mVoiceCallSessionStats0.setTimeMillis(8000L);
+        mVoiceCallSessionStats0.onImsCallTerminated(
+                mImsConnection0, new ImsReasonInfo(ImsReasonInfo.CODE_USER_TERMINATED, 0));
+
+        ArgumentCaptor<VoiceCallSession> callCaptor =
+                ArgumentCaptor.forClass(VoiceCallSession.class);
+        verify(mPersistAtomsStorage, times(1)).addVoiceCallSession(callCaptor.capture());
+        verify(mPersistAtomsStorage, times(1)).addVoiceCallRatUsage(any());
+        verifyNoMoreInteractions(mPersistAtomsStorage);
+        assertProtoEquals(expectedCall, callCaptor.getValue());
+    }
+
+    @Test
+    @SmallTest
+    public void singleImsCall_ratSwitch() {
+        doReturn(TelephonyManager.NETWORK_TYPE_LTE).when(mServiceState).getVoiceNetworkType();
+        doReturn(true).when(mImsConnection0).isIncoming();
+        doReturn(2000L).when(mImsConnection0).getCreateTime();
+        doReturn(mImsCall0).when(mImsConnection0).getCall();
+        doReturn(new ArrayList(List.of(mImsConnection0))).when(mImsCall0).getConnections();
+        VoiceCallSession expectedCall =
+                makeSlot0CallProto(
+                        VOICE_CALL_SESSION__BEARER_AT_END__CALL_BEARER_IMS,
+                        VOICE_CALL_SESSION__DIRECTION__CALL_DIRECTION_MT,
+                        TelephonyManager.NETWORK_TYPE_LTE,
+                        ImsReasonInfo.CODE_USER_TERMINATED);
+        expectedCall.setupDuration =
+                VOICE_CALL_SESSION__SETUP_DURATION__CALL_SETUP_DURATION_ULTRA_FAST;
+        expectedCall.setupFailed = false;
+        expectedCall.codecBitmask = 1L << AudioCodec.AUDIO_CODEC_AMR;
+        expectedCall.ratSwitchCount = 2L;
+        expectedCall.ratAtEnd = TelephonyManager.NETWORK_TYPE_UMTS;
+        RawVoiceCallRatUsage expectedRatUsageLte =
+                makeRatUsageProto(
+                        CARRIER_ID_SLOT_0, TelephonyManager.NETWORK_TYPE_LTE, 2000L, 4000L, 1L);
+        RawVoiceCallRatUsage expectedRatUsageHspa =
+                makeRatUsageProto(
+                        CARRIER_ID_SLOT_0, TelephonyManager.NETWORK_TYPE_HSPA, 4000L, 6000L, 1L);
+        RawVoiceCallRatUsage expectedRatUsageUmts =
+                makeRatUsageProto(
+                        CARRIER_ID_SLOT_0, TelephonyManager.NETWORK_TYPE_UMTS, 6000L, 8000L, 1L);
+        final AtomicReference<RawVoiceCallRatUsage[]> ratUsage = setupRatUsageCapture();
+
+        mVoiceCallSessionStats0.setTimeMillis(2000L);
+        doReturn(Call.State.INCOMING).when(mImsCall0).getState();
+        doReturn(Call.State.INCOMING).when(mImsConnection0).getState();
+        mVoiceCallSessionStats0.onImsCallReceived(mImsConnection0);
+        mVoiceCallSessionStats0.setTimeMillis(2100L);
+        mVoiceCallSessionStats0.onAudioCodecChanged(
+                mImsConnection0, ImsStreamMediaProfile.AUDIO_QUALITY_AMR);
+        mVoiceCallSessionStats0.setTimeMillis(2200L);
+        mVoiceCallSessionStats0.onImsAcceptCall(List.of(mImsConnection0));
+        mVoiceCallSessionStats0.setTimeMillis(2280L);
+        doReturn(Call.State.ACTIVE).when(mImsCall0).getState();
+        doReturn(Call.State.ACTIVE).when(mImsConnection0).getState();
+        mVoiceCallSessionStats0.onCallStateChanged(mImsCall0);
+        mVoiceCallSessionStats0.setTimeMillis(4000L);
+        doReturn(TelephonyManager.NETWORK_TYPE_HSPA).when(mServiceState).getVoiceNetworkType();
+        mVoiceCallSessionStats0.onServiceStateChanged(mServiceState);
+        mVoiceCallSessionStats0.setTimeMillis(6000L);
+        doReturn(TelephonyManager.NETWORK_TYPE_UMTS).when(mServiceState).getVoiceNetworkType();
+        mVoiceCallSessionStats0.onServiceStateChanged(mServiceState);
+        mVoiceCallSessionStats0.setTimeMillis(8000L);
+        mVoiceCallSessionStats0.onImsCallTerminated(
+                mImsConnection0, new ImsReasonInfo(ImsReasonInfo.CODE_USER_TERMINATED, 0));
+
+        ArgumentCaptor<VoiceCallSession> callCaptor =
+                ArgumentCaptor.forClass(VoiceCallSession.class);
+        verify(mPersistAtomsStorage, times(1)).addVoiceCallSession(callCaptor.capture());
+        verify(mPersistAtomsStorage, times(1)).addVoiceCallRatUsage(any());
+        verifyNoMoreInteractions(mPersistAtomsStorage);
+        assertProtoEquals(expectedCall, callCaptor.getValue());
+        assertSortedProtoArrayEquals(
+                new RawVoiceCallRatUsage[] {
+                    expectedRatUsageLte, expectedRatUsageHspa, expectedRatUsageUmts
+                },
+                ratUsage.get());
+    }
+
+    @Test
+    @SmallTest
+    public void singleImsCall_rttOnDial() {
+        doReturn(TelephonyManager.NETWORK_TYPE_LTE).when(mServiceState).getVoiceNetworkType();
+        doReturn(true).when(mImsConnection0).isIncoming();
+        doReturn(2000L).when(mImsConnection0).getCreateTime();
+        doReturn(true).when(mImsConnection0).hasRttTextStream();
+        doReturn(mImsCall0).when(mImsConnection0).getCall();
+        doReturn(new ArrayList(List.of(mImsConnection0))).when(mImsCall0).getConnections();
+        VoiceCallSession expectedCall =
+                makeSlot0CallProto(
+                        VOICE_CALL_SESSION__BEARER_AT_END__CALL_BEARER_IMS,
+                        VOICE_CALL_SESSION__DIRECTION__CALL_DIRECTION_MT,
+                        TelephonyManager.NETWORK_TYPE_LTE,
+                        ImsReasonInfo.CODE_LOCAL_CALL_DECLINE);
+        expectedCall.setupFailed = true;
+        expectedCall.codecBitmask = 1L << AudioCodec.AUDIO_CODEC_AMR;
+        expectedCall.rttEnabled = true;
+
+        mVoiceCallSessionStats0.setTimeMillis(2000L);
+        doReturn(Call.State.INCOMING).when(mImsCall0).getState();
+        doReturn(Call.State.INCOMING).when(mImsConnection0).getState();
+        mVoiceCallSessionStats0.onImsCallReceived(mImsConnection0);
+        mVoiceCallSessionStats0.setTimeMillis(2100L);
+        mVoiceCallSessionStats0.onAudioCodecChanged(
+                mImsConnection0, ImsStreamMediaProfile.AUDIO_QUALITY_AMR);
+        mVoiceCallSessionStats0.setTimeMillis(8000L);
+        mVoiceCallSessionStats0.onImsCallTerminated(
+                mImsConnection0, new ImsReasonInfo(ImsReasonInfo.CODE_LOCAL_CALL_DECLINE, 0));
+
+        ArgumentCaptor<VoiceCallSession> callCaptor =
+                ArgumentCaptor.forClass(VoiceCallSession.class);
+        verify(mPersistAtomsStorage, times(1)).addVoiceCallSession(callCaptor.capture());
+        verify(mPersistAtomsStorage, times(1)).addVoiceCallRatUsage(any());
+        verifyNoMoreInteractions(mPersistAtomsStorage);
+        assertProtoEquals(expectedCall, callCaptor.getValue());
+    }
+
+    @Test
+    @SmallTest
+    public void singleImsCall_rttStartedMidCall() {
+        doReturn(TelephonyManager.NETWORK_TYPE_LTE).when(mServiceState).getVoiceNetworkType();
+        doReturn(true).when(mImsConnection0).isIncoming();
+        doReturn(2000L).when(mImsConnection0).getCreateTime();
+        doReturn(mImsCall0).when(mImsConnection0).getCall();
+        doReturn(new ArrayList(List.of(mImsConnection0))).when(mImsCall0).getConnections();
+        VoiceCallSession expectedCall =
+                makeSlot0CallProto(
+                        VOICE_CALL_SESSION__BEARER_AT_END__CALL_BEARER_IMS,
+                        VOICE_CALL_SESSION__DIRECTION__CALL_DIRECTION_MT,
+                        TelephonyManager.NETWORK_TYPE_LTE,
+                        ImsReasonInfo.CODE_USER_TERMINATED);
+        expectedCall.setupDuration =
+                VOICE_CALL_SESSION__SETUP_DURATION__CALL_SETUP_DURATION_ULTRA_FAST;
+        expectedCall.setupFailed = false;
+        expectedCall.codecBitmask = 1L << AudioCodec.AUDIO_CODEC_AMR;
+        expectedCall.rttEnabled = true;
+
+        mVoiceCallSessionStats0.setTimeMillis(2000L);
+        doReturn(Call.State.INCOMING).when(mImsCall0).getState();
+        doReturn(Call.State.INCOMING).when(mImsConnection0).getState();
+        mVoiceCallSessionStats0.onImsCallReceived(mImsConnection0);
+        mVoiceCallSessionStats0.setTimeMillis(2100L);
+        mVoiceCallSessionStats0.onAudioCodecChanged(
+                mImsConnection0, ImsStreamMediaProfile.AUDIO_QUALITY_AMR);
+        mVoiceCallSessionStats0.setTimeMillis(2200L);
+        mVoiceCallSessionStats0.onImsAcceptCall(List.of(mImsConnection0));
+        mVoiceCallSessionStats0.setTimeMillis(2280L);
+        doReturn(Call.State.ACTIVE).when(mImsCall0).getState();
+        doReturn(Call.State.ACTIVE).when(mImsConnection0).getState();
+        mVoiceCallSessionStats0.onCallStateChanged(mImsCall0);
+        mVoiceCallSessionStats0.setTimeMillis(4000L);
+        mVoiceCallSessionStats0.onRttStarted(mImsConnection0);
+        mVoiceCallSessionStats0.setTimeMillis(8000L);
+        mVoiceCallSessionStats0.onImsCallTerminated(
+                mImsConnection0, new ImsReasonInfo(ImsReasonInfo.CODE_USER_TERMINATED, 0));
+
+        ArgumentCaptor<VoiceCallSession> callCaptor =
+                ArgumentCaptor.forClass(VoiceCallSession.class);
+        verify(mPersistAtomsStorage, times(1)).addVoiceCallSession(callCaptor.capture());
+        verify(mPersistAtomsStorage, times(1)).addVoiceCallRatUsage(any());
+        verifyNoMoreInteractions(mPersistAtomsStorage);
+        assertProtoEquals(expectedCall, callCaptor.getValue());
+    }
+
+    @Test
+    @SmallTest
+    public void concurrentImsCalls_firstCallHangupFirst() {
+        doReturn(TelephonyManager.NETWORK_TYPE_LTE).when(mServiceState).getVoiceNetworkType();
+        // call 0 starts first, MO
+        doReturn(false).when(mImsConnection0).isIncoming();
+        doReturn(2000L).when(mImsConnection0).getCreateTime();
+        doReturn(mImsCall0).when(mImsConnection0).getCall();
+        doReturn(new ArrayList(List.of(mImsConnection0))).when(mImsCall0).getConnections();
+        VoiceCallSession expectedCall0 =
+                makeSlot0CallProto(
+                        VOICE_CALL_SESSION__BEARER_AT_END__CALL_BEARER_IMS,
+                        VOICE_CALL_SESSION__DIRECTION__CALL_DIRECTION_MO,
+                        TelephonyManager.NETWORK_TYPE_LTE,
+                        ImsReasonInfo.CODE_USER_TERMINATED_BY_REMOTE);
+        expectedCall0.setupDuration =
+                VOICE_CALL_SESSION__SETUP_DURATION__CALL_SETUP_DURATION_ULTRA_FAST;
+        expectedCall0.setupFailed = false;
+        expectedCall0.codecBitmask = 1L << AudioCodec.AUDIO_CODEC_AMR;
+        expectedCall0.concurrentCallCountAtStart = 0;
+        expectedCall0.concurrentCallCountAtEnd = 1;
+        expectedCall0.ratSwitchCount = 1L;
+        expectedCall0.ratAtEnd = TelephonyManager.NETWORK_TYPE_HSPA;
+        // call 1 starts later, MT
+        doReturn(true).when(mImsConnection1).isIncoming();
+        doReturn(60000L).when(mImsConnection1).getCreateTime();
+        doReturn(mImsCall1).when(mImsConnection1).getCall();
+        doReturn(new ArrayList(List.of(mImsConnection1))).when(mImsCall1).getConnections();
+        VoiceCallSession expectedCall1 =
+                makeSlot0CallProto(
+                        VOICE_CALL_SESSION__BEARER_AT_END__CALL_BEARER_IMS,
+                        VOICE_CALL_SESSION__DIRECTION__CALL_DIRECTION_MT,
+                        TelephonyManager.NETWORK_TYPE_LTE,
+                        ImsReasonInfo.CODE_USER_TERMINATED);
+        expectedCall1.setupDuration =
+                VOICE_CALL_SESSION__SETUP_DURATION__CALL_SETUP_DURATION_EXTREMELY_FAST;
+        expectedCall1.setupFailed = false;
+        expectedCall1.codecBitmask = 1L << AudioCodec.AUDIO_CODEC_AMR;
+        expectedCall1.concurrentCallCountAtStart = 1;
+        expectedCall1.concurrentCallCountAtEnd = 0;
+        expectedCall1.ratSwitchCount = 2L;
+        expectedCall1.ratAtEnd = TelephonyManager.NETWORK_TYPE_UMTS;
+        RawVoiceCallRatUsage expectedRatUsageLte =
+                makeRatUsageProto(
+                        CARRIER_ID_SLOT_0, TelephonyManager.NETWORK_TYPE_LTE, 2000L, 80000L, 2L);
+        RawVoiceCallRatUsage expectedRatUsageHspa =
+                makeRatUsageProto(
+                        CARRIER_ID_SLOT_0, TelephonyManager.NETWORK_TYPE_HSPA, 80000L, 100000L, 2L);
+        RawVoiceCallRatUsage expectedRatUsageUmts =
+                makeRatUsageProto(
+                        CARRIER_ID_SLOT_0,
+                        TelephonyManager.NETWORK_TYPE_UMTS,
+                        100000L,
+                        120000L,
+                        1L);
+        final AtomicReference<RawVoiceCallRatUsage[]> ratUsage = setupRatUsageCapture();
+
+        // call 0 dial
+        mVoiceCallSessionStats0.setTimeMillis(2000L);
+        doReturn(Call.State.DIALING).when(mImsCall0).getState();
+        doReturn(Call.State.DIALING).when(mImsConnection0).getState();
+        mVoiceCallSessionStats0.onImsDial(mImsConnection0);
+        mVoiceCallSessionStats0.setTimeMillis(2020L);
+        mVoiceCallSessionStats0.onAudioCodecChanged(
+                mImsConnection0, ImsStreamMediaProfile.AUDIO_QUALITY_AMR);
+        mVoiceCallSessionStats0.setTimeMillis(2080L);
+        doReturn(Call.State.ALERTING).when(mImsCall0).getState();
+        doReturn(Call.State.ALERTING).when(mImsConnection0).getState();
+        mVoiceCallSessionStats0.onCallStateChanged(mImsCall0);
+        mVoiceCallSessionStats0.setTimeMillis(4000L);
+        doReturn(Call.State.ACTIVE).when(mImsCall0).getState();
+        doReturn(Call.State.ACTIVE).when(mImsConnection0).getState();
+        mVoiceCallSessionStats0.onCallStateChanged(mImsCall0);
+        // call 1 ring
+        mVoiceCallSessionStats0.setTimeMillis(60000L);
+        doReturn(Call.State.INCOMING).when(mImsCall1).getState();
+        doReturn(Call.State.INCOMING).when(mImsConnection1).getState();
+        mVoiceCallSessionStats0.onImsCallReceived(mImsConnection1);
+        mVoiceCallSessionStats0.setTimeMillis(60100L);
+        mVoiceCallSessionStats0.onAudioCodecChanged(
+                mImsConnection1, ImsStreamMediaProfile.AUDIO_QUALITY_AMR);
+        mVoiceCallSessionStats0.setTimeMillis(65000L);
+        mVoiceCallSessionStats0.onImsAcceptCall(List.of(mImsConnection1));
+        mVoiceCallSessionStats0.setTimeMillis(65020L);
+        doReturn(Call.State.ACTIVE).when(mImsCall1).getState();
+        doReturn(Call.State.ACTIVE).when(mImsConnection1).getState();
+        mVoiceCallSessionStats0.onCallStateChanged(mImsCall1);
+        // RAT change, LTE to HSPA
+        mVoiceCallSessionStats0.setTimeMillis(80000L);
+        doReturn(TelephonyManager.NETWORK_TYPE_HSPA).when(mServiceState).getVoiceNetworkType();
+        mVoiceCallSessionStats0.onServiceStateChanged(mServiceState);
+        // call 0 hangup by remote
+        mVoiceCallSessionStats0.setTimeMillis(90000L);
+        mVoiceCallSessionStats0.onImsCallTerminated(
+                mImsConnection0,
+                new ImsReasonInfo(ImsReasonInfo.CODE_USER_TERMINATED_BY_REMOTE, 0));
+        // RAT change, HSPA to UMTS
+        mVoiceCallSessionStats0.setTimeMillis(100000L);
+        doReturn(TelephonyManager.NETWORK_TYPE_UMTS).when(mServiceState).getVoiceNetworkType();
+        mVoiceCallSessionStats0.onServiceStateChanged(mServiceState);
+        // call 1 hangup by local
+        mVoiceCallSessionStats0.setTimeMillis(120000L);
+        mVoiceCallSessionStats0.onImsCallTerminated(
+                mImsConnection1, new ImsReasonInfo(ImsReasonInfo.CODE_USER_TERMINATED, 0));
+
+        ArgumentCaptor<VoiceCallSession> callCaptor =
+                ArgumentCaptor.forClass(VoiceCallSession.class);
+        verify(mPersistAtomsStorage, times(2)).addVoiceCallSession(callCaptor.capture());
+        verify(mPersistAtomsStorage, times(1)).addVoiceCallRatUsage(any());
+        verifyNoMoreInteractions(mPersistAtomsStorage);
+        assertSortedProtoArrayEquals(
+                new VoiceCallSession[] {expectedCall0, expectedCall1},
+                callCaptor.getAllValues().stream().toArray(VoiceCallSession[]::new));
+        assertSortedProtoArrayEquals(
+                new RawVoiceCallRatUsage[] {
+                    expectedRatUsageLte, expectedRatUsageHspa, expectedRatUsageUmts
+                },
+                ratUsage.get());
+    }
+
+    @Test
+    @SmallTest
+    public void concurrentImsCalls_firstCallHangupLast() {
+        doReturn(TelephonyManager.NETWORK_TYPE_LTE).when(mServiceState).getVoiceNetworkType();
+        // call 0 starts first, MO
+        doReturn(false).when(mImsConnection0).isIncoming();
+        doReturn(2000L).when(mImsConnection0).getCreateTime();
+        doReturn(mImsCall0).when(mImsConnection0).getCall();
+        doReturn(new ArrayList(List.of(mImsConnection0))).when(mImsCall0).getConnections();
+        VoiceCallSession expectedCall0 =
+                makeSlot0CallProto(
+                        VOICE_CALL_SESSION__BEARER_AT_END__CALL_BEARER_IMS,
+                        VOICE_CALL_SESSION__DIRECTION__CALL_DIRECTION_MO,
+                        TelephonyManager.NETWORK_TYPE_LTE,
+                        ImsReasonInfo.CODE_USER_TERMINATED);
+        expectedCall0.setupDuration =
+                VOICE_CALL_SESSION__SETUP_DURATION__CALL_SETUP_DURATION_ULTRA_FAST;
+        expectedCall0.setupFailed = false;
+        expectedCall0.codecBitmask = 1L << AudioCodec.AUDIO_CODEC_AMR;
+        expectedCall0.concurrentCallCountAtStart = 0;
+        expectedCall0.concurrentCallCountAtEnd = 0;
+        expectedCall0.ratSwitchCount = 2L;
+        expectedCall0.ratAtEnd = TelephonyManager.NETWORK_TYPE_UMTS;
+        // call 1 starts later, MT
+        doReturn(true).when(mImsConnection1).isIncoming();
+        doReturn(60000L).when(mImsConnection1).getCreateTime();
+        doReturn(mImsCall1).when(mImsConnection1).getCall();
+        doReturn(new ArrayList(List.of(mImsConnection1))).when(mImsCall1).getConnections();
+        VoiceCallSession expectedCall1 =
+                makeSlot0CallProto(
+                        VOICE_CALL_SESSION__BEARER_AT_END__CALL_BEARER_IMS,
+                        VOICE_CALL_SESSION__DIRECTION__CALL_DIRECTION_MT,
+                        TelephonyManager.NETWORK_TYPE_LTE,
+                        ImsReasonInfo.CODE_USER_TERMINATED_BY_REMOTE);
+        expectedCall1.setupDuration =
+                VOICE_CALL_SESSION__SETUP_DURATION__CALL_SETUP_DURATION_EXTREMELY_FAST;
+        expectedCall1.setupFailed = false;
+        expectedCall1.codecBitmask = 1L << AudioCodec.AUDIO_CODEC_AMR;
+        expectedCall1.concurrentCallCountAtStart = 1;
+        expectedCall1.concurrentCallCountAtEnd = 1;
+        expectedCall1.ratSwitchCount = 1L;
+        expectedCall1.ratAtEnd = TelephonyManager.NETWORK_TYPE_HSPA;
+        RawVoiceCallRatUsage expectedRatUsageLte =
+                makeRatUsageProto(
+                        CARRIER_ID_SLOT_0, TelephonyManager.NETWORK_TYPE_LTE, 2000L, 80000L, 2L);
+        RawVoiceCallRatUsage expectedRatUsageHspa =
+                makeRatUsageProto(
+                        CARRIER_ID_SLOT_0, TelephonyManager.NETWORK_TYPE_HSPA, 80000L, 100000L, 2L);
+        RawVoiceCallRatUsage expectedRatUsageUmts =
+                makeRatUsageProto(
+                        CARRIER_ID_SLOT_0,
+                        TelephonyManager.NETWORK_TYPE_UMTS,
+                        100000L,
+                        120000L,
+                        1L);
+        final AtomicReference<RawVoiceCallRatUsage[]> ratUsage = setupRatUsageCapture();
+
+        // call 0 dial
+        mVoiceCallSessionStats0.setTimeMillis(2000L);
+        doReturn(Call.State.DIALING).when(mImsCall0).getState();
+        doReturn(Call.State.DIALING).when(mImsConnection0).getState();
+        mVoiceCallSessionStats0.onImsDial(mImsConnection0);
+        mVoiceCallSessionStats0.setTimeMillis(2020L);
+        mVoiceCallSessionStats0.onAudioCodecChanged(
+                mImsConnection0, ImsStreamMediaProfile.AUDIO_QUALITY_AMR);
+        mVoiceCallSessionStats0.setTimeMillis(2080L);
+        doReturn(Call.State.ALERTING).when(mImsCall0).getState();
+        doReturn(Call.State.ALERTING).when(mImsConnection0).getState();
+        mVoiceCallSessionStats0.onCallStateChanged(mImsCall0);
+        mVoiceCallSessionStats0.setTimeMillis(4000L);
+        doReturn(Call.State.ACTIVE).when(mImsCall0).getState();
+        doReturn(Call.State.ACTIVE).when(mImsConnection0).getState();
+        mVoiceCallSessionStats0.onCallStateChanged(mImsCall0);
+        // call 1 ring
+        mVoiceCallSessionStats0.setTimeMillis(60000L);
+        doReturn(Call.State.INCOMING).when(mImsCall1).getState();
+        doReturn(Call.State.INCOMING).when(mImsConnection1).getState();
+        mVoiceCallSessionStats0.onImsCallReceived(mImsConnection1);
+        mVoiceCallSessionStats0.setTimeMillis(60100L);
+        mVoiceCallSessionStats0.onAudioCodecChanged(
+                mImsConnection1, ImsStreamMediaProfile.AUDIO_QUALITY_AMR);
+        mVoiceCallSessionStats0.setTimeMillis(65000L);
+        mVoiceCallSessionStats0.onImsAcceptCall(List.of(mImsConnection1));
+        mVoiceCallSessionStats0.setTimeMillis(65020L);
+        doReturn(Call.State.ACTIVE).when(mImsCall1).getState();
+        doReturn(Call.State.ACTIVE).when(mImsConnection1).getState();
+        mVoiceCallSessionStats0.onCallStateChanged(mImsCall1);
+        // RAT change, LTE to HSPA
+        mVoiceCallSessionStats0.setTimeMillis(80000L);
+        doReturn(TelephonyManager.NETWORK_TYPE_HSPA).when(mServiceState).getVoiceNetworkType();
+        mVoiceCallSessionStats0.onServiceStateChanged(mServiceState);
+        // call 1 hangup by remote
+        mVoiceCallSessionStats0.setTimeMillis(90000L);
+        mVoiceCallSessionStats0.onImsCallTerminated(
+                mImsConnection1,
+                new ImsReasonInfo(ImsReasonInfo.CODE_USER_TERMINATED_BY_REMOTE, 0));
+        // RAT change, HSPA to UMTS
+        mVoiceCallSessionStats0.setTimeMillis(100000L);
+        doReturn(TelephonyManager.NETWORK_TYPE_UMTS).when(mServiceState).getVoiceNetworkType();
+        mVoiceCallSessionStats0.onServiceStateChanged(mServiceState);
+        // call 0 hangup by local
+        mVoiceCallSessionStats0.setTimeMillis(120000L);
+        mVoiceCallSessionStats0.onImsCallTerminated(
+                mImsConnection0, new ImsReasonInfo(ImsReasonInfo.CODE_USER_TERMINATED, 0));
+
+        ArgumentCaptor<VoiceCallSession> callCaptor =
+                ArgumentCaptor.forClass(VoiceCallSession.class);
+        verify(mPersistAtomsStorage, times(2)).addVoiceCallSession(callCaptor.capture());
+        verify(mPersistAtomsStorage, times(1)).addVoiceCallRatUsage(any());
+        verifyNoMoreInteractions(mPersistAtomsStorage);
+        assertSortedProtoArrayEquals(
+                new VoiceCallSession[] {expectedCall0, expectedCall1},
+                callCaptor.getAllValues().stream().toArray(VoiceCallSession[]::new));
+        assertSortedProtoArrayEquals(
+                new RawVoiceCallRatUsage[] {
+                    expectedRatUsageLte, expectedRatUsageHspa, expectedRatUsageUmts
+                },
+                ratUsage.get());
+    }
+
+    @Test
+    @SmallTest
+    public void concurrentImsCalls_firstCallHangupDuringSecondCallSetup() {
+        doReturn(TelephonyManager.NETWORK_TYPE_LTE).when(mServiceState).getVoiceNetworkType();
+        // call 0 starts first, MO
+        doReturn(false).when(mImsConnection0).isIncoming();
+        doReturn(2000L).when(mImsConnection0).getCreateTime();
+        doReturn(mImsCall0).when(mImsConnection0).getCall();
+        doReturn(new ArrayList(List.of(mImsConnection0))).when(mImsCall0).getConnections();
+        VoiceCallSession expectedCall0 =
+                makeSlot0CallProto(
+                        VOICE_CALL_SESSION__BEARER_AT_END__CALL_BEARER_IMS,
+                        VOICE_CALL_SESSION__DIRECTION__CALL_DIRECTION_MO,
+                        TelephonyManager.NETWORK_TYPE_LTE,
+                        ImsReasonInfo.CODE_USER_TERMINATED);
+        expectedCall0.setupDuration =
+                VOICE_CALL_SESSION__SETUP_DURATION__CALL_SETUP_DURATION_ULTRA_FAST;
+        expectedCall0.setupFailed = false;
+        expectedCall0.codecBitmask = 1L << AudioCodec.AUDIO_CODEC_AMR;
+        expectedCall0.concurrentCallCountAtStart = 0;
+        expectedCall0.concurrentCallCountAtEnd = 1;
+        expectedCall0.ratSwitchCount = 0L;
+        expectedCall0.ratAtEnd = TelephonyManager.NETWORK_TYPE_LTE;
+        // call 1 starts later, MT
+        doReturn(true).when(mImsConnection1).isIncoming();
+        doReturn(60000L).when(mImsConnection1).getCreateTime();
+        doReturn(mImsCall1).when(mImsConnection1).getCall();
+        doReturn(new ArrayList(List.of(mImsConnection1))).when(mImsCall1).getConnections();
+        VoiceCallSession expectedCall1 =
+                makeSlot0CallProto(
+                        VOICE_CALL_SESSION__BEARER_AT_END__CALL_BEARER_IMS,
+                        VOICE_CALL_SESSION__DIRECTION__CALL_DIRECTION_MT,
+                        TelephonyManager.NETWORK_TYPE_LTE,
+                        ImsReasonInfo.CODE_USER_TERMINATED_BY_REMOTE);
+        expectedCall1.setupDuration =
+                VOICE_CALL_SESSION__SETUP_DURATION__CALL_SETUP_DURATION_EXTREMELY_FAST;
+        expectedCall1.setupFailed = false;
+        expectedCall1.codecBitmask = 1L << AudioCodec.AUDIO_CODEC_AMR;
+        expectedCall1.concurrentCallCountAtStart = 1;
+        expectedCall1.concurrentCallCountAtEnd = 0;
+        expectedCall1.ratSwitchCount = 1L;
+        expectedCall1.ratAtEnd = TelephonyManager.NETWORK_TYPE_HSPA;
+        RawVoiceCallRatUsage expectedRatUsageLte =
+                makeRatUsageProto(
+                        CARRIER_ID_SLOT_0, TelephonyManager.NETWORK_TYPE_LTE, 2000L, 80000L, 2L);
+        RawVoiceCallRatUsage expectedRatUsageHspa =
+                makeRatUsageProto(
+                        CARRIER_ID_SLOT_0, TelephonyManager.NETWORK_TYPE_HSPA, 80000L, 90000L, 1L);
+        final AtomicReference<RawVoiceCallRatUsage[]> ratUsage = setupRatUsageCapture();
+
+        // call 0 dial
+        mVoiceCallSessionStats0.setTimeMillis(2000L);
+        doReturn(Call.State.DIALING).when(mImsCall0).getState();
+        doReturn(Call.State.DIALING).when(mImsConnection0).getState();
+        mVoiceCallSessionStats0.onImsDial(mImsConnection0);
+        mVoiceCallSessionStats0.setTimeMillis(2020L);
+        mVoiceCallSessionStats0.onAudioCodecChanged(
+                mImsConnection0, ImsStreamMediaProfile.AUDIO_QUALITY_AMR);
+        mVoiceCallSessionStats0.setTimeMillis(2080L);
+        doReturn(Call.State.ALERTING).when(mImsCall0).getState();
+        doReturn(Call.State.ALERTING).when(mImsConnection0).getState();
+        mVoiceCallSessionStats0.onCallStateChanged(mImsCall0);
+        mVoiceCallSessionStats0.setTimeMillis(4000L);
+        doReturn(Call.State.ACTIVE).when(mImsCall0).getState();
+        doReturn(Call.State.ACTIVE).when(mImsConnection0).getState();
+        mVoiceCallSessionStats0.onCallStateChanged(mImsCall0);
+        // call 1 ring
+        mVoiceCallSessionStats0.setTimeMillis(60000L);
+        doReturn(Call.State.INCOMING).when(mImsCall1).getState();
+        doReturn(Call.State.INCOMING).when(mImsConnection1).getState();
+        mVoiceCallSessionStats0.onImsCallReceived(mImsConnection1);
+        mVoiceCallSessionStats0.setTimeMillis(60100L);
+        mVoiceCallSessionStats0.onAudioCodecChanged(
+                mImsConnection1, ImsStreamMediaProfile.AUDIO_QUALITY_AMR);
+        // call 0 hangup by local
+        mVoiceCallSessionStats0.setTimeMillis(61000L);
+        mVoiceCallSessionStats0.onImsCallTerminated(
+                mImsConnection0, new ImsReasonInfo(ImsReasonInfo.CODE_USER_TERMINATED, 0));
+        mVoiceCallSessionStats0.setTimeMillis(65000L);
+        mVoiceCallSessionStats0.onImsAcceptCall(List.of(mImsConnection1));
+        mVoiceCallSessionStats0.setTimeMillis(65020L);
+        doReturn(Call.State.ACTIVE).when(mImsCall1).getState();
+        doReturn(Call.State.ACTIVE).when(mImsConnection1).getState();
+        mVoiceCallSessionStats0.onCallStateChanged(mImsCall1);
+        // RAT change, LTE to HSPA
+        mVoiceCallSessionStats0.setTimeMillis(80000L);
+        doReturn(TelephonyManager.NETWORK_TYPE_HSPA).when(mServiceState).getVoiceNetworkType();
+        mVoiceCallSessionStats0.onServiceStateChanged(mServiceState);
+        // call 1 hangup by remote
+        mVoiceCallSessionStats0.setTimeMillis(90000L);
+        mVoiceCallSessionStats0.onImsCallTerminated(
+                mImsConnection1,
+                new ImsReasonInfo(ImsReasonInfo.CODE_USER_TERMINATED_BY_REMOTE, 0));
+
+        ArgumentCaptor<VoiceCallSession> callCaptor =
+                ArgumentCaptor.forClass(VoiceCallSession.class);
+        verify(mPersistAtomsStorage, times(2)).addVoiceCallSession(callCaptor.capture());
+        verify(mPersistAtomsStorage, times(1)).addVoiceCallRatUsage(any());
+        verifyNoMoreInteractions(mPersistAtomsStorage);
+        assertSortedProtoArrayEquals(
+                new VoiceCallSession[] {expectedCall0, expectedCall1},
+                callCaptor.getAllValues().stream().toArray(VoiceCallSession[]::new));
+        assertSortedProtoArrayEquals(
+                new RawVoiceCallRatUsage[] {expectedRatUsageLte, expectedRatUsageHspa},
+                ratUsage.get());
+    }
+
+    @Test
+    @SmallTest
+    public void singleCsCall_moRejected() {
+        doReturn(false).when(mGsmConnection0).isIncoming();
+        doReturn(2000L).when(mGsmConnection0).getCreateTime();
+        doReturn(mCsCall0).when(mGsmConnection0).getCall();
+        VoiceCallSession expectedCall =
+                makeSlot0CallProto(
+                        VOICE_CALL_SESSION__BEARER_AT_END__CALL_BEARER_CS,
+                        VOICE_CALL_SESSION__DIRECTION__CALL_DIRECTION_MO,
+                        TelephonyManager.NETWORK_TYPE_LTE,
+                        DisconnectCause.NORMAL);
+        expectedCall.ratAtEnd = TelephonyManager.NETWORK_TYPE_UMTS;
+        expectedCall.setupDuration =
+                VOICE_CALL_SESSION__SETUP_DURATION__CALL_SETUP_DURATION_VERY_SLOW;
+        expectedCall.disconnectExtraCode = PreciseDisconnectCause.CALL_REJECTED;
+        expectedCall.ratSwitchCount = 1L;
+        expectedCall.setupFailed = true;
+        expectedCall.codecBitmask = 1L << AudioCodec.AUDIO_CODEC_AMR;
+        RawVoiceCallRatUsage expectedRatUsageLte =
+                makeRatUsageProto(
+                        CARRIER_ID_SLOT_0, TelephonyManager.NETWORK_TYPE_LTE, 2000L, 3000L, 1L);
+        RawVoiceCallRatUsage expectedRatUsageUmts =
+                makeRatUsageProto(
+                        CARRIER_ID_SLOT_0, TelephonyManager.NETWORK_TYPE_UMTS, 3000L, 15000L, 1L);
+        final AtomicReference<RawVoiceCallRatUsage[]> ratUsage = setupRatUsageCapture();
+
+        mVoiceCallSessionStats0.setTimeMillis(2000L);
+        doReturn(TelephonyManager.NETWORK_TYPE_LTE).when(mServiceState).getVoiceNetworkType();
+        doReturn(Call.State.DIALING).when(mCsCall0).getState();
+        doReturn(Call.State.DIALING).when(mGsmConnection0).getState();
+        doReturn(DisconnectCause.NOT_DISCONNECTED).when(mGsmConnection0).getDisconnectCause();
+        mVoiceCallSessionStats0.onRilDial(mGsmConnection0);
+        mVoiceCallSessionStats0.setTimeMillis(3000L);
+        doReturn(TelephonyManager.NETWORK_TYPE_UMTS).when(mServiceState).getVoiceNetworkType();
+        mVoiceCallSessionStats0.onServiceStateChanged(mServiceState);
+        mVoiceCallSessionStats0.setTimeMillis(3100L);
+        mVoiceCallSessionStats0.onAudioCodecChanged(mGsmConnection0, DriverCall.AUDIO_QUALITY_AMR);
+        mVoiceCallSessionStats0.setTimeMillis(7000L);
+        doReturn(Call.State.ALERTING).when(mCsCall0).getState();
+        doReturn(Call.State.ALERTING).when(mGsmConnection0).getState();
+        mVoiceCallSessionStats0.onRilCallListChanged(List.of(mGsmConnection0));
+        mVoiceCallSessionStats0.setTimeMillis(15000L);
+        doReturn(DisconnectCause.NORMAL).when(mGsmConnection0).getDisconnectCause();
+        doReturn(PreciseDisconnectCause.CALL_REJECTED)
+                .when(mGsmConnection0)
+                .getPreciseDisconnectCause();
+        mVoiceCallSessionStats0.onRilCallListChanged(List.of(mGsmConnection0));
+
+        ArgumentCaptor<VoiceCallSession> callCaptor =
+                ArgumentCaptor.forClass(VoiceCallSession.class);
+        verify(mPersistAtomsStorage, times(1)).addVoiceCallSession(callCaptor.capture());
+        verify(mPersistAtomsStorage, times(1)).addVoiceCallRatUsage(any());
+        verifyNoMoreInteractions(mPersistAtomsStorage);
+        assertProtoEquals(expectedCall, callCaptor.getValue());
+        assertSortedProtoArrayEquals(
+                new RawVoiceCallRatUsage[] {expectedRatUsageLte, expectedRatUsageUmts},
+                ratUsage.get());
+    }
+
+    @Test
+    @SmallTest
+    public void singleCsCall_moFailed() {
+        doReturn(false).when(mGsmConnection0).isIncoming();
+        doReturn(2000L).when(mGsmConnection0).getCreateTime();
+        doReturn(mCsCall0).when(mGsmConnection0).getCall();
+        VoiceCallSession expectedCall =
+                makeSlot0CallProto(
+                        VOICE_CALL_SESSION__BEARER_AT_END__CALL_BEARER_CS,
+                        VOICE_CALL_SESSION__DIRECTION__CALL_DIRECTION_MO,
+                        TelephonyManager.NETWORK_TYPE_LTE,
+                        DisconnectCause.LOST_SIGNAL);
+        expectedCall.ratAtEnd = TelephonyManager.NETWORK_TYPE_UMTS;
+        expectedCall.ratSwitchCount = 1L;
+        expectedCall.setupFailed = true;
+        expectedCall.codecBitmask = 1L << AudioCodec.AUDIO_CODEC_AMR;
+        RawVoiceCallRatUsage expectedRatUsageLte =
+                makeRatUsageProto(
+                        CARRIER_ID_SLOT_0, TelephonyManager.NETWORK_TYPE_LTE, 2000L, 3000L, 1L);
+        RawVoiceCallRatUsage expectedRatUsageUmts =
+                makeRatUsageProto(
+                        CARRIER_ID_SLOT_0, TelephonyManager.NETWORK_TYPE_UMTS, 3000L, 15000L, 1L);
+        final AtomicReference<RawVoiceCallRatUsage[]> ratUsage = setupRatUsageCapture();
+
+        mVoiceCallSessionStats0.setTimeMillis(2000L);
+        doReturn(TelephonyManager.NETWORK_TYPE_LTE).when(mServiceState).getVoiceNetworkType();
+        doReturn(Call.State.DIALING).when(mCsCall0).getState();
+        doReturn(Call.State.DIALING).when(mGsmConnection0).getState();
+        doReturn(DisconnectCause.NOT_DISCONNECTED).when(mGsmConnection0).getDisconnectCause();
+        mVoiceCallSessionStats0.onRilDial(mGsmConnection0);
+        mVoiceCallSessionStats0.setTimeMillis(3000L);
+        doReturn(TelephonyManager.NETWORK_TYPE_UMTS).when(mServiceState).getVoiceNetworkType();
+        mVoiceCallSessionStats0.onServiceStateChanged(mServiceState);
+        mVoiceCallSessionStats0.setTimeMillis(3100L);
+        mVoiceCallSessionStats0.onAudioCodecChanged(mGsmConnection0, DriverCall.AUDIO_QUALITY_AMR);
+        mVoiceCallSessionStats0.setTimeMillis(15000L);
+        doReturn(DisconnectCause.LOST_SIGNAL).when(mGsmConnection0).getDisconnectCause();
+        mVoiceCallSessionStats0.onRilCallListChanged(List.of(mGsmConnection0));
+
+        ArgumentCaptor<VoiceCallSession> callCaptor =
+                ArgumentCaptor.forClass(VoiceCallSession.class);
+        verify(mPersistAtomsStorage, times(1)).addVoiceCallSession(callCaptor.capture());
+        verify(mPersistAtomsStorage, times(1)).addVoiceCallRatUsage(any());
+        verifyNoMoreInteractions(mPersistAtomsStorage);
+        assertProtoEquals(expectedCall, callCaptor.getValue());
+        assertSortedProtoArrayEquals(
+                new RawVoiceCallRatUsage[] {expectedRatUsageLte, expectedRatUsageUmts},
+                ratUsage.get());
+    }
+
+    @Test
+    @SmallTest
+    public void singleCsCall_moAccepted() {
+        doReturn(false).when(mGsmConnection0).isIncoming();
+        doReturn(2000L).when(mGsmConnection0).getCreateTime();
+        doReturn(mCsCall0).when(mGsmConnection0).getCall();
+        VoiceCallSession expectedCall =
+                makeSlot0CallProto(
+                        VOICE_CALL_SESSION__BEARER_AT_END__CALL_BEARER_CS,
+                        VOICE_CALL_SESSION__DIRECTION__CALL_DIRECTION_MO,
+                        TelephonyManager.NETWORK_TYPE_LTE,
+                        DisconnectCause.NORMAL);
+        expectedCall.ratAtEnd = TelephonyManager.NETWORK_TYPE_UMTS;
+        expectedCall.setupDuration =
+                VOICE_CALL_SESSION__SETUP_DURATION__CALL_SETUP_DURATION_VERY_SLOW;
+        expectedCall.disconnectExtraCode = PreciseDisconnectCause.NORMAL;
+        expectedCall.ratSwitchCount = 1L;
+        expectedCall.setupFailed = false;
+        expectedCall.codecBitmask = 1L << AudioCodec.AUDIO_CODEC_AMR;
+        RawVoiceCallRatUsage expectedRatUsageLte =
+                makeRatUsageProto(
+                        CARRIER_ID_SLOT_0, TelephonyManager.NETWORK_TYPE_LTE, 2000L, 3000L, 1L);
+        RawVoiceCallRatUsage expectedRatUsageUmts =
+                makeRatUsageProto(
+                        CARRIER_ID_SLOT_0, TelephonyManager.NETWORK_TYPE_UMTS, 3000L, 100000L, 1L);
+        final AtomicReference<RawVoiceCallRatUsage[]> ratUsage = setupRatUsageCapture();
+
+        mVoiceCallSessionStats0.setTimeMillis(2000L);
+        doReturn(TelephonyManager.NETWORK_TYPE_LTE).when(mServiceState).getVoiceNetworkType();
+        doReturn(Call.State.DIALING).when(mCsCall0).getState();
+        doReturn(Call.State.DIALING).when(mGsmConnection0).getState();
+        doReturn(DisconnectCause.NOT_DISCONNECTED).when(mGsmConnection0).getDisconnectCause();
+        mVoiceCallSessionStats0.onRilDial(mGsmConnection0);
+        mVoiceCallSessionStats0.setTimeMillis(3000L);
+        doReturn(TelephonyManager.NETWORK_TYPE_UMTS).when(mServiceState).getVoiceNetworkType();
+        mVoiceCallSessionStats0.onServiceStateChanged(mServiceState);
+        mVoiceCallSessionStats0.setTimeMillis(3100L);
+        mVoiceCallSessionStats0.onAudioCodecChanged(mGsmConnection0, DriverCall.AUDIO_QUALITY_AMR);
+        mVoiceCallSessionStats0.setTimeMillis(7000L);
+        doReturn(Call.State.ALERTING).when(mCsCall0).getState();
+        doReturn(Call.State.ALERTING).when(mGsmConnection0).getState();
+        mVoiceCallSessionStats0.onRilCallListChanged(List.of(mGsmConnection0));
+        mVoiceCallSessionStats0.setTimeMillis(10000L);
+        doReturn(Call.State.ACTIVE).when(mCsCall0).getState();
+        doReturn(Call.State.ACTIVE).when(mGsmConnection0).getState();
+        mVoiceCallSessionStats0.onRilCallListChanged(List.of(mGsmConnection0));
+        mVoiceCallSessionStats0.setTimeMillis(100000L);
+        doReturn(DisconnectCause.NORMAL).when(mGsmConnection0).getDisconnectCause();
+        doReturn(PreciseDisconnectCause.NORMAL).when(mGsmConnection0).getPreciseDisconnectCause();
+        mVoiceCallSessionStats0.onRilCallListChanged(List.of(mGsmConnection0));
+
+        ArgumentCaptor<VoiceCallSession> callCaptor =
+                ArgumentCaptor.forClass(VoiceCallSession.class);
+        verify(mPersistAtomsStorage, times(1)).addVoiceCallSession(callCaptor.capture());
+        verify(mPersistAtomsStorage, times(1)).addVoiceCallRatUsage(any());
+        verifyNoMoreInteractions(mPersistAtomsStorage);
+        assertProtoEquals(expectedCall, callCaptor.getValue());
+        assertSortedProtoArrayEquals(
+                new RawVoiceCallRatUsage[] {expectedRatUsageLte, expectedRatUsageUmts},
+                ratUsage.get());
+    }
+
+    @Test
+    @SmallTest
+    public void singleCsCall_mtRejected() {
+        doReturn(TelephonyManager.NETWORK_TYPE_UMTS).when(mServiceState).getVoiceNetworkType();
+        doReturn(true).when(mGsmConnection0).isIncoming();
+        doReturn(2000L).when(mGsmConnection0).getCreateTime();
+        doReturn(mCsCall0).when(mGsmConnection0).getCall();
+        VoiceCallSession expectedCall =
+                makeSlot0CallProto(
+                        VOICE_CALL_SESSION__BEARER_AT_END__CALL_BEARER_CS,
+                        VOICE_CALL_SESSION__DIRECTION__CALL_DIRECTION_MT,
+                        TelephonyManager.NETWORK_TYPE_UMTS,
+                        DisconnectCause.NORMAL);
+        expectedCall.setupDuration =
+                VOICE_CALL_SESSION__SETUP_DURATION__CALL_SETUP_DURATION_UNKNOWN;
+        expectedCall.disconnectExtraCode = PreciseDisconnectCause.CALL_REJECTED;
+        expectedCall.setupFailed = true;
+        expectedCall.codecBitmask = 1L << AudioCodec.AUDIO_CODEC_AMR;
+        RawVoiceCallRatUsage expectedRatUsage =
+                makeRatUsageProto(
+                        CARRIER_ID_SLOT_0, TelephonyManager.NETWORK_TYPE_UMTS, 2500L, 15000L, 1L);
+        final AtomicReference<RawVoiceCallRatUsage[]> ratUsage = setupRatUsageCapture();
+
+        mVoiceCallSessionStats0.setTimeMillis(2000L);
+        mVoiceCallSessionStats0.onServiceStateChanged(mServiceState);
+        mVoiceCallSessionStats0.setTimeMillis(2500L);
+        doReturn(Call.State.INCOMING).when(mCsCall0).getState();
+        doReturn(Call.State.INCOMING).when(mGsmConnection0).getState();
+        doReturn(DisconnectCause.NOT_DISCONNECTED).when(mGsmConnection0).getDisconnectCause();
+        mVoiceCallSessionStats0.onRilCallListChanged(List.of(mGsmConnection0));
+        mVoiceCallSessionStats0.setTimeMillis(3000L);
+        mVoiceCallSessionStats0.onAudioCodecChanged(mGsmConnection0, DriverCall.AUDIO_QUALITY_AMR);
+        mVoiceCallSessionStats0.setTimeMillis(15000L);
+        doReturn(DisconnectCause.NORMAL).when(mGsmConnection0).getDisconnectCause();
+        doReturn(PreciseDisconnectCause.CALL_REJECTED)
+                .when(mGsmConnection0)
+                .getPreciseDisconnectCause();
+        mVoiceCallSessionStats0.onRilCallListChanged(List.of(mGsmConnection0));
+
+        ArgumentCaptor<VoiceCallSession> callCaptor =
+                ArgumentCaptor.forClass(VoiceCallSession.class);
+        verify(mPersistAtomsStorage, times(1)).addVoiceCallSession(callCaptor.capture());
+        verify(mPersistAtomsStorage, times(1)).addVoiceCallRatUsage(any());
+        verifyNoMoreInteractions(mPersistAtomsStorage);
+        assertProtoEquals(expectedCall, callCaptor.getValue());
+        assertThat(ratUsage.get()).hasLength(1);
+        assertProtoEquals(expectedRatUsage, ratUsage.get()[0]);
+    }
+
+    @Test
+    @SmallTest
+    public void singleCsCall_mtAccepted() {
+        doReturn(TelephonyManager.NETWORK_TYPE_UMTS).when(mServiceState).getVoiceNetworkType();
+        doReturn(true).when(mGsmConnection0).isIncoming();
+        doReturn(2000L).when(mGsmConnection0).getCreateTime();
+        doReturn(mCsCall0).when(mGsmConnection0).getCall();
+        VoiceCallSession expectedCall =
+                makeSlot0CallProto(
+                        VOICE_CALL_SESSION__BEARER_AT_END__CALL_BEARER_CS,
+                        VOICE_CALL_SESSION__DIRECTION__CALL_DIRECTION_MT,
+                        TelephonyManager.NETWORK_TYPE_UMTS,
+                        DisconnectCause.NORMAL);
+        expectedCall.setupDuration = VOICE_CALL_SESSION__SETUP_DURATION__CALL_SETUP_DURATION_FAST;
+        expectedCall.disconnectExtraCode = PreciseDisconnectCause.NORMAL;
+        expectedCall.setupFailed = false;
+        expectedCall.codecBitmask = 1L << AudioCodec.AUDIO_CODEC_AMR;
+        RawVoiceCallRatUsage expectedRatUsage =
+                makeRatUsageProto(
+                        CARRIER_ID_SLOT_0, TelephonyManager.NETWORK_TYPE_UMTS, 2500L, 100000L, 1L);
+        final AtomicReference<RawVoiceCallRatUsage[]> ratUsage = setupRatUsageCapture();
+
+        mVoiceCallSessionStats0.setTimeMillis(2000L);
+        mVoiceCallSessionStats0.onServiceStateChanged(mServiceState);
+        mVoiceCallSessionStats0.setTimeMillis(2500L);
+        doReturn(Call.State.INCOMING).when(mCsCall0).getState();
+        doReturn(Call.State.INCOMING).when(mGsmConnection0).getState();
+        doReturn(DisconnectCause.NOT_DISCONNECTED).when(mGsmConnection0).getDisconnectCause();
+        mVoiceCallSessionStats0.onRilCallListChanged(List.of(mGsmConnection0));
+        mVoiceCallSessionStats0.setTimeMillis(3000L);
+        mVoiceCallSessionStats0.onAudioCodecChanged(mGsmConnection0, DriverCall.AUDIO_QUALITY_AMR);
+        mVoiceCallSessionStats0.setTimeMillis(15000L);
+        mVoiceCallSessionStats0.onRilAcceptCall(List.of(mGsmConnection0));
+        mVoiceCallSessionStats0.setTimeMillis(15500L);
+        doReturn(Call.State.ACTIVE).when(mCsCall0).getState();
+        doReturn(Call.State.ACTIVE).when(mGsmConnection0).getState();
+        mVoiceCallSessionStats0.onRilCallListChanged(List.of(mGsmConnection0));
+        mVoiceCallSessionStats0.setTimeMillis(100000L);
+        doReturn(DisconnectCause.NORMAL).when(mGsmConnection0).getDisconnectCause();
+        doReturn(PreciseDisconnectCause.NORMAL).when(mGsmConnection0).getPreciseDisconnectCause();
+        mVoiceCallSessionStats0.onRilCallListChanged(List.of(mGsmConnection0));
+
+        ArgumentCaptor<VoiceCallSession> callCaptor =
+                ArgumentCaptor.forClass(VoiceCallSession.class);
+        verify(mPersistAtomsStorage, times(1)).addVoiceCallSession(callCaptor.capture());
+        verify(mPersistAtomsStorage, times(1)).addVoiceCallRatUsage(any());
+        verifyNoMoreInteractions(mPersistAtomsStorage);
+        assertProtoEquals(expectedCall, callCaptor.getValue());
+        assertThat(ratUsage.get()).hasLength(1);
+        assertProtoEquals(expectedRatUsage, ratUsage.get()[0]);
+    }
+
+    @Test
+    @SmallTest
+    public void singleCall_srvccFailed() {
+        doReturn(TelephonyManager.NETWORK_TYPE_LTE).when(mServiceState).getVoiceNetworkType();
+        doReturn(true).when(mImsConnection0).isIncoming();
+        doReturn(2000L).when(mImsConnection0).getCreateTime();
+        doReturn(mImsCall0).when(mImsConnection0).getCall();
+        doReturn(new ArrayList(List.of(mImsConnection0))).when(mImsCall0).getConnections();
+        doReturn(mImsPhone).when(mPhone).getImsPhone();
+        doReturn(new ArrayList(List.of(mImsConnection0))).when(mImsPhone).getHandoverConnection();
+        VoiceCallSession expectedCall =
+                makeSlot0CallProto(
+                        VOICE_CALL_SESSION__BEARER_AT_END__CALL_BEARER_IMS,
+                        VOICE_CALL_SESSION__DIRECTION__CALL_DIRECTION_MT,
+                        TelephonyManager.NETWORK_TYPE_LTE,
+                        ImsReasonInfo.CODE_LOCAL_HO_NOT_FEASIBLE);
+        expectedCall.setupDuration =
+                VOICE_CALL_SESSION__SETUP_DURATION__CALL_SETUP_DURATION_ULTRA_FAST;
+        expectedCall.setupFailed = false;
+        expectedCall.srvccFailureCount = 2L;
+        expectedCall.ratSwitchCount = 1L;
+        expectedCall.ratAtEnd = TelephonyManager.NETWORK_TYPE_UMTS;
+        expectedCall.codecBitmask = 1L << AudioCodec.AUDIO_CODEC_AMR;
+        RawVoiceCallRatUsage expectedRatUsageLte =
+                makeRatUsageProto(
+                        CARRIER_ID_SLOT_0, TelephonyManager.NETWORK_TYPE_LTE, 2000L, 10000L, 1L);
+        RawVoiceCallRatUsage expectedRatUsageUmts =
+                makeRatUsageProto(
+                        CARRIER_ID_SLOT_0, TelephonyManager.NETWORK_TYPE_UMTS, 10000L, 12000L, 1L);
+        final AtomicReference<RawVoiceCallRatUsage[]> ratUsage = setupRatUsageCapture();
+
+        mVoiceCallSessionStats0.setTimeMillis(2000L);
+        doReturn(Call.State.INCOMING).when(mImsCall0).getState();
+        doReturn(Call.State.INCOMING).when(mImsConnection0).getState();
+        mVoiceCallSessionStats0.onImsCallReceived(mImsConnection0);
+        mVoiceCallSessionStats0.setTimeMillis(2100L);
+        mVoiceCallSessionStats0.onAudioCodecChanged(
+                mImsConnection0, ImsStreamMediaProfile.AUDIO_QUALITY_AMR);
+        mVoiceCallSessionStats0.setTimeMillis(2200L);
+        mVoiceCallSessionStats0.onImsAcceptCall(List.of(mImsConnection0));
+        mVoiceCallSessionStats0.setTimeMillis(2280L);
+        doReturn(Call.State.ACTIVE).when(mImsCall0).getState();
+        doReturn(Call.State.ACTIVE).when(mImsConnection0).getState();
+        mVoiceCallSessionStats0.onCallStateChanged(mImsCall0);
+        mVoiceCallSessionStats0.setTimeMillis(9000L);
+        mVoiceCallSessionStats0.onRilSrvccStateChanged(
+                TelephonyManager.SRVCC_STATE_HANDOVER_STARTED);
+        mVoiceCallSessionStats0.setTimeMillis(10000L);
+        doReturn(TelephonyManager.NETWORK_TYPE_UMTS).when(mServiceState).getVoiceNetworkType();
+        mVoiceCallSessionStats0.onServiceStateChanged(mServiceState);
+        mVoiceCallSessionStats0.setTimeMillis(11000L);
+        mVoiceCallSessionStats0.onRilSrvccStateChanged(
+                TelephonyManager.SRVCC_STATE_HANDOVER_FAILED);
+        mVoiceCallSessionStats0.setTimeMillis(11100L);
+        mVoiceCallSessionStats0.onRilSrvccStateChanged(
+                TelephonyManager.SRVCC_STATE_HANDOVER_STARTED);
+        mVoiceCallSessionStats0.setTimeMillis(11500L);
+        mVoiceCallSessionStats0.onRilSrvccStateChanged(
+                TelephonyManager.SRVCC_STATE_HANDOVER_FAILED);
+        mVoiceCallSessionStats0.setTimeMillis(12000L);
+        mVoiceCallSessionStats0.onImsCallTerminated(
+                mImsConnection0, new ImsReasonInfo(ImsReasonInfo.CODE_LOCAL_HO_NOT_FEASIBLE, 0));
+
+        ArgumentCaptor<VoiceCallSession> callCaptor =
+                ArgumentCaptor.forClass(VoiceCallSession.class);
+        verify(mPersistAtomsStorage, times(1)).addVoiceCallSession(callCaptor.capture());
+        verify(mPersistAtomsStorage, times(1)).addVoiceCallRatUsage(any());
+        verifyNoMoreInteractions(mPersistAtomsStorage);
+        assertProtoEquals(expectedCall, callCaptor.getValue());
+        assertSortedProtoArrayEquals(
+                new RawVoiceCallRatUsage[] {expectedRatUsageLte, expectedRatUsageUmts},
+                ratUsage.get());
+    }
+
+    @Test
+    @SmallTest
+    public void singleCall_srvccCanceled() {
+        doReturn(TelephonyManager.NETWORK_TYPE_LTE).when(mServiceState).getVoiceNetworkType();
+        doReturn(true).when(mImsConnection0).isIncoming();
+        doReturn(2000L).when(mImsConnection0).getCreateTime();
+        doReturn(mImsCall0).when(mImsConnection0).getCall();
+        doReturn(new ArrayList(List.of(mImsConnection0))).when(mImsCall0).getConnections();
+        doReturn(mImsPhone).when(mPhone).getImsPhone();
+        doReturn(new ArrayList(List.of(mImsConnection0))).when(mImsPhone).getHandoverConnection();
+        VoiceCallSession expectedCall =
+                makeSlot0CallProto(
+                        VOICE_CALL_SESSION__BEARER_AT_END__CALL_BEARER_IMS,
+                        VOICE_CALL_SESSION__DIRECTION__CALL_DIRECTION_MT,
+                        TelephonyManager.NETWORK_TYPE_LTE,
+                        ImsReasonInfo.CODE_USER_TERMINATED);
+        expectedCall.setupDuration =
+                VOICE_CALL_SESSION__SETUP_DURATION__CALL_SETUP_DURATION_ULTRA_FAST;
+        expectedCall.setupFailed = false;
+        expectedCall.srvccCancellationCount = 2L;
+        expectedCall.codecBitmask = 1L << AudioCodec.AUDIO_CODEC_AMR;
+        RawVoiceCallRatUsage expectedRatUsage =
+                makeRatUsageProto(
+                        CARRIER_ID_SLOT_0, TelephonyManager.NETWORK_TYPE_LTE, 2000L, 12000L, 1L);
+        final AtomicReference<RawVoiceCallRatUsage[]> ratUsage = setupRatUsageCapture();
+
+        mVoiceCallSessionStats0.setTimeMillis(2000L);
+        doReturn(Call.State.INCOMING).when(mImsCall0).getState();
+        doReturn(Call.State.INCOMING).when(mImsConnection0).getState();
+        mVoiceCallSessionStats0.onImsCallReceived(mImsConnection0);
+        mVoiceCallSessionStats0.setTimeMillis(2100L);
+        mVoiceCallSessionStats0.onAudioCodecChanged(
+                mImsConnection0, ImsStreamMediaProfile.AUDIO_QUALITY_AMR);
+        mVoiceCallSessionStats0.setTimeMillis(2200L);
+        mVoiceCallSessionStats0.onImsAcceptCall(List.of(mImsConnection0));
+        mVoiceCallSessionStats0.setTimeMillis(2280L);
+        doReturn(Call.State.ACTIVE).when(mImsCall0).getState();
+        doReturn(Call.State.ACTIVE).when(mImsConnection0).getState();
+        mVoiceCallSessionStats0.onCallStateChanged(mImsCall0);
+        mVoiceCallSessionStats0.setTimeMillis(9500L);
+        mVoiceCallSessionStats0.onRilSrvccStateChanged(
+                TelephonyManager.SRVCC_STATE_HANDOVER_STARTED);
+        mVoiceCallSessionStats0.setTimeMillis(10000L);
+        mVoiceCallSessionStats0.onRilSrvccStateChanged(
+                TelephonyManager.SRVCC_STATE_HANDOVER_CANCELED);
+        mVoiceCallSessionStats0.setTimeMillis(10500L);
+        mVoiceCallSessionStats0.onRilSrvccStateChanged(
+                TelephonyManager.SRVCC_STATE_HANDOVER_STARTED);
+        mVoiceCallSessionStats0.setTimeMillis(11000L);
+        mVoiceCallSessionStats0.onRilSrvccStateChanged(
+                TelephonyManager.SRVCC_STATE_HANDOVER_CANCELED);
+        mVoiceCallSessionStats0.setTimeMillis(12000L);
+        mVoiceCallSessionStats0.onImsCallTerminated(
+                mImsConnection0, new ImsReasonInfo(ImsReasonInfo.CODE_USER_TERMINATED, 0));
+
+        ArgumentCaptor<VoiceCallSession> callCaptor =
+                ArgumentCaptor.forClass(VoiceCallSession.class);
+        verify(mPersistAtomsStorage, times(1)).addVoiceCallSession(callCaptor.capture());
+        verify(mPersistAtomsStorage, times(1)).addVoiceCallRatUsage(any());
+        verifyNoMoreInteractions(mPersistAtomsStorage);
+        assertProtoEquals(expectedCall, callCaptor.getValue());
+        assertThat(ratUsage.get()).hasLength(1);
+        assertProtoEquals(expectedRatUsage, ratUsage.get()[0]);
+    }
+
+    @Test
+    @SmallTest
+    public void singleCall_srvccSuccess() {
+        doReturn(TelephonyManager.NETWORK_TYPE_LTE).when(mServiceState).getVoiceNetworkType();
+        doReturn(true).when(mImsConnection0).isIncoming();
+        doReturn(2000L).when(mImsConnection0).getCreateTime();
+        doReturn(mImsCall0).when(mImsConnection0).getCall();
+        doReturn(new ArrayList(List.of(mImsConnection0))).when(mImsCall0).getConnections();
+        doReturn(mImsPhone).when(mPhone).getImsPhone();
+        doReturn(new ArrayList(List.of(mImsConnection0))).when(mImsPhone).getHandoverConnection();
+        doReturn(2000L).when(mGsmConnection0).getCreateTime();
+        doReturn(mCsCall0).when(mGsmConnection0).getCall();
+        VoiceCallSession expectedCall =
+                makeSlot0CallProto(
+                        VOICE_CALL_SESSION__BEARER_AT_END__CALL_BEARER_IMS,
+                        VOICE_CALL_SESSION__DIRECTION__CALL_DIRECTION_MT,
+                        TelephonyManager.NETWORK_TYPE_LTE,
+                        DisconnectCause.NORMAL);
+        expectedCall.disconnectExtraCode = PreciseDisconnectCause.NORMAL;
+        expectedCall.setupDuration =
+                VOICE_CALL_SESSION__SETUP_DURATION__CALL_SETUP_DURATION_ULTRA_FAST;
+        expectedCall.setupFailed = false;
+        expectedCall.srvccCancellationCount = 1L;
+        expectedCall.srvccFailureCount = 1L;
+        expectedCall.srvccCompleted = true;
+        expectedCall.bearerAtEnd = VOICE_CALL_SESSION__BEARER_AT_END__CALL_BEARER_CS;
+        expectedCall.ratSwitchCount = 1L;
+        expectedCall.ratAtEnd = TelephonyManager.NETWORK_TYPE_UMTS;
+        expectedCall.codecBitmask = 1L << AudioCodec.AUDIO_CODEC_AMR;
+        RawVoiceCallRatUsage expectedRatUsageLte =
+                makeRatUsageProto(
+                        CARRIER_ID_SLOT_0, TelephonyManager.NETWORK_TYPE_LTE, 2000L, 7000L, 1L);
+        RawVoiceCallRatUsage expectedRatUsageUmts =
+                makeRatUsageProto(
+                        CARRIER_ID_SLOT_0, TelephonyManager.NETWORK_TYPE_UMTS, 7000L, 12000L, 1L);
+        final AtomicReference<RawVoiceCallRatUsage[]> ratUsage = setupRatUsageCapture();
+
+        // IMS call created
+        mVoiceCallSessionStats0.setTimeMillis(2000L);
+        doReturn(Call.State.INCOMING).when(mImsCall0).getState();
+        doReturn(Call.State.INCOMING).when(mImsConnection0).getState();
+        mVoiceCallSessionStats0.onImsCallReceived(mImsConnection0);
+        mVoiceCallSessionStats0.setTimeMillis(2100L);
+        mVoiceCallSessionStats0.onAudioCodecChanged(
+                mImsConnection0, ImsStreamMediaProfile.AUDIO_QUALITY_AMR);
+        mVoiceCallSessionStats0.setTimeMillis(2200L);
+        mVoiceCallSessionStats0.onImsAcceptCall(List.of(mImsConnection0));
+        mVoiceCallSessionStats0.setTimeMillis(2280L);
+        doReturn(Call.State.ACTIVE).when(mImsCall0).getState();
+        doReturn(Call.State.ACTIVE).when(mImsConnection0).getState();
+        mVoiceCallSessionStats0.onCallStateChanged(mImsCall0);
+        // canceled SRVCC attempt
+        mVoiceCallSessionStats0.setTimeMillis(4500L);
+        mVoiceCallSessionStats0.onRilSrvccStateChanged(
+                TelephonyManager.SRVCC_STATE_HANDOVER_STARTED);
+        mVoiceCallSessionStats0.setTimeMillis(5000L);
+        mVoiceCallSessionStats0.onRilSrvccStateChanged(
+                TelephonyManager.SRVCC_STATE_HANDOVER_CANCELED);
+        // failed SRVCC attempt
+        mVoiceCallSessionStats0.setTimeMillis(6500L);
+        mVoiceCallSessionStats0.onRilSrvccStateChanged(
+                TelephonyManager.SRVCC_STATE_HANDOVER_STARTED);
+        mVoiceCallSessionStats0.setTimeMillis(7000L);
+        doReturn(TelephonyManager.NETWORK_TYPE_UMTS).when(mServiceState).getVoiceNetworkType();
+        mVoiceCallSessionStats0.onServiceStateChanged(mServiceState);
+        mVoiceCallSessionStats0.setTimeMillis(8000L);
+        mVoiceCallSessionStats0.onRilSrvccStateChanged(
+                TelephonyManager.SRVCC_STATE_HANDOVER_FAILED);
+        // successful SRVCC attempt
+        mVoiceCallSessionStats0.setTimeMillis(9000L);
+        mVoiceCallSessionStats0.onRilSrvccStateChanged(
+                TelephonyManager.SRVCC_STATE_HANDOVER_COMPLETED);
+        // CS call terminated
+        mVoiceCallSessionStats0.setTimeMillis(12000L);
+        doReturn(DisconnectCause.NORMAL).when(mGsmConnection0).getDisconnectCause();
+        doReturn(PreciseDisconnectCause.NORMAL).when(mGsmConnection0).getPreciseDisconnectCause();
+        mVoiceCallSessionStats0.onRilCallListChanged(List.of(mGsmConnection0));
+
+        ArgumentCaptor<VoiceCallSession> callCaptor =
+                ArgumentCaptor.forClass(VoiceCallSession.class);
+        verify(mPersistAtomsStorage, times(1)).addVoiceCallSession(callCaptor.capture());
+        verify(mPersistAtomsStorage, times(1)).addVoiceCallRatUsage(any());
+        verifyNoMoreInteractions(mPersistAtomsStorage);
+        assertProtoEquals(expectedCall, callCaptor.getValue());
+        assertSortedProtoArrayEquals(
+                new RawVoiceCallRatUsage[] {expectedRatUsageLte, expectedRatUsageUmts},
+                ratUsage.get());
+    }
+
+    @Test
+    @SmallTest
+    public void concurrentCalls_srvcc() {
+        doReturn(TelephonyManager.NETWORK_TYPE_LTE).when(mServiceState).getVoiceNetworkType();
+        doReturn(mImsPhone).when(mPhone).getImsPhone();
+        doReturn(new ArrayList(List.of(mImsConnection0, mImsConnection1)))
+                .when(mImsPhone).getHandoverConnection();
+        // call 0 starts first, MO
+        doReturn(false).when(mImsConnection0).isIncoming();
+        doReturn(2000L).when(mImsConnection0).getCreateTime();
+        doReturn(mImsCall0).when(mImsConnection0).getCall();
+        doReturn(new ArrayList(List.of(mImsConnection0))).when(mImsCall0).getConnections();
+        doReturn(2000L).when(mGsmConnection0).getCreateTime();
+        doReturn(mCsCall0).when(mGsmConnection0).getCall();
+        VoiceCallSession expectedCall0 =
+                makeSlot0CallProto(
+                        VOICE_CALL_SESSION__BEARER_AT_END__CALL_BEARER_IMS,
+                        VOICE_CALL_SESSION__DIRECTION__CALL_DIRECTION_MO,
+                        TelephonyManager.NETWORK_TYPE_LTE,
+                        DisconnectCause.NORMAL);
+        expectedCall0.disconnectExtraCode = PreciseDisconnectCause.NORMAL;
+        expectedCall0.setupDuration =
+                VOICE_CALL_SESSION__SETUP_DURATION__CALL_SETUP_DURATION_ULTRA_FAST;
+        expectedCall0.setupFailed = false;
+        expectedCall0.codecBitmask = 1L << AudioCodec.AUDIO_CODEC_AMR;
+        expectedCall0.concurrentCallCountAtStart = 0;
+        expectedCall0.concurrentCallCountAtEnd = 1;
+        expectedCall0.ratSwitchCount = 1L;
+        expectedCall0.ratAtEnd = TelephonyManager.NETWORK_TYPE_UMTS;
+        expectedCall0.srvccCompleted = true;
+        expectedCall0.bearerAtEnd = VOICE_CALL_SESSION__BEARER_AT_END__CALL_BEARER_CS;
+        // call 1 starts later, MT
+        doReturn(true).when(mImsConnection1).isIncoming();
+        doReturn(60000L).when(mImsConnection1).getCreateTime();
+        doReturn(mImsCall1).when(mImsConnection1).getCall();
+        doReturn(new ArrayList(List.of(mImsConnection1))).when(mImsCall1).getConnections();
+        doReturn(60000L).when(mGsmConnection1).getCreateTime();
+        doReturn(mCsCall1).when(mGsmConnection1).getCall();
+        VoiceCallSession expectedCall1 =
+                makeSlot0CallProto(
+                        VOICE_CALL_SESSION__BEARER_AT_END__CALL_BEARER_IMS,
+                        VOICE_CALL_SESSION__DIRECTION__CALL_DIRECTION_MT,
+                        TelephonyManager.NETWORK_TYPE_LTE,
+                        DisconnectCause.NORMAL);
+        expectedCall1.disconnectExtraCode = PreciseDisconnectCause.NORMAL;
+        expectedCall1.setupDuration =
+                VOICE_CALL_SESSION__SETUP_DURATION__CALL_SETUP_DURATION_EXTREMELY_FAST;
+        expectedCall1.setupFailed = false;
+        expectedCall1.codecBitmask = 1L << AudioCodec.AUDIO_CODEC_AMR;
+        expectedCall1.concurrentCallCountAtStart = 1;
+        expectedCall1.concurrentCallCountAtEnd = 0;
+        expectedCall1.ratSwitchCount = 1L;
+        expectedCall1.ratAtEnd = TelephonyManager.NETWORK_TYPE_UMTS;
+        expectedCall1.srvccCompleted = true;
+        expectedCall1.bearerAtEnd = VOICE_CALL_SESSION__BEARER_AT_END__CALL_BEARER_CS;
+        RawVoiceCallRatUsage expectedRatUsageLte =
+                makeRatUsageProto(
+                        CARRIER_ID_SLOT_0, TelephonyManager.NETWORK_TYPE_LTE, 2000L, 80000L, 2L);
+        RawVoiceCallRatUsage expectedRatUsageUmts =
+                makeRatUsageProto(
+                        CARRIER_ID_SLOT_0, TelephonyManager.NETWORK_TYPE_UMTS, 80000L, 120000L, 2L);
+        final AtomicReference<RawVoiceCallRatUsage[]> ratUsage = setupRatUsageCapture();
+
+        // call 0 dial
+        mVoiceCallSessionStats0.setTimeMillis(2000L);
+        doReturn(Call.State.DIALING).when(mImsCall0).getState();
+        doReturn(Call.State.DIALING).when(mImsConnection0).getState();
+        mVoiceCallSessionStats0.onImsDial(mImsConnection0);
+        mVoiceCallSessionStats0.setTimeMillis(2020L);
+        mVoiceCallSessionStats0.onAudioCodecChanged(
+                mImsConnection0, ImsStreamMediaProfile.AUDIO_QUALITY_AMR);
+        mVoiceCallSessionStats0.setTimeMillis(2080L);
+        doReturn(Call.State.ALERTING).when(mImsCall0).getState();
+        doReturn(Call.State.ALERTING).when(mImsConnection0).getState();
+        mVoiceCallSessionStats0.onCallStateChanged(mImsCall0);
+        mVoiceCallSessionStats0.setTimeMillis(4000L);
+        doReturn(Call.State.ACTIVE).when(mImsCall0).getState();
+        doReturn(Call.State.ACTIVE).when(mImsConnection0).getState();
+        mVoiceCallSessionStats0.onCallStateChanged(mImsCall0);
+        // call 1 ring
+        mVoiceCallSessionStats0.setTimeMillis(60000L);
+        doReturn(Call.State.INCOMING).when(mImsCall1).getState();
+        doReturn(Call.State.INCOMING).when(mImsConnection1).getState();
+        mVoiceCallSessionStats0.onImsCallReceived(mImsConnection1);
+        mVoiceCallSessionStats0.setTimeMillis(60100L);
+        mVoiceCallSessionStats0.onAudioCodecChanged(
+                mImsConnection1, ImsStreamMediaProfile.AUDIO_QUALITY_AMR);
+        mVoiceCallSessionStats0.setTimeMillis(65000L);
+        mVoiceCallSessionStats0.onImsAcceptCall(List.of(mImsConnection1));
+        mVoiceCallSessionStats0.setTimeMillis(65020L);
+        doReturn(Call.State.ACTIVE).when(mImsCall1).getState();
+        doReturn(Call.State.ACTIVE).when(mImsConnection1).getState();
+        mVoiceCallSessionStats0.onCallStateChanged(mImsCall1);
+        // SRVCC affecting all IMS calls
+        mVoiceCallSessionStats0.setTimeMillis(75000L);
+        mVoiceCallSessionStats0.onRilSrvccStateChanged(
+                TelephonyManager.SRVCC_STATE_HANDOVER_STARTED);
+        // RAT change, LTE to UMTS
+        mVoiceCallSessionStats0.setTimeMillis(80000L);
+        doReturn(TelephonyManager.NETWORK_TYPE_UMTS).when(mServiceState).getVoiceNetworkType();
+        mVoiceCallSessionStats0.onServiceStateChanged(mServiceState);
+        mVoiceCallSessionStats0.setTimeMillis(85000L);
+        mVoiceCallSessionStats0.onRilSrvccStateChanged(
+                TelephonyManager.SRVCC_STATE_HANDOVER_COMPLETED);
+        // call 0 hangup
+        mVoiceCallSessionStats0.setTimeMillis(90000L);
+        doReturn(DisconnectCause.NORMAL).when(mGsmConnection0).getDisconnectCause();
+        doReturn(PreciseDisconnectCause.NORMAL).when(mGsmConnection0).getPreciseDisconnectCause();
+        mVoiceCallSessionStats0.onRilCallListChanged(List.of(mGsmConnection0));
+        // call 1 hangup
+        mVoiceCallSessionStats0.setTimeMillis(120000L);
+        doReturn(DisconnectCause.NORMAL).when(mGsmConnection1).getDisconnectCause();
+        doReturn(PreciseDisconnectCause.NORMAL).when(mGsmConnection1).getPreciseDisconnectCause();
+        mVoiceCallSessionStats0.onRilCallListChanged(List.of(mGsmConnection1));
+
+        ArgumentCaptor<VoiceCallSession> callCaptor =
+                ArgumentCaptor.forClass(VoiceCallSession.class);
+        verify(mPersistAtomsStorage, times(2)).addVoiceCallSession(callCaptor.capture());
+        verify(mPersistAtomsStorage, times(1)).addVoiceCallRatUsage(any());
+        verifyNoMoreInteractions(mPersistAtomsStorage);
+        assertSortedProtoArrayEquals(
+                new VoiceCallSession[] {expectedCall0, expectedCall1},
+                callCaptor.getAllValues().stream().toArray(VoiceCallSession[]::new));
+        assertSortedProtoArrayEquals(
+                new RawVoiceCallRatUsage[] {expectedRatUsageLte, expectedRatUsageUmts},
+                ratUsage.get());
+    }
+
+    @Test
+    @SmallTest
+    public void singleWifiCall_preferred() {
+        doReturn(TelephonyManager.NETWORK_TYPE_LTE).when(mServiceState).getVoiceNetworkType();
+        doReturn(TelephonyManager.NETWORK_TYPE_IWLAN).when(mServiceState).getDataNetworkType();
+        doReturn(mImsPhone).when(mPhone).getImsPhone();
+        doReturn(true).when(mImsPhone).isWifiCallingEnabled();
+        doReturn(true).when(mImsConnection0).isIncoming();
+        doReturn(2000L).when(mImsConnection0).getCreateTime();
+        doReturn(mImsCall0).when(mImsConnection0).getCall();
+        doReturn(new ArrayList(List.of(mImsConnection0))).when(mImsCall0).getConnections();
+        VoiceCallSession expectedCall =
+                makeSlot0CallProto(
+                        VOICE_CALL_SESSION__BEARER_AT_END__CALL_BEARER_IMS,
+                        VOICE_CALL_SESSION__DIRECTION__CALL_DIRECTION_MT,
+                        TelephonyManager.NETWORK_TYPE_IWLAN,
+                        ImsReasonInfo.CODE_LOCAL_CALL_DECLINE);
+        expectedCall.setupFailed = true;
+        expectedCall.codecBitmask = 1L << AudioCodec.AUDIO_CODEC_AMR;
+        RawVoiceCallRatUsage expectedRatUsage =
+                makeRatUsageProto(
+                        CARRIER_ID_SLOT_0, TelephonyManager.NETWORK_TYPE_IWLAN, 2000L, 8000L, 1L);
+        final AtomicReference<RawVoiceCallRatUsage[]> ratUsage = setupRatUsageCapture();
+
+        mVoiceCallSessionStats0.setTimeMillis(2000L);
+        doReturn(Call.State.INCOMING).when(mImsCall0).getState();
+        doReturn(Call.State.INCOMING).when(mImsConnection0).getState();
+        mVoiceCallSessionStats0.onImsCallReceived(mImsConnection0);
+        mVoiceCallSessionStats0.setTimeMillis(2100L);
+        mVoiceCallSessionStats0.onAudioCodecChanged(
+                mImsConnection0, ImsStreamMediaProfile.AUDIO_QUALITY_AMR);
+        mVoiceCallSessionStats0.setTimeMillis(8000L);
+        mVoiceCallSessionStats0.onImsCallTerminated(
+                mImsConnection0, new ImsReasonInfo(ImsReasonInfo.CODE_LOCAL_CALL_DECLINE, 0));
+
+        ArgumentCaptor<VoiceCallSession> callCaptor =
+                ArgumentCaptor.forClass(VoiceCallSession.class);
+        verify(mPersistAtomsStorage, times(1)).addVoiceCallSession(callCaptor.capture());
+        verify(mPersistAtomsStorage, times(1)).addVoiceCallRatUsage(any());
+        verifyNoMoreInteractions(mPersistAtomsStorage);
+        assertProtoEquals(expectedCall, callCaptor.getValue());
+        assertThat(ratUsage.get()).hasLength(1);
+        assertProtoEquals(expectedRatUsage, ratUsage.get()[0]);
+    }
+
+    @Test
+    @SmallTest
+    public void singleWifiCall_airPlaneMode() {
+        doReturn(TelephonyManager.NETWORK_TYPE_UNKNOWN).when(mServiceState).getVoiceNetworkType();
+        doReturn(TelephonyManager.NETWORK_TYPE_IWLAN).when(mServiceState).getDataNetworkType();
+        doReturn(mImsPhone).when(mPhone).getImsPhone();
+        doReturn(true).when(mImsPhone).isWifiCallingEnabled();
+        doReturn(true).when(mImsConnection0).isIncoming();
+        doReturn(2000L).when(mImsConnection0).getCreateTime();
+        doReturn(mImsCall0).when(mImsConnection0).getCall();
+        doReturn(new ArrayList(List.of(mImsConnection0))).when(mImsCall0).getConnections();
+        VoiceCallSession expectedCall =
+                makeSlot0CallProto(
+                        VOICE_CALL_SESSION__BEARER_AT_END__CALL_BEARER_IMS,
+                        VOICE_CALL_SESSION__DIRECTION__CALL_DIRECTION_MT,
+                        TelephonyManager.NETWORK_TYPE_IWLAN,
+                        ImsReasonInfo.CODE_LOCAL_CALL_DECLINE);
+        expectedCall.setupFailed = true;
+        expectedCall.codecBitmask = 1L << AudioCodec.AUDIO_CODEC_AMR;
+        RawVoiceCallRatUsage expectedRatUsage =
+                makeRatUsageProto(
+                        CARRIER_ID_SLOT_0, TelephonyManager.NETWORK_TYPE_IWLAN, 2000L, 8000L, 1L);
+        final AtomicReference<RawVoiceCallRatUsage[]> ratUsage = setupRatUsageCapture();
+
+        mVoiceCallSessionStats0.setTimeMillis(2000L);
+        doReturn(Call.State.INCOMING).when(mImsCall0).getState();
+        doReturn(Call.State.INCOMING).when(mImsConnection0).getState();
+        mVoiceCallSessionStats0.onImsCallReceived(mImsConnection0);
+        mVoiceCallSessionStats0.setTimeMillis(2100L);
+        mVoiceCallSessionStats0.onAudioCodecChanged(
+                mImsConnection0, ImsStreamMediaProfile.AUDIO_QUALITY_AMR);
+        mVoiceCallSessionStats0.setTimeMillis(8000L);
+        mVoiceCallSessionStats0.onImsCallTerminated(
+                mImsConnection0, new ImsReasonInfo(ImsReasonInfo.CODE_LOCAL_CALL_DECLINE, 0));
+
+        ArgumentCaptor<VoiceCallSession> callCaptor =
+                ArgumentCaptor.forClass(VoiceCallSession.class);
+        verify(mPersistAtomsStorage, times(1)).addVoiceCallSession(callCaptor.capture());
+        verify(mPersistAtomsStorage, times(1)).addVoiceCallRatUsage(any());
+        verifyNoMoreInteractions(mPersistAtomsStorage);
+        assertProtoEquals(expectedCall, callCaptor.getValue());
+        assertThat(ratUsage.get()).hasLength(1);
+        assertProtoEquals(expectedRatUsage, ratUsage.get()[0]);
+    }
+
+    private AtomicReference<RawVoiceCallRatUsage[]> setupRatUsageCapture() {
+        final AtomicReference<RawVoiceCallRatUsage[]> ratUsage = new AtomicReference<>(null);
+        doAnswer(invocation -> {
+            VoiceCallRatTracker tracker = (VoiceCallRatTracker) invocation.getArguments()[0];
+            ratUsage.set(tracker.toProto());
+            return null; // for void
+        }).when(mPersistAtomsStorage).addVoiceCallRatUsage(any());
+        return ratUsage;
+    }
+
+    private static VoiceCallSession makeSlot0CallProto(
+            int bearer, int direction, int rat, int reason) {
+        VoiceCallSession call = new VoiceCallSession();
+        call.bearerAtStart = bearer;
+        call.bearerAtEnd = bearer;
+        call.direction = direction;
+        call.setupDuration = VOICE_CALL_SESSION__SETUP_DURATION__CALL_SETUP_DURATION_UNKNOWN;
+        call.setupFailed = true;
+        call.disconnectReasonCode = reason;
+        call.disconnectExtraCode = 0;
+        call.disconnectExtraMessage = "";
+        call.ratAtStart = rat;
+        call.ratAtEnd = rat;
+        call.ratSwitchCount = 0L;
+        call.codecBitmask = 0L;
+        call.simSlotIndex = 0;
+        call.isMultiSim = false;
+        call.isEsim = false;
+        call.carrierId = CARRIER_ID_SLOT_0;
+        call.srvccCompleted = false;
+        call.srvccFailureCount = 0L;
+        call.srvccCancellationCount = 0L;
+        call.rttEnabled = false;
+        call.isEmergency = false;
+        call.isRoaming = false;
+        call.setupBeginMillis = 0L;
+        return call;
+    }
+
+    private static VoiceCallSession makeSlot1CallProto(
+            int bearer, int direction, int rat, int reason) {
+        VoiceCallSession call = new VoiceCallSession();
+        call.bearerAtStart = bearer;
+        call.bearerAtEnd = bearer;
+        call.direction = direction;
+        call.setupDuration = VOICE_CALL_SESSION__SETUP_DURATION__CALL_SETUP_DURATION_UNKNOWN;
+        call.setupFailed = true;
+        call.disconnectReasonCode = reason;
+        call.disconnectExtraCode = 0;
+        call.disconnectExtraMessage = "";
+        call.ratAtStart = rat;
+        call.ratAtEnd = rat;
+        call.ratSwitchCount = 0L;
+        call.codecBitmask = 0L;
+        call.simSlotIndex = 1;
+        call.isMultiSim = true;
+        call.isEsim = true;
+        call.carrierId = CARRIER_ID_SLOT_1;
+        call.srvccCompleted = false;
+        call.srvccFailureCount = 0L;
+        call.srvccCancellationCount = 0L;
+        call.rttEnabled = false;
+        call.isEmergency = false;
+        call.isRoaming = false;
+        call.setupBeginMillis = 0L;
+        return call;
+    }
+
+    private static RawVoiceCallRatUsage makeRatUsageProto(
+            int carrierId, int rat, long beginMillis, long endMillis, long callCount) {
+        RawVoiceCallRatUsage usage = new RawVoiceCallRatUsage();
+        usage.carrierId = carrierId;
+        usage.rat = rat;
+        usage.totalDurationMillis = endMillis - beginMillis;
+        usage.callCount = callCount;
+        return usage;
+    }
+
+    private static void assertProtoEquals(MessageNano expected, MessageNano actual) {
+        assertWithMessage(
+                        "  actual proto:\n"
+                                + actual.toString()
+                                + "  differs from expected:\n"
+                                + expected.toString())
+                .that(MessageNano.messageNanoEquals(expected, actual))
+                .isTrue();
+    }
+
+    private static final Comparator<MessageNano> sProtoComparator =
+            new Comparator<>() {
+                @Override
+                public int compare(MessageNano o1, MessageNano o2) {
+                    if (o1 == o2) {
+                        return 0;
+                    }
+                    if (o1 == null) {
+                        return -1;
+                    }
+                    if (o2 == null) {
+                        return 1;
+                    }
+                    assertThat(o1.getClass()).isEqualTo(o2.getClass());
+                    return o1.toString().compareTo(o2.toString());
+                }
+            };
+
+    private static void assertSortedProtoArrayEquals(MessageNano[] expected, MessageNano[] actual) {
+        assertThat(expected).isNotNull();
+        assertThat(actual).isNotNull();
+        assertThat(actual.length).isEqualTo(expected.length);
+        MessageNano[] sortedExpected = expected.clone();
+        MessageNano[] sortedActual = actual.clone();
+        Arrays.sort(sortedExpected, sProtoComparator);
+        Arrays.sort(sortedActual, sProtoComparator);
+        for (int i = 0; i < sortedExpected.length; i++) {
+            assertProtoEquals(sortedExpected[i], sortedActual[i]);
+        }
+    }
+}
diff --git a/tests/telephonytests/src/com/android/internal/telephony/mocks/ConnectivityServiceMock.java b/tests/telephonytests/src/com/android/internal/telephony/mocks/ConnectivityServiceMock.java
deleted file mode 100644
index ef2110d..0000000
--- a/tests/telephonytests/src/com/android/internal/telephony/mocks/ConnectivityServiceMock.java
+++ /dev/null
@@ -1,614 +0,0 @@
-/*
- * Copyright (C) 2008 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.mocks;
-
-import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET;
-import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED;
-
-import android.app.PendingIntent;
-import android.content.Context;
-import android.net.ConnectivityManager;
-import android.net.LinkProperties;
-import android.net.NetworkCapabilities;
-import android.net.NetworkFactory;
-import android.net.NetworkInfo;
-import android.net.NetworkMisc;
-import android.net.NetworkRequest;
-import android.os.Binder;
-import android.os.Handler;
-import android.os.HandlerThread;
-import android.os.IBinder;
-import android.os.Looper;
-import android.os.Message;
-import android.os.Messenger;
-import android.os.Process;
-import android.os.RemoteException;
-import android.util.Slog;
-
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.util.AsyncChannel;
-import com.android.server.connectivity.NetworkAgentInfo;
-
-import java.util.HashMap;
-
-public class ConnectivityServiceMock {
-    private static final String TAG = "ConnectivityServiceMock";
-    private static final boolean DBG = true;
-    private static final boolean VDBG = true;
-
-    /**
-     * used internally when registering NetworkFactories
-     * obj = NetworkFactoryInfo
-     */
-    private static final int EVENT_REGISTER_NETWORK_FACTORY = 17;
-
-    /**
-     * used internally when registering NetworkAgents
-     * obj = Messenger
-     */
-    private static final int EVENT_REGISTER_NETWORK_AGENT = 18;
-
-    /**
-     * used to add a network request
-     * includes a NetworkRequestInfo
-     */
-    private static final int EVENT_REGISTER_NETWORK_REQUEST = 19;
-
-    /**
-     * used to add a network listener - no request
-     * includes a NetworkRequestInfo
-     */
-    private static final int EVENT_REGISTER_NETWORK_LISTENER = 21;
-
-    /**
-     * used to remove a network request, either a listener or a real request
-     * arg1 = UID of caller
-     * obj  = NetworkRequest
-     */
-    private static final int EVENT_RELEASE_NETWORK_REQUEST = 22;
-
-    /**
-     * used internally when registering NetworkFactories
-     * obj = Messenger
-     */
-    private static final int EVENT_UNREGISTER_NETWORK_FACTORY = 23;
-
-
-    private final HandlerThread mHandlerThread;
-    /** Handler used for internal events. */
-    final private InternalHandler mHandler;
-    /** Handler used for incoming {@link NetworkStateTracker} events. */
-    final private NetworkStateTrackerHandler mTrackerHandler;
-
-    final private Context mContext;
-
-    public ConnectivityServiceMock(Context context) {
-        if (DBG) log("starting up");
-
-        mContext = context;
-        mHandlerThread = new HandlerThread("ConnectivityServiceMock");
-        mHandlerThread.start();
-        mHandler = new InternalHandler(mHandlerThread.getLooper());
-        mTrackerHandler = new NetworkStateTrackerHandler(mHandlerThread.getLooper());
-    }
-
-    public void die() {
-        // clean up threads/handlers
-        if (mHandlerThread != null) {
-            mHandlerThread.quit();
-        }
-    }
-
-    private class InternalHandler extends Handler {
-        public InternalHandler(Looper looper) {
-            super(looper);
-        }
-
-        @Override
-        public void handleMessage(Message msg) {
-            switch (msg.what) {
-                case EVENT_REGISTER_NETWORK_FACTORY: {
-                    handleRegisterNetworkFactory((NetworkFactoryInfo)msg.obj);
-                    break;
-                }
-                case EVENT_UNREGISTER_NETWORK_FACTORY: {
-                    handleUnregisterNetworkFactory((Messenger)msg.obj);
-                    break;
-                }
-                case EVENT_REGISTER_NETWORK_AGENT: {
-                    handleRegisterNetworkAgent((NetworkAgentInfo)msg.obj);
-                    break;
-                }
-                case EVENT_REGISTER_NETWORK_REQUEST:
-                case EVENT_REGISTER_NETWORK_LISTENER: {
-                    handleRegisterNetworkRequest((NetworkRequestInfo) msg.obj);
-                    break;
-                }
-                case EVENT_RELEASE_NETWORK_REQUEST: {
-                    handleReleaseNetworkRequest((NetworkRequest) msg.obj, msg.arg1);
-                    break;
-                }
-            }
-        }
-    }
-
-    private class NetworkStateTrackerHandler extends Handler {
-        public NetworkStateTrackerHandler(Looper looper) {
-            super(looper);
-        }
-
-        @Override
-        public void handleMessage(Message msg) {
-            NetworkInfo info;
-            switch (msg.what) {
-                case AsyncChannel.CMD_CHANNEL_HALF_CONNECTED: {
-                    handleAsyncChannelHalfConnect(msg);
-                    break;
-                }
-                case AsyncChannel.CMD_CHANNEL_DISCONNECT: {
-                    NetworkAgentInfo nai = mNetworkAgentInfos.get(msg.replyTo);
-                    if (nai != null) nai.asyncChannel.disconnect();
-                    break;
-                }
-                case AsyncChannel.CMD_CHANNEL_DISCONNECTED: {
-                    handleAsyncChannelDisconnected(msg);
-                    break;
-                }
-            }
-        }
-    }
-
-    private boolean isRequest(NetworkRequest request) {
-        return mNetworkRequests.get(request).isRequest;
-    }
-
-    private void handleAsyncChannelHalfConnect(Message msg) {
-        AsyncChannel ac = (AsyncChannel) msg.obj;
-        if (mNetworkFactoryInfos.containsKey(msg.replyTo)) {
-            if (msg.arg1 == AsyncChannel.STATUS_SUCCESSFUL) {
-                if (VDBG) log("NetworkFactory connected");
-                // A network factory has connected.  Send it all current NetworkRequests.
-                for (NetworkRequestInfo nri : mNetworkRequests.values()) {
-                    if (nri.isRequest == false) continue;
-                    //NetworkAgentInfo nai = mNetworkForRequestId.get(nri.request.requestId);
-                    NetworkAgentInfo nai = null;
-                    ac.sendMessage(android.net.NetworkFactory.CMD_REQUEST_NETWORK,
-                            (nai != null ? nai.getCurrentScore() : 0), 0, nri.request);
-                }
-            } else {
-                loge("Error connecting NetworkFactory");
-                mNetworkFactoryInfos.remove(msg.obj);
-            }
-        } else if (mNetworkAgentInfos.containsKey(msg.replyTo)) {
-            if (msg.arg1 == AsyncChannel.STATUS_SUCCESSFUL) {
-                if (VDBG) log("NetworkAgent connected");
-                // A network agent has requested a connection.  Establish the connection.
-                mNetworkAgentInfos.get(msg.replyTo).asyncChannel.
-                        sendMessage(AsyncChannel.CMD_CHANNEL_FULL_CONNECTION);
-            } else {
-                loge("Error connecting NetworkAgent");
-                NetworkAgentInfo nai = mNetworkAgentInfos.remove(msg.replyTo);
-                //if (nai != null) {
-                //    final boolean wasDefault = isDefaultNetwork(nai);
-                //    synchronized (mNetworkForNetId) {
-                //        mNetworkForNetId.remove(nai.network.netId);
-                //        mNetIdInUse.delete(nai.network.netId);
-                //    }
-                //    // Just in case.
-                //    mLegacyTypeTracker.remove(nai, wasDefault);
-                //}
-            }
-        }
-    }
-
-    private void handleAsyncChannelDisconnected(Message msg) {
-        NetworkAgentInfo nai = mNetworkAgentInfos.get(msg.replyTo);
-        if (nai != null) {
-            if (DBG) {
-                log(nai.name() + " got DISCONNECTED, was satisfying " + nai.numNetworkRequests());
-            }
-            // A network agent has disconnected.
-            // TODO - if we move the logic to the network agent (have them disconnect
-            // because they lost all their requests or because their score isn't good)
-            // then they would disconnect organically, report their new state and then
-            // disconnect the channel.
-            //if (nai.networkInfo.isConnected()) {
-            //    nai.networkInfo.setDetailedState(NetworkInfo.DetailedState.DISCONNECTED,
-            //            null, null);
-            //}
-            //final boolean wasDefault = isDefaultNetwork(nai);
-            //if (wasDefault) {
-            //    mDefaultInetConditionPublished = 0;
-            //}
-            //notifyIfacesChanged();
-            // TODO - we shouldn't send CALLBACK_LOST to requests that can be satisfied
-            // by other networks that are already connected. Perhaps that can be done by
-            // sending all CALLBACK_LOST messages (for requests, not listens) at the end
-            // of rematchAllNetworksAndRequests
-            //notifyNetworkCallbacks(nai, ConnectivityManager.CALLBACK_LOST);
-            //mKeepaliveTracker.handleStopAllKeepalives(nai,
-            //       ConnectivityManager.PacketKeepalive.ERROR_INVALID_NETWORK);
-            // nai.networkMonitor.sendMessage(NetworkMonitor.CMD_NETWORK_DISCONNECTED);
-            mNetworkAgentInfos.remove(msg.replyTo);
-            //updateClat(null, nai.linkProperties, nai);
-            //synchronized (mNetworkForNetId) {
-            //    // Remove the NetworkAgent, but don't mark the netId as
-            //    // available until we've told netd to delete it below.
-            //    mNetworkForNetId.remove(nai.network.netId);
-            //}
-            // Remove all previously satisfied requests.
-            //for (int i = 0; i < nai.networkRequests.size(); i++) {
-            //    NetworkRequest request = nai.networkRequests.valueAt(i);
-            //    NetworkAgentInfo currentNetwork = mNetworkForRequestId.get(request.requestId);
-            //    if (currentNetwork != null && currentNetwork.network.netId == nai.network.netId) {
-            //        mNetworkForRequestId.remove(request.requestId);
-            //        sendUpdatedScoreToFactories(request, 0);
-            //    }
-            //}
-            //if (nai.networkRequests.get(mDefaultRequest.requestId) != null) {
-            //    removeDataActivityTracking(nai);
-            //    notifyLockdownVpn(nai);
-            //    requestNetworkTransitionWakelock(nai.name());
-            //}
-            //mLegacyTypeTracker.remove(nai, wasDefault);
-            //rematchAllNetworksAndRequests(null, 0);
-            //if (nai.created) {
-            //    // Tell netd to clean up the configuration for this network
-            //    // (routing rules, DNS, etc).
-            //    // This may be slow as it requires a lot of netd shelling out to ip and
-            //    // ip[6]tables to flush routes and remove the incoming packet mark rule, so do it
-            //    // after we've rematched networks with requests which should make a potential
-            //    // fallback network the default or requested a new network from the
-            //    // NetworkFactories, so network traffic isn't interrupted for an unnecessarily
-            //    // long time.
-            //    try {
-            //        mNetd.removeNetwork(nai.network.netId);
-            //    } catch (Exception e) {
-            //        loge("Exception removing network: " + e);
-            //    }
-            //}
-            //synchronized (mNetworkForNetId) {
-            //    mNetIdInUse.delete(nai.network.netId);
-            //}
-        } else {
-            NetworkFactoryInfo nfi = mNetworkFactoryInfos.remove(msg.replyTo);
-            if (DBG && nfi != null) log("unregisterNetworkFactory for " + nfi.name);
-        }
-    }
-
-    private void log(String str) {
-        Slog.d(TAG, str);
-    }
-    private void loge(String str) {
-        Slog.e(TAG, str);
-    }
-
-    // NetworkAgentInfo keyed off its connecting messenger
-    // TODO - eval if we can reduce the number of lists/hashmaps/sparsearrays
-    // NOTE: Only should be accessed on ConnectivityServiceThread, except dump().
-    private final HashMap<Messenger, NetworkAgentInfo> mNetworkAgentInfos =
-            new HashMap<Messenger, NetworkAgentInfo>();
-    private final HashMap<Messenger, NetworkFactoryInfo> mNetworkFactoryInfos =
-            new HashMap<Messenger, NetworkFactoryInfo>();
-    private final HashMap<NetworkRequest, NetworkRequestInfo> mNetworkRequests =
-            new HashMap<NetworkRequest, NetworkRequestInfo>();
-
-    private static class NetworkFactoryInfo {
-        public final String name;
-        public final Messenger messenger;
-        public final AsyncChannel asyncChannel;
-        public final int factorySerialNumber;
-
-        NetworkFactoryInfo(String name, Messenger messenger, AsyncChannel asyncChannel,
-                int factorySerialNumber) {
-            this.name = name;
-            this.messenger = messenger;
-            this.asyncChannel = asyncChannel;
-            this.factorySerialNumber = factorySerialNumber;
-        }
-    }
-
-    private int getCallingUid() {
-        return Process.myUid();
-    }
-
-    private int getCallingPid() {
-        return Process.myPid();
-    }
-
-    private class NetworkRequestInfo implements IBinder.DeathRecipient {
-        static final boolean REQUEST = true;
-        static final boolean LISTEN = false;
-
-        final NetworkRequest request;
-        final PendingIntent mPendingIntent;
-        boolean mPendingIntentSent;
-        private final IBinder mBinder;
-        final int mPid;
-        final int mUid;
-        final Messenger messenger;
-        final boolean isRequest;
-
-        NetworkRequestInfo(NetworkRequest r, PendingIntent pi, boolean isRequest) {
-            request = r;
-            mPendingIntent = pi;
-            messenger = null;
-            mBinder = null;
-            mPid = getCallingPid();
-            mUid = getCallingUid();
-            this.isRequest = isRequest;
-        }
-
-        NetworkRequestInfo(Messenger m, NetworkRequest r, IBinder binder, boolean isRequest) {
-            super();
-            messenger = m;
-            request = r;
-            mBinder = binder;
-            mPid = getCallingPid();
-            mUid = getCallingUid();
-            this.isRequest = isRequest;
-            mPendingIntent = null;
-
-            try {
-                mBinder.linkToDeath(this, 0);
-            } catch (RemoteException e) {
-                binderDied();
-            }
-        }
-
-        void unlinkDeathRecipient() {
-            if (mBinder != null) {
-                mBinder.unlinkToDeath(this, 0);
-            }
-        }
-
-        public void binderDied() {
-            log("ConnectivityService NetworkRequestInfo binderDied(" +
-                    request + ", " + mBinder + ")");
-            releaseNetworkRequest(request);
-        }
-
-        public String toString() {
-            return (isRequest ? "Request" : "Listen") +
-                    " from uid/pid:" + mUid + "/" + mPid +
-                    " for " + request +
-                    (mPendingIntent == null ? "" : " to trigger " + mPendingIntent);
-        }
-    }
-
-
-    // sequence number of NetworkRequests
-    private int mNextNetworkRequestId = 1;
-    private synchronized int nextNetworkRequestId() {
-        return mNextNetworkRequestId++;
-    }
-
-    public NetworkRequest requestNetwork(NetworkCapabilities networkCapabilities,
-            Messenger messenger, int timeoutMs, IBinder binder, int legacyType) {
-        networkCapabilities = new NetworkCapabilities(networkCapabilities);
-
-        if (timeoutMs < 0) {
-            throw new IllegalArgumentException("Bad timeout specified");
-        }
-
-        NetworkRequest networkRequest = new NetworkRequest(networkCapabilities, legacyType,
-                nextNetworkRequestId(), NetworkRequest.Type.REQUEST);
-        NetworkRequestInfo nri = new NetworkRequestInfo(messenger, networkRequest, binder, true);
-        if (DBG) log("requestNetwork for " + nri);
-
-        mHandler.sendMessage(mHandler.obtainMessage(EVENT_REGISTER_NETWORK_REQUEST, nri));
-
-        return networkRequest;
-    }
-
-    public void releaseNetworkRequest(NetworkRequest networkRequest) {
-        mHandler.sendMessage(mHandler.obtainMessage(EVENT_RELEASE_NETWORK_REQUEST, getCallingUid(),
-                0, networkRequest));
-    }
-
-    public int registerNetworkFactory(Messenger messenger, String name) {
-        NetworkFactoryInfo nfi = new NetworkFactoryInfo(name, messenger, new AsyncChannel(),
-                NetworkFactory.SerialNumber.nextSerialNumber());
-        mHandler.sendMessage(mHandler.obtainMessage(EVENT_REGISTER_NETWORK_FACTORY, nfi));
-        return nfi.factorySerialNumber;
-    }
-
-    private void handleRegisterNetworkFactory(NetworkFactoryInfo nfi) {
-        if (DBG) log("Got NetworkFactory Messenger for " + nfi.name);
-        mNetworkFactoryInfos.put(nfi.messenger, nfi);
-        nfi.asyncChannel.connect(mContext, mTrackerHandler, nfi.messenger);
-    }
-
-    public void unregisterNetworkFactory(Messenger messenger) {
-        mHandler.sendMessage(mHandler.obtainMessage(EVENT_UNREGISTER_NETWORK_FACTORY, messenger));
-    }
-
-    private void handleUnregisterNetworkFactory(Messenger messenger) {
-        NetworkFactoryInfo nfi = mNetworkFactoryInfos.remove(messenger);
-        if (nfi == null) {
-            loge("Failed to find Messenger in unregisterNetworkFactory");
-            return;
-        }
-        if (DBG) log("unregisterNetworkFactory for " + nfi.name);
-    }
-
-    public int registerNetworkAgent(Messenger messenger, NetworkInfo networkInfo,
-            LinkProperties linkProperties, NetworkCapabilities networkCapabilities,
-            int currentScore, NetworkMisc networkMisc, int factorySerialNumber) {
-//        final NetworkAgentInfo nai = new NetworkAgentInfo(messenger, new AsyncChannel(),
-//                new Network(reserveNetId()), new NetworkInfo(networkInfo), new LinkProperties(
-//                linkProperties), new NetworkCapabilities(networkCapabilities), currentScore,
-//                mContext, mTrackerHandler, new NetworkMisc(networkMisc), mDefaultRequest, this);
-//        synchronized (this) {
-//            nai.networkMonitor.systemReady = mSystemReady;
-//        }
-//        mHandler.sendMessage(mHandler.obtainMessage(EVENT_REGISTER_NETWORK_AGENT, nai));
-//        return nai.network.netId;
-        throw new RuntimeException("not implemented");
-    }
-
-    private void handleRegisterNetworkAgent(NetworkAgentInfo na) {
-        if (VDBG) log("Got NetworkAgent Messenger");
-//        mNetworkAgentInfos.put(na.messenger, na);
-//        synchronized (mNetworkForNetId) {
-//            mNetworkForNetId.put(na.network.netId, na);
-//        }
-//        na.asyncChannel.connect(mContext, mTrackerHandler, na.messenger);
-//        NetworkInfo networkInfo = na.networkInfo;
-//        na.networkInfo = null;
-//        updateNetworkInfo(na, networkInfo);
-    }
-
-
-    private void handleRegisterNetworkRequest(NetworkRequestInfo nri) {
-        mNetworkRequests.put(nri.request, nri);
-        if (!nri.isRequest) {
-            for (NetworkAgentInfo network : mNetworkAgentInfos.values()) {
-                if (nri.request.networkCapabilities.hasSignalStrength() &&
-                        network.satisfiesImmutableCapabilitiesOf(nri.request)) {
-                }
-            }
-        }
-        rematchAllNetworksAndRequests(null, 0);
-        if (nri.isRequest) {
-            sendUpdatedScoreToFactories(nri.request, 0);
-        }
-    }
-
-    private void handleReleaseNetworkRequest(NetworkRequest request, int callingUid) {
-        NetworkRequestInfo nri = mNetworkRequests.get(request);
-        if (nri != null) {
-            if (DBG) log("releasing NetworkRequest " + request);
-            nri.unlinkDeathRecipient();
-            mNetworkRequests.remove(request);
-            if (nri.isRequest) {
-                // Find all networks that are satisfying this request and remove the request
-                // from their request lists.
-                // TODO - it's my understanding that for a request there is only a single
-                // network satisfying it, so this loop is wasteful
-                //boolean wasKept = false;
-                //for (NetworkAgentInfo nai : mNetworkAgentInfos.values()) {
-                //    if (nai.networkRequests.get(nri.request.requestId) != null) {
-                //        nai.networkRequests.remove(nri.request.requestId);
-                //        if (DBG) {
-                //            log(" Removing from current network " + nai.name() +
-                //                    ", leaving " + nai.networkRequests.size() +
-                //                    " requests.");
-                //        }
-                //        if (unneeded(nai)) {
-                //            if (DBG) log("no live requests for " + nai.name() + "; disconnecting");
-                //            teardownUnneededNetwork(nai);
-                //        } else {
-                //            // suspect there should only be one pass through here
-                //            // but if any were kept do the check below
-                //            wasKept |= true;
-                //        }
-                //    }
-                //}
-
-                //NetworkAgentInfo nai = mNetworkForRequestId.get(nri.request.requestId);
-                //if (nai != null) {
-                //    mNetworkForRequestId.remove(nri.request.requestId);
-                //}
-                // Maintain the illusion.  When this request arrived, we might have pretended
-                // that a network connected to serve it, even though the network was already
-                // connected.  Now that this request has gone away, we might have to pretend
-                // that the network disconnected.  LegacyTypeTracker will generate that
-                // phantom disconnect for this type.
-                //if (nri.request.legacyType != TYPE_NONE && nai != null) {
-                //    boolean doRemove = true;
-                //    if (wasKept) {
-                //        // check if any of the remaining requests for this network are for the
-                //        // same legacy type - if so, don't remove the nai
-                //        for (int i = 0; i < nai.networkRequests.size(); i++) {
-                //            NetworkRequest otherRequest = nai.networkRequests.valueAt(i);
-                //            if (otherRequest.legacyType == nri.request.legacyType &&
-                //                    isRequest(otherRequest)) {
-                //                if (DBG) log(" still have other legacy request - leaving");
-                //                doRemove = false;
-                //            }
-                //        }
-                //    }
-                //
-                //    if (doRemove) {
-                //        mLegacyTypeTracker.remove(nri.request.legacyType, nai, false);
-                //    }
-                //}
-
-                for (NetworkFactoryInfo nfi : mNetworkFactoryInfos.values()) {
-                    nfi.asyncChannel.sendMessage(android.net.NetworkFactory.CMD_CANCEL_REQUEST,
-                            nri.request);
-                }
-            } else {
-                // listens don't have a singular affectedNetwork.  Check all networks to see
-                // if this listen request applies and remove it.
-                //for (NetworkAgentInfo nai : mNetworkAgentInfos.values()) {
-                //    nai.networkRequests.remove(nri.request.requestId);
-                //    if (nri.request.networkCapabilities.hasSignalStrength() &&
-                //            nai.satisfiesImmutableCapabilitiesOf(nri.request)) {
-                //        updateSignalStrengthThresholds(nai, "RELEASE", nri.request);
-                //    }
-                //}
-            }
-            //callCallbackForRequest(nri, null, ConnectivityManager.CALLBACK_RELEASED);
-        }
-    }
-
-    private void sendUpdatedScoreToFactories(NetworkAgentInfo nai) {
-        for (int i = 0; i < nai.numNetworkRequests(); i++) {
-            NetworkRequest nr = nai.requestAt(i);
-            // Don't send listening requests to factories. b/17393458
-            if (!isRequest(nr)) continue;
-                sendUpdatedScoreToFactories(nr, nai.getCurrentScore());
-        }
-    }
-
-    private void sendUpdatedScoreToFactories(NetworkRequest networkRequest, int score) {
-        if (VDBG) log("sending new Min Network Score(" + score + "): " + networkRequest.toString());
-        for (NetworkFactoryInfo nfi : mNetworkFactoryInfos.values()) {
-            nfi.asyncChannel.sendMessage(android.net.NetworkFactory.CMD_REQUEST_NETWORK, score, 0,
-                    networkRequest);
-        }
-    }
-
-    private void rematchAllNetworksAndRequests(NetworkAgentInfo changed, int oldScore) {
-    }
-
-    @VisibleForTesting
-    public NetworkRequest defaultRequest = null;
-    @VisibleForTesting
-    public synchronized void addDefaultRequest() {
-        if (defaultRequest != null) return;
-        NetworkCapabilities netCap = new NetworkCapabilities();
-        netCap.addCapability(NET_CAPABILITY_INTERNET);
-        netCap.addCapability(NET_CAPABILITY_NOT_RESTRICTED);
-        defaultRequest = requestNetwork(netCap, null, 0, new Binder(),
-                ConnectivityManager.TYPE_NONE);
-    }
-
-    @VisibleForTesting
-    public synchronized void setCurrentScoreForRequest(NetworkRequest nr, int score) {
-        sendUpdatedScoreToFactories(nr, score);
-    }
-
-    @VisibleForTesting
-    public synchronized void removeDefaultRequest() {
-        if (defaultRequest == null) return;
-        releaseNetworkRequest(defaultRequest);
-        defaultRequest = null;
-    }
-}
diff --git a/tests/telephonytests/src/com/android/internal/telephony/mocks/PhoneMock.java b/tests/telephonytests/src/com/android/internal/telephony/mocks/PhoneMock.java
deleted file mode 100644
index 67994ca..0000000
--- a/tests/telephonytests/src/com/android/internal/telephony/mocks/PhoneMock.java
+++ /dev/null
@@ -1,1336 +0,0 @@
-/*
- * Copyright (C) 2016 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;
-
-import android.content.Context;
-import android.net.LinkProperties;
-import android.net.NetworkCapabilities;
-import android.os.AsyncResult;
-import android.os.Handler;
-import android.os.Message;
-import android.os.Registrant;
-import android.os.RegistrantList;
-import android.os.ResultReceiver;
-import android.os.WorkSource;
-import android.service.carrier.CarrierIdentifier;
-import android.telephony.CellInfo;
-import android.telephony.CellLocation;
-import android.telephony.DataConnectionRealTimeInfo;
-import android.telephony.NetworkScanRequest;
-import android.telephony.ServiceState;
-import android.telephony.SignalStrength;
-import android.telephony.VoLteServiceState;
-
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.telephony.test.SimulatedRadioControl;
-import com.android.internal.telephony.uicc.IccCardApplicationStatus.AppType;
-import com.android.internal.telephony.uicc.IccFileHandler;
-import com.android.internal.telephony.uicc.IsimRecords;
-import com.android.internal.telephony.uicc.UiccCard;
-import com.android.internal.telephony.uicc.UsimServiceTable;
-
-import java.io.FileDescriptor;
-import java.io.PrintWriter;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Locale;
-import java.util.concurrent.atomic.AtomicBoolean;
-
-/**
- * (<em>Not for SDK use</em>)
- * A base implementation for the com.android.internal.telephony.Phone interface.
- *
- * Note that implementations of Phone.java are expected to be used
- * from a single application thread. This should be the same thread that
- * originally called PhoneFactory to obtain the interface.
- *
- *  {@hide}
- *
- */
-
-public class PhoneMock extends Phone {
-    private static final String LOG_TAG = "PhoneMock";
-
-    protected PhoneMock(String name, PhoneNotifier notifier, Context context, CommandsInterface ci,
-            boolean unitTestMode) {
-        super(name, notifier, context, ci, unitTestMode);
-        throw new RuntimeException("not implemented");
-    }
-
-    protected PhoneMock(String name, PhoneNotifier notifier, Context context, CommandsInterface ci,
-            boolean unitTestMode, int phoneId,
-            TelephonyComponentFactory telephonyComponentFactory) {
-        super(name, notifier, context, ci, unitTestMode, phoneId, telephonyComponentFactory);
-        throw new RuntimeException("not implemented");
-    }
-
-    public String getPhoneName() {
-        throw new RuntimeException("not implemented");
-    }
-
-    protected void setPhoneName(String name) {
-        throw new RuntimeException("not implemented");
-    }
-
-    public String getNai(){
-        throw new RuntimeException("not implemented");
-    }
-
-    public String getActionDetached() {
-        return "phonemock.action.detached.not.implemented";
-    }
-
-    public String getActionAttached() {
-        return "phonemock.action.attached.not.implemented";
-    }
-
-    public void setSystemProperty(String property, String value) {
-        throw new RuntimeException("not implemented");
-    }
-
-    public String getSystemProperty(String property, String defValue) {
-        throw new RuntimeException("not implemented");
-    }
-
-    protected final RegistrantList mPreciseCallStateRegistrants
-            = new RegistrantList();
-
-    protected final RegistrantList mHandoverRegistrants
-             = new RegistrantList();
-
-    protected final RegistrantList mNewRingingConnectionRegistrants
-            = new RegistrantList();
-
-    protected final RegistrantList mIncomingRingRegistrants
-            = new RegistrantList();
-
-    protected final RegistrantList mDisconnectRegistrants
-            = new RegistrantList();
-
-    protected final RegistrantList mServiceStateRegistrants
-            = new RegistrantList();
-
-    protected final RegistrantList mMmiCompleteRegistrants
-            = new RegistrantList();
-
-    protected final RegistrantList mMmiRegistrants
-            = new RegistrantList();
-
-    protected final RegistrantList mUnknownConnectionRegistrants
-            = new RegistrantList();
-
-    protected final RegistrantList mSuppServiceFailedRegistrants
-            = new RegistrantList();
-
-    protected final RegistrantList mRadioOffOrNotAvailableRegistrants
-            = new RegistrantList();
-
-    protected final RegistrantList mSimRecordsLoadedRegistrants
-            = new RegistrantList();
-
-    protected final RegistrantList mVideoCapabilityChangedRegistrants
-            = new RegistrantList();
-
-    protected final RegistrantList mEmergencyCallToggledRegistrants
-            = new RegistrantList();
-
-
-    public void startMonitoringImsService() {
-        throw new RuntimeException("not implemented");
-    }
-
-    @Override
-    public void handleMessage(Message msg) {
-        throw new RuntimeException("not implemented");
-    }
-
-    @Override
-    public boolean handleUssdRequest(String ussdRequest, ResultReceiver wrappedCallback) {
-        throw new RuntimeException("not implemented");
-    }
-
-    public ArrayList<Connection> getHandoverConnection() {
-        throw new RuntimeException("not implemented");
-    }
-
-    public void notifySrvccState(Call.SrvccState state) {
-        throw new RuntimeException("not implemented");
-    }
-
-    public void registerForSilentRedial(Handler h, int what, Object obj) {
-        throw new RuntimeException("not implemented");
-    }
-
-    public void unregisterForSilentRedial(Handler h) {
-        throw new RuntimeException("not implemented");
-    }
-
-    public Context getContext() {
-        return mContext;
-    }
-
-    protected void onUpdateIccAvailability() {
-        throw new RuntimeException("not implemented");
-    }
-
-    public void disableDnsCheck(boolean b) {
-        throw new RuntimeException("not implemented");
-    }
-
-    public boolean isDnsCheckDisabled() {
-        throw new RuntimeException("not implemented");
-    }
-
-    public void registerForPreciseCallStateChanged(Handler h, int what, Object obj) {
-        mPreciseCallStateRegistrants.addUnique(h, what, obj);
-    }
-
-    public void unregisterForPreciseCallStateChanged(Handler h) {
-        mPreciseCallStateRegistrants.remove(h);
-    }
-
-    protected void notifyPreciseCallStateChangedP() {
-        AsyncResult ar = new AsyncResult(null, this, null);
-        mPreciseCallStateRegistrants.notifyRegistrants(ar);
-
-        mNotifier.notifyPreciseCallState(this);
-    }
-
-    public void registerForHandoverStateChanged(Handler h, int what, Object obj) {
-        mHandoverRegistrants.addUnique(h, what, obj);
-    }
-
-    public void unregisterForHandoverStateChanged(Handler h) {
-        mHandoverRegistrants.remove(h);
-    }
-
-    public void notifyHandoverStateChanged(Connection cn) {
-       AsyncResult ar = new AsyncResult(null, cn, null);
-       mHandoverRegistrants.notifyRegistrants(ar);
-    }
-
-    public void migrateFrom(Phone from) {
-        throw new RuntimeException("not implemented");
-    }
-
-    public void migrate(RegistrantList to, RegistrantList from) {
-        throw new RuntimeException("not implemented");
-    }
-
-    public void registerForUnknownConnection(Handler h, int what, Object obj) {
-        mUnknownConnectionRegistrants.addUnique(h, what, obj);
-    }
-
-    public void unregisterForUnknownConnection(Handler h) {
-        mUnknownConnectionRegistrants.remove(h);
-    }
-
-    public void registerForNewRingingConnection(
-            Handler h, int what, Object obj) {
-        mNewRingingConnectionRegistrants.addUnique(h, what, obj);
-    }
-
-    public void unregisterForNewRingingConnection(Handler h) {
-        mNewRingingConnectionRegistrants.remove(h);
-    }
-
-    public void registerForVideoCapabilityChanged(
-            Handler h, int what, Object obj) {
-        mVideoCapabilityChangedRegistrants.addUnique(h, what, obj);
-        notifyForVideoCapabilityChanged(mIsVideoCapable);
-    }
-
-    public void unregisterForVideoCapabilityChanged(Handler h) {
-        mVideoCapabilityChangedRegistrants.remove(h);
-    }
-
-    public void registerForInCallVoicePrivacyOn(Handler h, int what, Object obj){
-        throw new RuntimeException("not implemented");
-    }
-
-    public void unregisterForInCallVoicePrivacyOn(Handler h){
-        throw new RuntimeException("not implemented");
-    }
-
-    public void registerForInCallVoicePrivacyOff(Handler h, int what, Object obj){
-        throw new RuntimeException("not implemented");
-    }
-
-    public void unregisterForInCallVoicePrivacyOff(Handler h){
-        throw new RuntimeException("not implemented");
-    }
-
-    public void registerForIncomingRing(
-            Handler h, int what, Object obj) {
-        mIncomingRingRegistrants.addUnique(h, what, obj);
-    }
-
-    public void unregisterForIncomingRing(Handler h) {
-        mIncomingRingRegistrants.remove(h);
-    }
-
-    public void registerForDisconnect(Handler h, int what, Object obj) {
-        mDisconnectRegistrants.addUnique(h, what, obj);
-    }
-
-    public void unregisterForDisconnect(Handler h) {
-        mDisconnectRegistrants.remove(h);
-    }
-
-    public void registerForSuppServiceFailed(Handler h, int what, Object obj) {
-        mSuppServiceFailedRegistrants.addUnique(h, what, obj);
-    }
-
-    public void unregisterForSuppServiceFailed(Handler h) {
-        mSuppServiceFailedRegistrants.remove(h);
-    }
-
-    public void registerForMmiInitiate(Handler h, int what, Object obj) {
-        mMmiRegistrants.addUnique(h, what, obj);
-    }
-
-    public void unregisterForMmiInitiate(Handler h) {
-        mMmiRegistrants.remove(h);
-    }
-
-    public void registerForMmiComplete(Handler h, int what, Object obj) {
-        mMmiCompleteRegistrants.addUnique(h, what, obj);
-    }
-
-    public void unregisterForMmiComplete(Handler h) {
-        mMmiCompleteRegistrants.remove(h);
-    }
-
-    public void registerForSimRecordsLoaded(Handler h, int what, Object obj) {
-        throw new RuntimeException("not implemented");
-    }
-
-    public void unregisterForSimRecordsLoaded(Handler h) {
-        throw new RuntimeException("not implemented");
-    }
-
-    public void registerForTtyModeReceived(Handler h, int what, Object obj) {
-        throw new RuntimeException("not implemented");
-    }
-
-    public void unregisterForTtyModeReceived(Handler h) {
-        throw new RuntimeException("not implemented");
-    }
-
-    public void setNetworkSelectionModeAutomatic(Message response) {
-        throw new RuntimeException("not implemented");
-    }
-
-    public void getNetworkSelectionMode(Message message) {
-        throw new RuntimeException("not implemented");
-    }
-
-    public void selectNetworkManually(OperatorInfo network, boolean persistSelection,
-            Message response) {
-        throw new RuntimeException("not implemented");
-    }
-
-    public void registerForEmergencyCallToggle(Handler h, int what, Object obj) {
-        Registrant r = new Registrant(h, what, obj);
-        mEmergencyCallToggledRegistrants.add(r);
-    }
-
-    public void unregisterForEmergencyCallToggle(Handler h) {
-        mEmergencyCallToggledRegistrants.remove(h);
-    }
-
-    public void restoreSavedNetworkSelection(Message response) {
-        throw new RuntimeException("not implemented");
-    }
-
-    public void saveClirSetting(int commandInterfaceCLIRMode) {
-        throw new RuntimeException("not implemented");
-    }
-
-    public void registerForServiceStateChanged(Handler h, int what, Object obj) {
-        mServiceStateRegistrants.add(h, what, obj);
-        throw new RuntimeException("not implemented");
-    }
-
-    public void unregisterForServiceStateChanged(Handler h) {
-        mServiceStateRegistrants.remove(h);
-        throw new RuntimeException("not implemented");
-    }
-
-    public void registerForRingbackTone(Handler h, int what, Object obj) {
-        throw new RuntimeException("not implemented");
-    }
-
-    public void unregisterForRingbackTone(Handler h) {
-        throw new RuntimeException("not implemented");
-    }
-
-    public void registerForOnHoldTone(Handler h, int what, Object obj) {
-        throw new RuntimeException("not implemented");
-    }
-
-    public void unregisterForOnHoldTone(Handler h) {
-        throw new RuntimeException("not implemented");
-    }
-
-    public void registerForResendIncallMute(Handler h, int what, Object obj) {
-        throw new RuntimeException("not implemented");
-    }
-
-    public void unregisterForResendIncallMute(Handler h) {
-        throw new RuntimeException("not implemented");
-    }
-
-    public void setEchoSuppressionEnabled() {
-        throw new RuntimeException("not implemented");
-    }
-
-    public SimulatedRadioControl getSimulatedRadioControl() {
-        throw new RuntimeException("not implemented");
-    }
-
-    public PhoneConstants.State getState() {
-        throw new RuntimeException("not implemented");
-    }
-
-    public IccFileHandler getIccFileHandler(){
-        throw new RuntimeException("not implemented");
-    }
-
-    public Handler getHandler() {
-        return this;
-    }
-
-    public void updatePhoneObject(int voiceRadioTech) {
-        throw new RuntimeException("not implemented");
-    }
-
-    public ServiceStateTracker getServiceStateTracker() {
-        throw new RuntimeException("not implemented");
-    }
-
-    public CallTracker getCallTracker() {
-        throw new RuntimeException("not implemented");
-    }
-
-    public AppType getCurrentUiccAppType() {
-        throw new RuntimeException("not implemented");
-    }
-
-    public IccCard getIccCard() {
-        throw new RuntimeException("not implemented");
-    }
-
-    public String getIccSerialNumber() {
-        throw new RuntimeException("not implemented");
-    }
-
-    public boolean getIccRecordsLoaded() {
-        throw new RuntimeException("not implemented");
-    }
-
-    public List<CellInfo> getAllCellInfo() {
-        throw new RuntimeException("not implemented");
-    }
-
-    public void setCellInfoListRate(int rateInMillis) {
-        throw new RuntimeException("not implemented");
-    }
-
-    public boolean getMessageWaitingIndicator() {
-        throw new RuntimeException("not implemented");
-    }
-
-    public void setVoiceCallForwardingFlag(int line, boolean enable, String number) {
-        throw new RuntimeException("not implemented");
-    }
-
-    public boolean getCallForwardingIndicator() {
-        throw new RuntimeException("not implemented");
-    }
-
-    public void queryCdmaRoamingPreference(Message response) {
-        throw new RuntimeException("not implemented");
-    }
-
-    public SignalStrength getSignalStrength() {
-        throw new RuntimeException("not implemented");
-    }
-
-    public void setCdmaRoamingPreference(int cdmaRoamingType, Message response) {
-        throw new RuntimeException("not implemented");
-    }
-
-    public void setCdmaSubscription(int cdmaSubscriptionType, Message response) {
-        throw new RuntimeException("not implemented");
-    }
-
-    public void setPreferredNetworkType(int networkType, Message response) {
-        throw new RuntimeException("not implemented");
-    }
-
-    public void getPreferredNetworkType(Message response) {
-        throw new RuntimeException("not implemented");
-    }
-
-    public void getSmscAddress(Message result) {
-        throw new RuntimeException("not implemented");
-    }
-
-    public void setSmscAddress(String address, Message result) {
-        throw new RuntimeException("not implemented");
-    }
-
-    public void setTTYMode(int ttyMode, Message onComplete) {
-        throw new RuntimeException("not implemented");
-    }
-
-    public void setUiTTYMode(int uiTtyMode, Message onComplete) {
-        throw new RuntimeException("not implemented");
-    }
-
-    public void queryTTYMode(Message onComplete) {
-        throw new RuntimeException("not implemented");
-    }
-
-    public void enableEnhancedVoicePrivacy(boolean enable, Message onComplete) {
-        throw new RuntimeException("not implemented");
-    }
-
-    public void getEnhancedVoicePrivacy(Message onComplete) {
-        throw new RuntimeException("not implemented");
-    }
-
-    public void setBandMode(int bandMode, Message response) {
-        throw new RuntimeException("not implemented");
-    }
-
-    public void queryAvailableBandMode(Message response) {
-        throw new RuntimeException("not implemented");
-    }
-
-    public void invokeOemRilRequestRaw(byte[] data, Message response) {
-        throw new RuntimeException("not implemented");
-    }
-
-    public void invokeOemRilRequestStrings(String[] strings, Message response) {
-        throw new RuntimeException("not implemented");
-    }
-
-    public void nvReadItem(int itemID, Message response) {
-        throw new RuntimeException("not implemented");
-    }
-
-    public void nvWriteItem(int itemID, String itemValue, Message response) {
-        throw new RuntimeException("not implemented");
-    }
-
-    public void nvWriteCdmaPrl(byte[] preferredRoamingList, Message response) {
-        throw new RuntimeException("not implemented");
-    }
-
-    public void nvResetConfig(int resetType, Message response) {
-        throw new RuntimeException("not implemented");
-    }
-
-    public void notifyDataActivity() {
-        throw new RuntimeException("not implemented");
-    }
-
-    public void notifyMessageWaitingIndicator() {
-        throw new RuntimeException("not implemented");
-    }
-
-    public void notifyDataConnection(String reason, String apnType,
-            PhoneConstants.DataState state) {
-        throw new RuntimeException("not implemented");
-    }
-
-    public void notifyDataConnection(String reason, String apnType) {
-        throw new RuntimeException("not implemented");
-    }
-
-    public void notifyDataConnection(String reason) {
-        throw new RuntimeException("not implemented");
-    }
-
-    public void notifyOtaspChanged(int otaspMode) {
-        throw new RuntimeException("not implemented");
-    }
-
-    public void notifySignalStrength() {
-        throw new RuntimeException("not implemented");
-    }
-
-    public void notifyCellInfo(List<CellInfo> cellInfo) {
-        throw new RuntimeException("not implemented");
-    }
-
-    public void notifyDataConnectionRealTimeInfo(DataConnectionRealTimeInfo dcRtInfo) {
-        throw new RuntimeException("not implemented");
-    }
-
-    public void notifyVoLteServiceStateChanged(VoLteServiceState lteState) {
-        throw new RuntimeException("not implemented");
-    }
-
-    private final AtomicBoolean mInEmergencyCall = new AtomicBoolean(false);
-
-    public boolean isInEmergencyCall() {
-        return mInEmergencyCall.get();
-    }
-
-    @VisibleForTesting
-    public void setInEmergencyCall(boolean value) {
-        final boolean oldValue = mInEmergencyCall.getAndSet(value);
-        if (oldValue != value) {
-            mEmergencyCallToggledRegistrants.notifyRegistrants();
-        }
-    }
-
-    private final AtomicBoolean mInEcm = new AtomicBoolean(false);
-
-    public boolean isInEcm() {
-        return mInEcm.get();
-    }
-
-    @VisibleForTesting
-    public void setInEcm(boolean value) {
-        final boolean oldValue = mInEcm.getAndSet(value);
-        if (oldValue != value) {
-            mEmergencyCallToggledRegistrants.notifyRegistrants();
-        }
-    }
-
-    public boolean isVideoCallPresent() {
-        throw new RuntimeException("not implemented");
-    }
-
-    public int getPhoneType() {
-        return PhoneConstants.PHONE_TYPE_GSM;
-    }
-
-    public int getPrecisePhoneType() {
-        throw new RuntimeException("not implemented");
-    }
-
-    public int getVoiceMessageCount(){
-        throw new RuntimeException("not implemented");
-    }
-
-    public void setVoiceMessageCount(int countWaiting) {
-        throw new RuntimeException("not implemented");
-    }
-
-    public int getCdmaEriIconIndex() {
-        throw new RuntimeException("not implemented");
-    }
-
-    public int getCdmaEriIconMode() {
-        throw new RuntimeException("not implemented");
-    }
-
-    public String getCdmaEriText() {
-        throw new RuntimeException("not implemented");
-    }
-
-    public String getCdmaMin() {
-        throw new RuntimeException("not implemented");
-    }
-
-    public boolean isMinInfoReady() {
-        throw new RuntimeException("not implemented");
-    }
-
-    public String getCdmaPrlVersion(){
-        throw new RuntimeException("not implemented");
-    }
-
-    public void sendBurstDtmf(String dtmfString, int on, int off, Message onComplete) {
-        throw new RuntimeException("not implemented");
-    }
-
-    public void exitEmergencyCallbackMode() {
-        throw new RuntimeException("not implemented");
-    }
-
-    public void registerForCdmaOtaStatusChange(Handler h, int what, Object obj) {
-        throw new RuntimeException("not implemented");
-    }
-
-    public void unregisterForCdmaOtaStatusChange(Handler h) {
-        throw new RuntimeException("not implemented");
-    }
-
-    public void registerForSubscriptionInfoReady(Handler h, int what, Object obj) {
-        throw new RuntimeException("not implemented");
-    }
-
-    public void unregisterForSubscriptionInfoReady(Handler h) {
-        throw new RuntimeException("not implemented");
-    }
-
-    public boolean needsOtaServiceProvisioning() {
-        throw new RuntimeException("not implemented");
-    }
-
-    public  boolean isOtaSpNumber(String dialStr) {
-        throw new RuntimeException("not implemented");
-    }
-
-    public void registerForCallWaiting(Handler h, int what, Object obj){
-        throw new RuntimeException("not implemented");
-    }
-
-    public void unregisterForCallWaiting(Handler h){
-        throw new RuntimeException("not implemented");
-    }
-
-    public void registerForEcmTimerReset(Handler h, int what, Object obj) {
-        throw new RuntimeException("not implemented");
-    }
-
-    public void unregisterForEcmTimerReset(Handler h) {
-        throw new RuntimeException("not implemented");
-    }
-
-    public void registerForSignalInfo(Handler h, int what, Object obj) {
-        throw new RuntimeException("not implemented");
-    }
-
-    public void unregisterForSignalInfo(Handler h) {
-        throw new RuntimeException("not implemented");
-    }
-
-    public void registerForDisplayInfo(Handler h, int what, Object obj) {
-        throw new RuntimeException("not implemented");
-    }
-
-    public void unregisterForDisplayInfo(Handler h) {
-        throw new RuntimeException("not implemented");
-    }
-
-    public void registerForNumberInfo(Handler h, int what, Object obj) {
-        throw new RuntimeException("not implemented");
-    }
-
-    public void unregisterForNumberInfo(Handler h) {
-        throw new RuntimeException("not implemented");
-    }
-
-    public void registerForRedirectedNumberInfo(Handler h, int what, Object obj) {
-        throw new RuntimeException("not implemented");
-    }
-
-    public void unregisterForRedirectedNumberInfo(Handler h) {
-        throw new RuntimeException("not implemented");
-    }
-
-    public void registerForLineControlInfo(Handler h, int what, Object obj) {
-        throw new RuntimeException("not implemented");
-    }
-
-    public void unregisterForLineControlInfo(Handler h) {
-        throw new RuntimeException("not implemented");
-    }
-
-    public void registerFoT53ClirlInfo(Handler h, int what, Object obj) {
-        throw new RuntimeException("not implemented");
-    }
-
-    public void unregisterForT53ClirInfo(Handler h) {
-        throw new RuntimeException("not implemented");
-    }
-
-    public void registerForT53AudioControlInfo(Handler h, int what, Object obj) {
-        throw new RuntimeException("not implemented");
-    }
-
-    public void unregisterForT53AudioControlInfo(Handler h) {
-        throw new RuntimeException("not implemented");
-    }
-
-    public void setOnEcbModeExitResponse(Handler h, int what, Object obj){
-        throw new RuntimeException("not implemented");
-    }
-
-    public void unsetOnEcbModeExitResponse(Handler h){
-        throw new RuntimeException("not implemented");
-    }
-
-    public void registerForRadioOffOrNotAvailable(Handler h, int what, Object obj) {
-        throw new RuntimeException("not implemented");
-    }
-
-    public void unregisterForRadioOffOrNotAvailable(Handler h) {
-        throw new RuntimeException("not implemented");
-    }
-
-    public String[] getActiveApnTypes() {
-        throw new RuntimeException("not implemented");
-    }
-
-    public boolean hasMatchedTetherApnSetting() {
-        throw new RuntimeException("not implemented");
-    }
-
-    public String getActiveApnHost(String apnType) {
-        throw new RuntimeException("not implemented");
-    }
-
-    public LinkProperties getLinkProperties(String apnType) {
-        throw new RuntimeException("not implemented");
-    }
-
-    public NetworkCapabilities getNetworkCapabilities(String apnType) {
-        throw new RuntimeException("not implemented");
-    }
-
-    public boolean isDataAllowed() {
-        throw new RuntimeException("not implemented");
-    }
-
-    public void notifyNewRingingConnectionP(Connection cn) {
-        throw new RuntimeException("not implemented");
-    }
-
-    public void notifyUnknownConnectionP(Connection cn) {
-        throw new RuntimeException("not implemented");
-    }
-
-    public void notifyForVideoCapabilityChanged(boolean isVideoCallCapable) {
-        throw new RuntimeException("not implemented");
-    }
-
-    public boolean isCspPlmnEnabled() {
-        throw new RuntimeException("not implemented");
-    }
-
-    public IsimRecords getIsimRecords() {
-        throw new RuntimeException("not implemented");
-    }
-
-    public String getMsisdn() {
-        throw new RuntimeException("not implemented");
-    }
-
-    public PhoneConstants.DataState getDataConnectionState() {
-        throw new RuntimeException("not implemented");
-    }
-
-    public void notifyCallForwardingIndicator() {
-        throw new RuntimeException("not implemented");
-    }
-
-    public void notifyDataConnectionFailed(String reason, String apnType) {
-        throw new RuntimeException("not implemented");
-    }
-
-    public void notifyPreciseDataConnectionFailed(String reason, String apnType, String apn,
-            String failCause) {
-        throw new RuntimeException("not implemented");
-    }
-
-    public int getLteOnCdmaMode() {
-        throw new RuntimeException("not implemented");
-    }
-
-    public void setVoiceMessageWaiting(int line, int countWaiting) {
-        throw new RuntimeException("not implemented");
-    }
-
-    public UsimServiceTable getUsimServiceTable() {
-        throw new RuntimeException("not implemented");
-    }
-
-    public UiccCard getUiccCard() {
-        throw new RuntimeException("not implemented");
-    }
-
-    public String[] getPcscfAddress(String apnType) {
-        throw new RuntimeException("not implemented");
-    }
-
-    public void setImsRegistrationState(boolean registered) {
-        throw new RuntimeException("not implemented");
-    }
-
-    public Phone getImsPhone() {
-        throw new RuntimeException("not implemented");
-    }
-
-    public boolean isUtEnabled() {
-        throw new RuntimeException("not implemented");
-    }
-
-    public void dispose() {
-        throw new RuntimeException("not implemented");
-    }
-
-    public int getSubId() {
-        throw new RuntimeException("not implemented");
-    }
-
-    public int getPhoneId() {
-        throw new RuntimeException("not implemented");
-    }
-
-    public int getVoicePhoneServiceState() {
-        throw new RuntimeException("not implemented");
-    }
-
-    public boolean setOperatorBrandOverride(String brand) {
-        throw new RuntimeException("not implemented");
-    }
-
-    public boolean setRoamingOverride(List<String> gsmRoamingList,
-            List<String> gsmNonRoamingList, List<String> cdmaRoamingList,
-            List<String> cdmaNonRoamingList) {
-        throw new RuntimeException("not implemented");
-    }
-
-    public boolean isMccMncMarkedAsRoaming(String mccMnc) {
-        throw new RuntimeException("not implemented");
-    }
-
-    public boolean isMccMncMarkedAsNonRoaming(String mccMnc) {
-        throw new RuntimeException("not implemented");
-    }
-
-    public boolean isSidMarkedAsRoaming(int SID) {
-        throw new RuntimeException("not implemented");
-    }
-
-    public boolean isSidMarkedAsNonRoaming(int SID) {
-        throw new RuntimeException("not implemented");
-    }
-
-    public boolean isImsRegistered() {
-        throw new RuntimeException("not implemented");
-    }
-
-    public boolean isWifiCallingEnabled() {
-        throw new RuntimeException("not implemented");
-    }
-
-    public boolean isVolteEnabled() {
-        throw new RuntimeException("not implemented");
-    }
-
-    public boolean isRadioAvailable() {
-        throw new RuntimeException("not implemented");
-    }
-
-    public boolean isRadioOn() {
-        throw new RuntimeException("not implemented");
-    }
-
-    public void shutdownRadio() {
-        throw new RuntimeException("not implemented");
-    }
-
-    public boolean isShuttingDown() {
-        throw new RuntimeException("not implemented");
-    }
-
-    public void setRadioCapability(RadioCapability rc, Message response) {
-        throw new RuntimeException("not implemented");
-    }
-
-    public int getRadioAccessFamily() {
-        throw new RuntimeException("not implemented");
-    }
-
-    public String getModemUuId() {
-        throw new RuntimeException("not implemented");
-    }
-
-    public RadioCapability getRadioCapability() {
-        throw new RuntimeException("not implemented");
-    }
-
-    public void radioCapabilityUpdated(RadioCapability rc) {
-        throw new RuntimeException("not implemented");
-    }
-
-    public void sendSubscriptionSettings(boolean restoreNetworkSelection) {
-        throw new RuntimeException("not implemented");
-    }
-
-    public void registerForRadioCapabilityChanged(Handler h, int what, Object obj) {
-        throw new RuntimeException("not implemented");
-    }
-
-    public void unregisterForRadioCapabilityChanged(Handler h) {
-        throw new RuntimeException("not implemented");
-    }
-
-    public boolean isImsUseEnabled() {
-        throw new RuntimeException("not implemented");
-    }
-
-    public boolean isVideoEnabled() {
-        throw new RuntimeException("not implemented");
-    }
-
-    public int getLceStatus() {
-        throw new RuntimeException("not implemented");
-    }
-
-    public void getModemActivityInfo(Message response)  {
-        throw new RuntimeException("not implemented");
-    }
-
-    public void setAllowedCarriers(List<CarrierIdentifier> carrierList, Message response) {
-        throw new RuntimeException("not implemented");
-    }
-
-    public void getAllowedCarriers(Message response) {
-        throw new RuntimeException("not implemented");
-    }
-
-    public void startLceAfterRadioIsAvailable() {
-        throw new RuntimeException("not implemented");
-    }
-
-    public Locale getLocaleFromSimAndCarrierPrefs() {
-        throw new RuntimeException("not implemented");
-    }
-
-    public void updateDataConnectionTracker() {
-        throw new RuntimeException("not implemented");
-    }
-
-    public void setInternalDataEnabled(boolean enable, Message onCompleteMsg) {
-        throw new RuntimeException("not implemented");
-    }
-
-    public boolean updateCurrentCarrierInProvider() {
-        throw new RuntimeException("not implemented");
-    }
-
-    public void registerForAllDataDisconnected(Handler h, int what) {
-        throw new RuntimeException("not implemented");
-    }
-
-    public void unregisterForAllDataDisconnected(Handler h) {
-        throw new RuntimeException("not implemented");
-    }
-
-    public IccSmsInterfaceManager getIccSmsInterfaceManager(){
-        throw new RuntimeException("not implemented");
-    }
-
-    public static void checkWfcWifiOnlyModeBeforeDial(Phone imsPhone, Context context) {
-        throw new RuntimeException("not implemented");
-    }
-
-    public void startRingbackTone() {
-        throw new RuntimeException("not implemented");
-    }
-
-    public void stopRingbackTone() {
-        throw new RuntimeException("not implemented");
-    }
-
-    public void callEndCleanupHandOverCallIfAny() {
-        throw new RuntimeException("not implemented");
-    }
-
-    public void cancelUSSD(Message msg) {
-        throw new RuntimeException("not implemented");
-    }
-
-    public void setBroadcastEmergencyCallStateChanges(boolean broadcast) {
-        throw new RuntimeException("not implemented");
-    }
-
-    public void sendEmergencyCallStateChange(boolean callActive) {
-        throw new RuntimeException("not implemented");
-    }
-
-    public Phone getDefaultPhone() {
-        throw new RuntimeException("not implemented");
-    }
-
-    /** From PhoneInternalInterface - man this class has alot of functions */
-    public ServiceState getServiceState() {
-        throw new RuntimeException("not implemented");
-    }
-
-    public CellLocation getCellLocation(WorkSource workSource) {
-        throw new RuntimeException("not implemented");
-    }
-
-    public DataActivityState getDataActivityState() {
-        throw new RuntimeException("not implemented");
-    }
-
-    public List<? extends MmiCode> getPendingMmiCodes() {
-        throw new RuntimeException("not implemented");
-    }
-
-    public void sendUssdResponse(String ussdMessge) {
-        throw new RuntimeException("not implemented");
-    }
-
-    public void registerForSuppServiceNotification(Handler h, int what, Object obj) {
-        throw new RuntimeException("not implemented");
-    }
-
-    public void unregisterForSuppServiceNotification(Handler h) {
-        throw new RuntimeException("not implemented");
-    }
-
-    public void acceptCall(int videoState) throws CallStateException {
-        throw new RuntimeException("not implemented");
-    }
-
-    public void rejectCall() throws CallStateException {
-        throw new RuntimeException("not implemented");
-    }
-
-    public void switchHoldingAndActive() throws CallStateException {
-        throw new RuntimeException("not implemented");
-    }
-
-    public boolean canConference() {
-        throw new RuntimeException("not implemented");
-    }
-
-    public void conference() throws CallStateException {
-        throw new RuntimeException("not implemented");
-    }
-
-    public boolean canTransfer() {
-        throw new RuntimeException("not implemented");
-    }
-
-    public void explicitCallTransfer() throws CallStateException {
-        throw new RuntimeException("not implemented");
-    }
-
-    public void clearDisconnected() {
-        throw new RuntimeException("not implemented");
-    }
-
-    public Call getForegroundCall() {
-        throw new RuntimeException("not implemented");
-    }
-
-    public Call getBackgroundCall() {
-        throw new RuntimeException("not implemented");
-    }
-
-    public Call getRingingCall() {
-        throw new RuntimeException("not implemented");
-    }
-
-    @Override
-    public Connection dial(String dialString, DialArgs dialArgs) throws CallStateException {
-        throw new RuntimeException("not implemented");
-    }
-
-    public boolean handlePinMmi(String dialString) {
-        throw new RuntimeException("not implemented");
-    }
-
-    public boolean handleUssdServiceCall(String dialString, Callback wrappedCallback) {
-        throw new RuntimeException("not implemented");
-    }
-
-    public boolean handleInCallMmiCommands(String command) throws CallStateException {
-        throw new RuntimeException("not implemented");
-    }
-
-    public void sendDtmf(char c) {
-        throw new RuntimeException("not implemented");
-    }
-
-    public void startDtmf(char c) {
-        throw new RuntimeException("not implemented");
-    }
-
-    public void stopDtmf() {
-        throw new RuntimeException("not implemented");
-    }
-
-    public void setRadioPower(boolean power) {
-        throw new RuntimeException("not implemented");
-    }
-
-    public String getLine1Number() {
-        throw new RuntimeException("not implemented");
-    }
-
-    public String getLine1AlphaTag() {
-        throw new RuntimeException("not implemented");
-    }
-
-    public boolean setLine1Number(String alphaTag, String number, Message onComplete) {
-        throw new RuntimeException("not implemented");
-    }
-
-    public String getVoiceMailNumber() {
-        throw new RuntimeException("not implemented");
-    }
-
-    public String getVoiceMailAlphaTag() {
-        throw new RuntimeException("not implemented");
-    }
-
-    public void setVoiceMailNumber(String alphaTag, String voiceMailNumber, Message onComplete) {
-        throw new RuntimeException("not implemented");
-    }
-
-    public void getCallForwardingOption(int commandInterfaceCFReason, Message onComplete) {
-        throw new RuntimeException("not implemented");
-    }
-
-    public void setCallForwardingOption(int commandInterfaceCFReason, int commandInterfaceCFAction,
-            String dialingNumber, int timerSeconds, Message onComplete) {
-        throw new RuntimeException("not implemented");
-    }
-
-    public void getCallBarring(String facility, String password, Message onComplete,
-            int serviceClass) {
-    }
-
-    public void setCallBarring(String facility, boolean lockState, String password,
-            Message onComplete, int serviceClass) {
-    }
-
-    public void getOutgoingCallerIdDisplay(Message onComplete) {
-        throw new RuntimeException("not implemented");
-    }
-
-    public void setOutgoingCallerIdDisplay(int commandInterfaceCLIRMode, Message onComplete) {
-        throw new RuntimeException("not implemented");
-    }
-
-    public void getCallWaiting(Message onComplete) {
-        throw new RuntimeException("not implemented");
-    }
-
-    public void setCallWaiting(boolean enable, Message onComplete) {
-        throw new RuntimeException("not implemented");
-    }
-
-    public void getAvailableNetworks(Message response) {
-        throw new RuntimeException("not implemented");
-    }
-
-    public void startNetworkScan(NetworkScanRequest nsr, Message response) {
-        throw new RuntimeException("not implemented");
-    }
-
-    public void stopNetworkScan(Message response) {
-        throw new RuntimeException("not implemented");
-    }
-
-    public void setOnPostDialCharacter(Handler h, int what, Object obj) {
-        throw new RuntimeException("not implemented");
-    }
-
-    public void setMute(boolean muted) {
-        throw new RuntimeException("not implemented");
-    }
-
-    public boolean getMute() {
-        throw new RuntimeException("not implemented");
-    }
-
-    public void getDataCallList(Message response) {
-        throw new RuntimeException("not implemented");
-    }
-
-    public void updateServiceLocation() {
-        throw new RuntimeException("not implemented");
-    }
-
-    public void enableLocationUpdates() {
-        throw new RuntimeException("not implemented");
-    }
-
-    public void disableLocationUpdates() {
-        throw new RuntimeException("not implemented");
-    }
-
-    public boolean getDataRoamingEnabled() {
-        throw new RuntimeException("not implemented");
-    }
-
-    public void setDataRoamingEnabled(boolean enable) {
-        throw new RuntimeException("not implemented");
-    }
-
-    public boolean isUserDataEnabled() {
-        throw new RuntimeException("not implemented");
-    }
-
-    public String getDeviceId() {
-        throw new RuntimeException("not implemented");
-    }
-
-    public String getDeviceSvn() {
-        throw new RuntimeException("not implemented");
-    }
-
-    public String getSubscriberId() {
-        throw new RuntimeException("not implemented");
-    }
-
-    public String getGroupIdLevel1() {
-        throw new RuntimeException("not implemented");
-    }
-
-    public String getGroupIdLevel2() {
-        throw new RuntimeException("not implemented");
-    }
-
-    public String getEsn() {
-        throw new RuntimeException("not implemented");
-    }
-
-    public String getMeid() {
-        throw new RuntimeException("not implemented");
-    }
-
-    public String getImei() {
-        throw new RuntimeException("not implemented");
-    }
-
-    public IccPhoneBookInterfaceManager getIccPhoneBookInterfaceManager() {
-        throw new RuntimeException("not implemented");
-    }
-
-    public void activateCellBroadcastSms(int activate, Message response) {
-        throw new RuntimeException("not implemented");
-    }
-
-    public void getCellBroadcastSmsConfig(Message response) {
-        throw new RuntimeException("not implemented");
-    }
-
-    public void setCellBroadcastSmsConfig(int[] configValuesArray, Message response) {
-        throw new RuntimeException("not implemented");
-    }
-
-    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
-        throw new RuntimeException("not implemented");
-    }
-}
diff --git a/tests/telephonytests/src/com/android/internal/telephony/mocks/PhoneSwitcherMock.java b/tests/telephonytests/src/com/android/internal/telephony/mocks/PhoneSwitcherMock.java
deleted file mode 100644
index b53a28f..0000000
--- a/tests/telephonytests/src/com/android/internal/telephony/mocks/PhoneSwitcherMock.java
+++ /dev/null
@@ -1,90 +0,0 @@
-/*
- * Copyright (C) 2006 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.mocks;
-
-import android.os.Handler;
-import android.os.Looper;
-import android.os.Registrant;
-import android.os.RegistrantList;
-
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.telephony.PhoneSwitcher;
-import com.android.internal.telephony.SubscriptionController;
-
-import java.lang.reflect.Field;
-import java.util.concurrent.atomic.AtomicBoolean;
-
-public class PhoneSwitcherMock extends PhoneSwitcher {
-    private final RegistrantList mActivePhoneRegistrants;
-    private final AtomicBoolean mIsActive[];
-
-    public PhoneSwitcherMock(int numPhones, Looper looper, SubscriptionController subController)
-            throws Exception {
-        super(numPhones, looper);
-
-        mActivePhoneRegistrants = new RegistrantList();
-        mIsActive = new AtomicBoolean[numPhones];
-        for(int i = 0; i < numPhones; i++) {
-            mIsActive[i] = new AtomicBoolean(false);
-        }
-
-        if (subController != null) {
-            Field subControllerField =
-                this.getClass().getSuperclass().getDeclaredField("mSubscriptionController");
-            subControllerField.setAccessible(true);
-            subControllerField.set(this, subController);
-        }
-    }
-
-    @Override
-    public void onRadioCapChanged(int phoneId) {
-        throw new RuntimeException("resendPhone not implemented");
-    }
-
-    @Override
-    protected boolean isPhoneActive(int phoneId) {
-        return mIsActive[phoneId].get();
-    }
-
-    @Override
-    public void registerForActivePhoneSwitch(Handler h, int what, Object o) {
-        Registrant r = new Registrant(h, what, o);
-        mActivePhoneRegistrants.add(r);
-        r.notifyRegistrant();
-    }
-
-    @Override
-    public void unregisterForActivePhoneSwitch(Handler h) {
-        mActivePhoneRegistrants.remove(h);
-    }
-
-    @VisibleForTesting
-    public void setPhoneActive(int phoneId, boolean active) {
-        validatePhoneId(phoneId);
-        if (mIsActive[phoneId].getAndSet(active) != active) {
-            notifyActivePhoneChange(phoneId);
-        }
-    }
-
-    public void setPreferredDataPhoneId(int preferredDataPhoneId) {
-        mPreferredDataPhoneId = preferredDataPhoneId;
-    }
-
-    public void notifyActivePhoneChange(int phoneId) {
-        mActivePhoneRegistrants.notifyRegistrants();
-    }
-}
diff --git a/tests/telephonytests/src/com/android/internal/telephony/mocks/SubscriptionControllerMock.java b/tests/telephonytests/src/com/android/internal/telephony/mocks/SubscriptionControllerMock.java
deleted file mode 100644
index b37c479..0000000
--- a/tests/telephonytests/src/com/android/internal/telephony/mocks/SubscriptionControllerMock.java
+++ /dev/null
@@ -1,277 +0,0 @@
-/*
- * Copyright (C) 2006 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.mocks;
-
-import static android.telephony.SubscriptionManager.DEFAULT_SUBSCRIPTION_ID;
-import static android.telephony.SubscriptionManager.INVALID_PHONE_INDEX;
-import static android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID;
-
-import android.content.Context;
-import android.content.Intent;
-import android.os.RemoteException;
-import android.os.UserHandle;
-import android.telephony.SubscriptionInfo;
-
-import com.android.internal.telephony.CommandsInterface;
-import com.android.internal.telephony.ITelephonyRegistry;
-import com.android.internal.telephony.Phone;
-import com.android.internal.telephony.PhoneConstants;
-import com.android.internal.telephony.SubscriptionController;
-import com.android.internal.telephony.TelephonyIntents;
-
-import java.io.FileDescriptor;
-import java.io.PrintWriter;
-import java.util.List;
-import java.util.concurrent.atomic.AtomicInteger;
-
-// must extend SubscriptionController as some people use it directly within-process
-public class SubscriptionControllerMock extends SubscriptionController {
-    final AtomicInteger mDefaultDataSubId = new AtomicInteger(INVALID_SUBSCRIPTION_ID);
-    final AtomicInteger mDefaultVoiceSubId = new AtomicInteger(INVALID_SUBSCRIPTION_ID);
-    final ITelephonyRegistry.Stub mTelephonyRegistry;
-    final int[][] mSlotIndexToSubId;
-
-    public static SubscriptionController init(Phone phone) {
-        throw new RuntimeException("not implemented");
-    }
-    public static SubscriptionController init(Context c, CommandsInterface[] ci) {
-        throw new RuntimeException("not implemented");
-    }
-    public static SubscriptionController getInstance() {
-        throw new RuntimeException("not implemented");
-    }
-
-    public SubscriptionControllerMock(Context c, ITelephonyRegistry.Stub tr, int phoneCount) {
-        super(c);
-        mTelephonyRegistry = tr;
-        mSlotIndexToSubId = new int[phoneCount][];
-        for (int i = 0; i < phoneCount; i++) {
-            mSlotIndexToSubId[i] = new int[1];
-            mSlotIndexToSubId[i][0] = INVALID_SUBSCRIPTION_ID;
-        }
-    }
-
-    protected void init(Context c) {
-        mContext = c;
-    }
-
-    @Override
-    public int getDefaultDataSubId() {
-        return mDefaultDataSubId.get();
-    }
-
-    @Override
-    public void setDefaultDataSubId(int subId) {
-        if (subId == DEFAULT_SUBSCRIPTION_ID) {
-            throw new RuntimeException("setDefaultDataSubId called with DEFAULT_SUB_ID");
-        }
-
-        mDefaultDataSubId.set(subId);
-        broadcastDefaultDataSubIdChanged(subId);
-    }
-
-    private void broadcastDefaultDataSubIdChanged(int subId) {
-        // Broadcast an Intent for default data sub change
-        Intent intent = new Intent(TelephonyIntents.ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED);
-        intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING);
-        intent.putExtra(PhoneConstants.SUBSCRIPTION_KEY, subId);
-        mContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL);
-    }
-
-    @Override
-    public int getSubIdUsingPhoneId(int phoneId) {
-        int[] subIds = getSubId(phoneId);
-        if (subIds == null || subIds.length == 0) {
-            return INVALID_SUBSCRIPTION_ID;
-        }
-        return subIds[0];
-    }
-
-    @Override
-    public void notifySubscriptionInfoChanged() {
-        try {
-            mTelephonyRegistry.notifySubscriptionInfoChanged();
-        } catch (RemoteException ex) {}
-    }
-    @Override
-    public SubscriptionInfo getActiveSubscriptionInfo(int subId, String callingPackage) {
-        throw new RuntimeException("not implemented");
-    }
-    @Override
-    public SubscriptionInfo getActiveSubscriptionInfoForIccId(String iccId, String callingPackage) {
-        throw new RuntimeException("not implemented");
-    }
-    @Override
-    public SubscriptionInfo getActiveSubscriptionInfoForSimSlotIndex(int slotIndex, String cp){
-        throw new RuntimeException("not implemented");
-    }
-    @Override
-    public List<SubscriptionInfo> getAllSubInfoList(String callingPackage) {
-        throw new RuntimeException("not implemented");
-    }
-    @Override
-    public List<SubscriptionInfo> getActiveSubscriptionInfoList(String callingPackage) {
-        throw new RuntimeException("not implemented");
-    }
-    @Override
-    public int getActiveSubInfoCount(String callingPackage) {
-        throw new RuntimeException("not implemented");
-    }
-    @Override
-    public int getAllSubInfoCount(String callingPackage) {
-        throw new RuntimeException("not implemented");
-    }
-    @Override
-    public int getActiveSubInfoCountMax() {
-        throw new RuntimeException("not implemented");
-    }
-    @Override
-    public int addSubInfoRecord(String iccId, int slotIndex) {
-        throw new RuntimeException("not implemented");
-    }
-    @Override
-    public int addSubInfo(String uniqueId, String displayName, int slotIndex,
-            int subscriptionType) {
-        throw new RuntimeException("not implemented");
-    }
-    @Override
-    public int removeSubInfo(String uniqueId, int subscriptionType) {
-        throw new RuntimeException("not implemented");
-    }
-    @Override
-    public boolean setPlmnSpn(int slotIndex, boolean showPlmn, String plmn, boolean showSpn,
-            String spn) {
-        throw new RuntimeException("not implemented");
-    }
-    @Override
-    public int setIconTint(int tint, int subId) {
-        throw new RuntimeException("not implemented");
-    }
-    @Override
-    public int setDisplayNameUsingSrc(String displayName, int subId, int nameSource) {
-        throw new RuntimeException("not implemented");
-    }
-    @Override
-    public int setDisplayNumber(String number, int subId) {
-        throw new RuntimeException("not implemented");
-    }
-    @Override
-    public int setDataRoaming(int roaming, int subId) {
-        throw new RuntimeException("not implemented");
-    }
-    @Override
-    public int setMccMnc(String mccMnc, int subId) {
-        throw new RuntimeException("not implemented");
-    }
-    @Override
-    public int getSlotIndex(int subId) {
-        throw new RuntimeException("not implemented");
-    }
-
-    private boolean isInvalidslotIndex(int slotIndex) {
-        if (slotIndex < 0 || slotIndex >= mSlotIndexToSubId.length) return true;
-        return false;
-    }
-
-    @Override
-    public int[] getSubId(int slotIndex) {
-        if (isInvalidslotIndex(slotIndex)) {
-            return null;
-        }
-        return mSlotIndexToSubId[slotIndex];
-    }
-    public void setSlotSubId(int slotIndex, int subId) {
-        if (isInvalidslotIndex(slotIndex)) {
-            throw new RuntimeException("invalid slot specified" + slotIndex);
-        }
-        if (mSlotIndexToSubId[slotIndex][0] != subId) {
-            mSlotIndexToSubId[slotIndex][0] = subId;
-            try {
-                mTelephonyRegistry.notifySubscriptionInfoChanged();
-            } catch (RemoteException ex) {}
-        }
-    }
-    @Override
-    public int getPhoneId(int subId) {
-        if (subId <= INVALID_SUBSCRIPTION_ID) return INVALID_PHONE_INDEX;
-
-        for (int i = 0; i < mSlotIndexToSubId.length; i++) {
-            if (mSlotIndexToSubId[i][0] == subId) return i;
-        }
-        return INVALID_PHONE_INDEX;
-    }
-    @Override
-    public int clearSubInfo() {
-        throw new RuntimeException("not implemented");
-    }
-    @Override
-    public int getDefaultSubId() {
-        throw new RuntimeException("not implemented");
-    }
-    @Override
-    public void setDefaultSmsSubId(int subId) {
-        throw new RuntimeException("not implemented");
-    }
-    @Override
-    public int getDefaultSmsSubId() {
-        throw new RuntimeException("not implemented");
-    }
-    @Override
-    public void setDefaultVoiceSubId(int subId) {
-        if (subId == DEFAULT_SUBSCRIPTION_ID) {
-            throw new RuntimeException("setDefaultDataSubId called with DEFAULT_SUB_ID");
-        }
-        mDefaultVoiceSubId.set(subId);
-        broadcastDefaultVoiceSubIdChanged(subId);
-    }
-    @Override
-    public int getDefaultVoiceSubId() {
-        if (mDefaultVoiceSubId != null) {
-            return mDefaultVoiceSubId.get();
-        } else {
-            return INVALID_SUBSCRIPTION_ID;
-        }
-    }
-    @Override
-    public void updatePhonesAvailability(Phone[] phones) {
-        throw new RuntimeException("not implemented");
-    }
-    @Override
-    public int[] getActiveSubIdList(boolean visibleOnly) {
-        throw new RuntimeException("not implemented");
-    }
-    @Override
-    public boolean isActiveSubId(int subId) {
-        return getPhoneId(subId) != INVALID_PHONE_INDEX;
-    }
-    @Override
-    public int getSimStateForSlotIndex(int slotIndex) {
-        throw new RuntimeException("not implemented");
-    }
-    @Override
-    public int setSubscriptionProperty(int subId, String propKey, String propValue) {
-        throw new RuntimeException("not implemented");
-    }
-    @Override
-    public String getSubscriptionProperty(int subId, String propKey, String callingPackage) {
-        throw new RuntimeException("not implemented");
-    }
-    @Override
-    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
-        throw new RuntimeException("not implemented");
-    }
-}
diff --git a/tests/telephonytests/src/com/android/internal/telephony/mocks/SubscriptionMonitorMock.java b/tests/telephonytests/src/com/android/internal/telephony/mocks/SubscriptionMonitorMock.java
deleted file mode 100644
index 28c8620..0000000
--- a/tests/telephonytests/src/com/android/internal/telephony/mocks/SubscriptionMonitorMock.java
+++ /dev/null
@@ -1,89 +0,0 @@
-/*
- * Copyright (C) 2006 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.mocks;
-
-import android.os.Handler;
-import android.os.Registrant;
-import android.os.RegistrantList;
-
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.telephony.SubscriptionMonitor;
-
-public class SubscriptionMonitorMock extends SubscriptionMonitor {
-    private final int mNumPhones;
-    private final RegistrantList mSubscriptionsChangedRegistrants[];
-    private final RegistrantList mDefaultSubscriptionRegistrants[];
-
-    public SubscriptionMonitorMock(int numPhones) {
-        super();
-        mNumPhones = numPhones;
-        mSubscriptionsChangedRegistrants = new RegistrantList[numPhones];
-        mDefaultSubscriptionRegistrants = new RegistrantList[numPhones];
-
-        for (int i = 0; i < numPhones; i++) {
-            mSubscriptionsChangedRegistrants[i] = new RegistrantList();
-            mDefaultSubscriptionRegistrants[i] = new RegistrantList();
-        }
-    }
-
-    @Override
-    public void registerForSubscriptionChanged(int phoneId, Handler h, int what, Object o) {
-        validatePhoneId(phoneId);
-        Registrant r = new Registrant(h, what, o);
-        mSubscriptionsChangedRegistrants[phoneId].add(r);
-        r.notifyRegistrant();
-    }
-
-    @Override
-    public void unregisterForSubscriptionChanged(int phoneId, Handler h) {
-        validatePhoneId(phoneId);
-        mSubscriptionsChangedRegistrants[phoneId].remove(h);
-    }
-
-    @Override
-    public void registerForDefaultDataSubscriptionChanged(int phoneId, Handler h, int what,
-            Object o) {
-        validatePhoneId(phoneId);
-        Registrant r = new Registrant(h, what, o);
-        mDefaultSubscriptionRegistrants[phoneId].add(r);
-        r.notifyRegistrant();
-    }
-
-    @Override
-    public void unregisterForDefaultDataSubscriptionChanged(int phoneId, Handler h) {
-        validatePhoneId(phoneId);
-        mDefaultSubscriptionRegistrants[phoneId].remove(h);
-    }
-
-    @VisibleForTesting
-    public void notifySubscriptionChanged(int phoneId) {
-        validatePhoneId(phoneId);
-        mSubscriptionsChangedRegistrants[phoneId].notifyRegistrants();
-    }
-
-    @VisibleForTesting
-    public void notifyDefaultSubscriptionChanged(int phoneId) {
-        validatePhoneId(phoneId);
-        mDefaultSubscriptionRegistrants[phoneId].notifyRegistrants();
-    }
-
-    private void validatePhoneId(int phoneId) {
-        if (phoneId < 0 || phoneId >= mNumPhones) {
-            throw new IllegalArgumentException("Invalid PhoneId");
-        }
-    }
-}
diff --git a/tests/telephonytests/src/com/android/internal/telephony/mocks/TelephonyRegistryMock.java b/tests/telephonytests/src/com/android/internal/telephony/mocks/TelephonyRegistryMock.java
deleted file mode 100644
index d597505..0000000
--- a/tests/telephonytests/src/com/android/internal/telephony/mocks/TelephonyRegistryMock.java
+++ /dev/null
@@ -1,440 +0,0 @@
-/*
- * Copyright (C) 2006 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.mocks;
-
-import android.net.LinkProperties;
-import android.net.NetworkCapabilities;
-import android.os.Bundle;
-import android.os.IBinder;
-import android.os.RemoteException;
-import android.os.UserHandle;
-import android.telephony.CallQuality;
-import android.telephony.CellInfo;
-import android.telephony.DataFailCause;
-import android.telephony.PhoneCapability;
-import android.telephony.PhysicalChannelConfig;
-import android.telephony.ServiceState;
-import android.telephony.SignalStrength;
-import android.telephony.SubscriptionManager;
-import android.telephony.ims.ImsReasonInfo;
-
-import com.android.internal.telephony.IOnSubscriptionsChangedListener;
-import com.android.internal.telephony.IPhoneStateListener;
-import com.android.internal.telephony.ITelephonyRegistry;
-
-import java.util.ArrayList;
-import java.util.List;
-
-public class TelephonyRegistryMock extends ITelephonyRegistry.Stub {
-
-    private static class Record {
-        String callingPackage;
-
-        IBinder binder;
-
-        IPhoneStateListener callback;
-        IOnSubscriptionsChangedListener onSubscriptionsChangedListenerCallback;
-        IOnSubscriptionsChangedListener onOpportunisticSubscriptionsChangedListenerCallback;
-
-        int callerUserId;
-
-        int events;
-
-        int subId = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
-
-        int phoneId = SubscriptionManager.INVALID_PHONE_INDEX;
-
-        boolean canReadPhoneState;
-
-        boolean matchPhoneStateListenerEvent(int events) {
-            return (callback != null) && ((events & this.events) != 0);
-        }
-
-        boolean matchOnSubscriptionsChangedListener() {
-            return (onSubscriptionsChangedListenerCallback != null);
-        }
-
-        boolean matchOnOpportunisticSubscriptionsChangedListener() {
-            return (onOpportunisticSubscriptionsChangedListenerCallback != null);
-        }
-
-
-        @Override
-        public String toString() {
-            return "{callingPackage=" + callingPackage + " binder=" + binder
-                    + " callback=" + callback
-                    + " onSubscriptionsChangedListenerCallback="
-                    + onSubscriptionsChangedListenerCallback
-                    + " onOpportunisticSubscriptionsChangedListenerCallback="
-                    + onOpportunisticSubscriptionsChangedListenerCallback
-                    + " callerUserId=" + callerUserId + " subId=" + subId + " phoneId=" + phoneId
-                    + " events=" + Integer.toHexString(events)
-                    + " canReadPhoneState=" + canReadPhoneState + "}";
-        }
-    }
-
-    private final ArrayList<IBinder> mRemoveList = new ArrayList<IBinder>();
-    private final ArrayList<Record> mRecords = new ArrayList<Record>();
-    private boolean mHasNotifySubscriptionInfoChangedOccurred = false;
-    private boolean mHasNotifyOpportunisticSubscriptionInfoChangedOccurred = false;
-
-    public TelephonyRegistryMock() {
-    }
-
-    private void remove(IBinder binder) {
-        synchronized (mRecords) {
-            final int recordCount = mRecords.size();
-            for (int i = 0; i < recordCount; i++) {
-                if (mRecords.get(i).binder == binder) {
-                    mRecords.remove(i);
-                    return;
-                }
-            }
-        }
-    }
-
-    private void handleRemoveListLocked() {
-        int size = mRemoveList.size();
-        if (size > 0) {
-            for (IBinder b: mRemoveList) {
-                remove(b);
-            }
-            mRemoveList.clear();
-        }
-    }
-
-
-    @Override
-    public void addOnSubscriptionsChangedListener(String callingPackage,
-            IOnSubscriptionsChangedListener callback) {
-        Record r;
-
-        synchronized (mRecords) {
-            // register
-            find_and_add: {
-                IBinder b = callback.asBinder();
-                final int N = mRecords.size();
-                for (int i = 0; i < N; i++) {
-                    r = mRecords.get(i);
-                    if (b == r.binder) {
-                        break find_and_add;
-                    }
-                }
-                r = new Record();
-                r.binder = b;
-                mRecords.add(r);
-            }
-
-            r.onSubscriptionsChangedListenerCallback = callback;
-            r.callingPackage = callingPackage;
-            r.callerUserId = UserHandle.getCallingUserId();
-            r.events = 0;
-            r.canReadPhoneState = true; // permission has been enforced above
-            // Always notify when registration occurs if there has been a notification.
-            if (mHasNotifySubscriptionInfoChangedOccurred) {
-                try {
-                    r.onSubscriptionsChangedListenerCallback.onSubscriptionsChanged();
-                } catch (RemoteException e) {
-                    remove(r.binder);
-                }
-            } else {
-                //log("listen oscl: mHasNotifySubscriptionInfoChangedOccurred==false no callback");
-            }
-        }
-
-    }
-
-    @Override
-    public void addOnOpportunisticSubscriptionsChangedListener(String callingPackage,
-            IOnSubscriptionsChangedListener callback) {
-        Record r;
-
-        synchronized (mRecords) {
-            // register
-            find_and_add: {
-                IBinder b = callback.asBinder();
-                final int n = mRecords.size();
-                for (int i = 0; i < n; i++) {
-                    r = mRecords.get(i);
-                    if (b == r.binder) {
-                        break find_and_add;
-                    }
-                }
-                r = new Record();
-                r.binder = b;
-                mRecords.add(r);
-            }
-
-            r.onOpportunisticSubscriptionsChangedListenerCallback = callback;
-            r.callingPackage = callingPackage;
-            r.callerUserId = UserHandle.getCallingUserId();
-            r.events = 0;
-            r.canReadPhoneState = true; // permission has been enforced above
-            // Always notify when registration occurs if there has been a notification.
-            if (mHasNotifyOpportunisticSubscriptionInfoChangedOccurred) {
-                try {
-                    r.onOpportunisticSubscriptionsChangedListenerCallback.onSubscriptionsChanged();
-                } catch (RemoteException e) {
-                    remove(r.binder);
-                }
-            } else {
-                //log("listen oscl: mHasNotifySubscriptionInfoChangedOccurred==false no callback");
-            }
-        }
-
-    }
-
-    @Override
-    public void removeOnSubscriptionsChangedListener(String pkgForDebug,
-            IOnSubscriptionsChangedListener callback) {
-        remove(callback.asBinder());
-    }
-
-    @Override
-    public void notifySubscriptionInfoChanged() {
-        synchronized (mRecords) {
-            if (!mHasNotifySubscriptionInfoChangedOccurred) {
-                //log("notifySubscriptionInfoChanged: first invocation mRecords.size="
-                //        + mRecords.size());
-            }
-            mHasNotifySubscriptionInfoChangedOccurred = true;
-            mRemoveList.clear();
-            for (Record r : mRecords) {
-                if (r.matchOnSubscriptionsChangedListener()) {
-                    try {
-                        r.onSubscriptionsChangedListenerCallback.onSubscriptionsChanged();
-                    } catch (RemoteException ex) {
-                        mRemoveList.add(r.binder);
-                    }
-                }
-            }
-            handleRemoveListLocked();
-        }
-    }
-
-    @Override
-    public void notifyOpportunisticSubscriptionInfoChanged() {
-        synchronized (mRecords) {
-            if (!mHasNotifyOpportunisticSubscriptionInfoChangedOccurred) {
-                //log("notifySubscriptionInfoChanged: first invocation mRecords.size="
-                //        + mRecords.size());
-            }
-            mHasNotifyOpportunisticSubscriptionInfoChangedOccurred = true;
-            mRemoveList.clear();
-            for (Record r : mRecords) {
-                if (r.matchOnOpportunisticSubscriptionsChangedListener()) {
-                    try {
-                        r.onOpportunisticSubscriptionsChangedListenerCallback
-                                .onSubscriptionsChanged();
-                    } catch (RemoteException ex) {
-                        mRemoveList.add(r.binder);
-                    }
-                }
-            }
-            handleRemoveListLocked();
-        }
-    }
-
-    @Override
-    public void listen(String pkg, IPhoneStateListener callback, int events, boolean notifyNow) {
-        throw new RuntimeException("Not implemented");
-    }
-
-    @Override
-    public void listenForSubscriber(int subId, String pkg, IPhoneStateListener callback, int events,
-                                    boolean notifyNow) {
-        throw new RuntimeException("Not implemented");
-    }
-
-    @Override
-    public void notifyCallState(int state, String incomingNumber) {
-        throw new RuntimeException("Not implemented");
-    }
-
-    @Override
-    public void notifyCallStateForPhoneId(int phoneId, int subId, int state,
-                String incomingNumber) {
-        throw new RuntimeException("Not implemented");
-    }
-
-    @Override
-    public void notifyServiceStateForPhoneId(int phoneId, int subId, ServiceState state) {
-        throw new RuntimeException("Not implemented");
-    }
-
-    @Override
-    public void notifySignalStrengthForPhoneId(int phoneId, int subId,
-                SignalStrength signalStrength) {
-        throw new RuntimeException("Not implemented");
-    }
-
-    @Override
-    public void notifyMessageWaitingChangedForPhoneId(int phoneId, int subId, boolean mwi) {
-        throw new RuntimeException("Not implemented");
-    }
-
-    @Override
-    public void notifyCallForwardingChanged(boolean cfi) {
-        throw new RuntimeException("Not implemented");
-    }
-
-    @Override
-    public void notifyCallForwardingChangedForSubscriber(int subId, boolean cfi) {
-        throw new RuntimeException("Not implemented");
-    }
-
-    @Override
-    public void notifyDataActivity(int state) {
-        throw new RuntimeException("Not implemented");
-    }
-
-    @Override
-    public void notifyDataActivityForSubscriber(int subId, int state) {
-        throw new RuntimeException("Not implemented");
-    }
-
-    @Override
-    public void notifyDataConnection(int state, boolean isDataConnectivityPossible,
-            String apn, String apnType, LinkProperties linkProperties,
-            NetworkCapabilities networkCapabilities, int networkType, boolean roaming) {
-        throw new RuntimeException("Not implemented");
-    }
-
-    @Override
-    public void notifyDataConnectionForSubscriber(int phoneId, int subId, int state,
-            boolean isDataConnectivityPossible, String apn, String apnType,
-            LinkProperties linkProperties, NetworkCapabilities networkCapabilities,
-            int networkType, boolean roaming) {
-        throw new RuntimeException("Not implemented");
-    }
-
-    @Override
-    public void notifyDataConnectionFailed(String apnType) {
-        throw new RuntimeException("Not implemented");
-    }
-
-    @Override
-    public void notifyDataConnectionFailedForSubscriber(int phoneId, int subId, String apnType) {
-        throw new RuntimeException("Not implemented");
-    }
-
-    @Override
-    public void notifyCellLocation(Bundle cellLocation) {
-        throw new RuntimeException("Not implemented");
-    }
-
-    @Override
-    public void notifyCellLocationForSubscriber(int subId, Bundle cellLocation) {
-        throw new RuntimeException("Not implemented");
-    }
-
-    @Override
-    public void notifyOtaspChanged(int subId, int otaspMode) {
-        throw new RuntimeException("Not implemented");
-    }
-
-    @Override
-    public void notifyCellInfo(List<CellInfo> cellInfo) {
-        throw new RuntimeException("Not implemented");
-    }
-
-    @Override
-    public void notifyPhysicalChannelConfigurationForSubscriber(int phoneId, int subId,
-            List<PhysicalChannelConfig> configs) {
-        throw new RuntimeException("Not implemented");
-    }
-
-    @Override
-    public void notifyEmergencyNumberList(int phoneId, int subId) {
-        throw new RuntimeException("Not implemented");
-    }
-
-    @Override
-    public void notifyCallQualityChanged(CallQuality callQuality, int phoneId, int subId,
-            int callNetworkType) {
-        throw new RuntimeException("Not implemented");
-    }
-
-    @Override
-    public void notifyPreciseCallState(int phoneId, int subId, int ringingCallState,
-                                       int foregroundCallState, int backgroundCallState) {
-        throw new RuntimeException("Not implemented");
-    }
-
-    @Override
-    public void notifyDisconnectCause(int phoneId, int subId, int disconnectCause,
-                                      int preciseDisconnectCause) {
-        throw new RuntimeException("Not implemented");
-    }
-
-    @Override
-    public void notifyPreciseDataConnectionFailed(int phoneId, int subId,
-                                                  String apnType, String apn,
-                                                  @DataFailCause.FailCause int failCause) {
-        throw new RuntimeException("Not implemented");
-    }
-
-    @Override
-    public void notifyCellInfoForSubscriber(int subId, List<CellInfo> cellInfo) {
-        throw new RuntimeException("Not implemented");
-    }
-
-    @Override
-    public void notifySrvccStateChanged(int subId, int state) {
-        throw new RuntimeException("Not implemented");
-    }
-
-    @Override
-    public void notifyOemHookRawEventForSubscriber(int phoneId, int subId, byte[] rawData) {
-        throw new RuntimeException("Not implemented");
-    }
-
-    @Override
-    public void notifyCarrierNetworkChange(boolean active) {
-        throw new RuntimeException("Not implemented");
-    }
-
-    @Override
-    public void notifySimActivationStateChangedForPhoneId(int phoneId, int subId,
-                                                          int activationType, int state) {
-        throw new RuntimeException("Not implemented");
-    }
-
-    @Override
-    public void notifyUserMobileDataStateChangedForPhoneId(int phoneId, int subId, boolean state) {
-    }
-
-    @Override
-    public void notifyPhoneCapabilityChanged(PhoneCapability capability) {
-        throw new RuntimeException("Not implemented");
-    }
-
-    @Override
-    public void notifyActiveDataSubIdChanged(int subId) {
-        throw new RuntimeException("Not implemented");
-    }
-
-    @Override
-    public void notifyRadioPowerStateChanged(int phoneId, int subId, int state) {
-        throw new RuntimeException("Not implemented");
-    }
-
-    @Override
-    public void notifyImsDisconnectCause(int subId, ImsReasonInfo imsReasonInfo)  {
-        throw new RuntimeException("Not implemented");
-    }
-}
diff --git a/tests/telephonytests/src/com/android/internal/telephony/nitz/NitzSignalInputFilterPredicateFactoryTest.java b/tests/telephonytests/src/com/android/internal/telephony/nitz/NitzSignalInputFilterPredicateFactoryTest.java
new file mode 100644
index 0000000..2339f08
--- /dev/null
+++ b/tests/telephonytests/src/com/android/internal/telephony/nitz/NitzSignalInputFilterPredicateFactoryTest.java
@@ -0,0 +1,333 @@
+/*
+ * Copyright 2019 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.nitz;
+
+import static com.android.internal.telephony.nitz.NitzSignalInputFilterPredicateFactory.createBogusElapsedRealtimeCheck;
+import static com.android.internal.telephony.nitz.NitzSignalInputFilterPredicateFactory.createIgnoreNitzPropertyCheck;
+import static com.android.internal.telephony.nitz.NitzSignalInputFilterPredicateFactory.createRateLimitCheck;
+import static com.android.internal.telephony.nitz.NitzStateMachineTestSupport.UNIQUE_US_ZONE_SCENARIO1;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import android.os.TimestampedValue;
+
+import com.android.internal.telephony.NitzData;
+import com.android.internal.telephony.TelephonyTest;
+import com.android.internal.telephony.nitz.NitzSignalInputFilterPredicateFactory.NitzSignalInputFilterPredicateImpl;
+import com.android.internal.telephony.nitz.NitzSignalInputFilterPredicateFactory.TrivalentPredicate;
+import com.android.internal.telephony.nitz.NitzStateMachineTestSupport.FakeDeviceState;
+import com.android.internal.telephony.nitz.NitzStateMachineTestSupport.Scenario;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+public class NitzSignalInputFilterPredicateFactoryTest extends TelephonyTest {
+
+    private FakeDeviceState mFakeDeviceState;
+
+    @Before
+    public void setUp() throws Exception {
+        super.setUp("NitzSignalInputFilterPredicateFactoryTest");
+        mFakeDeviceState = new FakeDeviceState();
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        super.tearDown();
+    }
+
+    @Test
+    public void testNitzSignalInputFilterPredicateImpl_nullSecondArgumentRejected() {
+        Scenario scenario = UNIQUE_US_ZONE_SCENARIO1;
+        TimestampedValue<NitzData> nitzSignal =
+                scenario.createNitzSignal(mFakeDeviceState.elapsedRealtime());
+        TrivalentPredicate[] triPredicates = {};
+        NitzSignalInputFilterPredicateImpl impl =
+                new NitzSignalInputFilterPredicateImpl(triPredicates);
+        try {
+            impl.mustProcessNitzSignal(nitzSignal, null);
+            fail();
+        } catch (NullPointerException expected) {
+        }
+    }
+
+    @Test
+    public void testNitzSignalInputFilterPredicateImpl_defaultIsTrue() {
+        Scenario scenario = UNIQUE_US_ZONE_SCENARIO1;
+        TimestampedValue<NitzData> nitzSignal = scenario
+                .createNitzSignal(mFakeDeviceState.elapsedRealtime());
+        NitzSignalInputFilterPredicateImpl impl =
+                new NitzSignalInputFilterPredicateImpl(new TrivalentPredicate[0]);
+        assertTrue(impl.mustProcessNitzSignal(null, nitzSignal));
+    }
+
+    @Test
+    public void testNitzSignalInputFilterPredicateImpl_nullIsIgnored() {
+        Scenario scenario = UNIQUE_US_ZONE_SCENARIO1;
+        TimestampedValue<NitzData> nitzSignal =
+                scenario.createNitzSignal(mFakeDeviceState.elapsedRealtime());
+        TrivalentPredicate nullPredicate = (x, y) -> null;
+        TrivalentPredicate[] triPredicates = { nullPredicate };
+        NitzSignalInputFilterPredicateImpl impl =
+                new NitzSignalInputFilterPredicateImpl(triPredicates);
+        assertTrue(impl.mustProcessNitzSignal(null, nitzSignal));
+    }
+
+    @Test
+    public void testNitzSignalInputFilterPredicateImpl_trueIsHonored() {
+        Scenario scenario = UNIQUE_US_ZONE_SCENARIO1;
+        TimestampedValue<NitzData> nitzSignal =
+                scenario.createNitzSignal(mFakeDeviceState.elapsedRealtime());
+        TrivalentPredicate nullPredicate = (x, y) -> null;
+        TrivalentPredicate truePredicate = (x, y) -> true;
+        TrivalentPredicate exceptionPredicate = (x, y) -> {
+            throw new RuntimeException();
+        };
+        TrivalentPredicate[] triPredicates = {
+                nullPredicate,
+                truePredicate,
+                exceptionPredicate,
+        };
+        NitzSignalInputFilterPredicateImpl impl =
+                new NitzSignalInputFilterPredicateImpl(triPredicates);
+        assertTrue(impl.mustProcessNitzSignal(null, nitzSignal));
+    }
+
+    @Test
+    public void testNitzSignalInputFilterPredicateImpl_falseIsHonored() {
+        Scenario scenario = UNIQUE_US_ZONE_SCENARIO1;
+        TimestampedValue<NitzData> nitzSignal =
+                scenario.createNitzSignal(mFakeDeviceState.elapsedRealtime());
+        TrivalentPredicate nullPredicate = (x, y) -> null;
+        TrivalentPredicate falsePredicate = (x, y) -> false;
+        TrivalentPredicate exceptionPredicate = (x, y) -> {
+            throw new RuntimeException();
+        };
+        TrivalentPredicate[] triPredicates = {
+                nullPredicate,
+                falsePredicate,
+                exceptionPredicate,
+        };
+        NitzSignalInputFilterPredicateImpl impl =
+                new NitzSignalInputFilterPredicateImpl(triPredicates);
+        assertFalse(impl.mustProcessNitzSignal(null, nitzSignal));
+    }
+
+    @Test
+    public void testTrivalentPredicate_ignoreNitzPropertyCheck() {
+        TrivalentPredicate triPredicate = createIgnoreNitzPropertyCheck(mFakeDeviceState);
+
+        mFakeDeviceState.ignoreNitz = true;
+        assertFalse(triPredicate.mustProcessNitzSignal(null, null));
+
+        mFakeDeviceState.ignoreNitz = false;
+        assertNull(triPredicate.mustProcessNitzSignal(null, null));
+    }
+
+    @Test
+    public void testTrivalentPredicate_bogusElapsedRealtimeCheck() {
+        Scenario scenario = UNIQUE_US_ZONE_SCENARIO1;
+        long elapsedRealtimeClock = mFakeDeviceState.elapsedRealtime();
+        TimestampedValue<NitzData> nitzSignal = scenario.createNitzSignal(elapsedRealtimeClock);
+
+        TrivalentPredicate triPredicate =
+                createBogusElapsedRealtimeCheck(mContext, mFakeDeviceState);
+        assertNull(triPredicate.mustProcessNitzSignal(null, nitzSignal));
+
+        // Any signal that claims to be from the future must be rejected.
+        TimestampedValue<NitzData> bogusNitzSignal = new TimestampedValue<>(
+                elapsedRealtimeClock + 1, nitzSignal.getValue());
+        assertFalse(triPredicate.mustProcessNitzSignal(null, bogusNitzSignal));
+    }
+
+    @Test
+    public void testTrivalentPredicate_noOldSignalCheck() {
+        Scenario scenario = UNIQUE_US_ZONE_SCENARIO1;
+        TimestampedValue<NitzData> nitzSignal =
+                scenario.createNitzSignal(mFakeDeviceState.elapsedRealtime());
+
+        TrivalentPredicate triPredicate =
+                NitzSignalInputFilterPredicateFactory.createNoOldSignalCheck();
+        assertTrue(triPredicate.mustProcessNitzSignal(null, nitzSignal));
+        assertNull(triPredicate.mustProcessNitzSignal(nitzSignal, nitzSignal));
+    }
+
+    @Test
+    public void testTrivalentPredicate_rateLimitCheck_elapsedRealtime() {
+        Scenario scenario = UNIQUE_US_ZONE_SCENARIO1;
+        int nitzSpacingThreshold = mFakeDeviceState.getNitzUpdateSpacingMillis();
+        NitzData baseNitzData = scenario.createNitzData();
+
+        TrivalentPredicate triPredicate = createRateLimitCheck(mFakeDeviceState);
+
+        long baseElapsedRealtimeMillis = mFakeDeviceState.elapsedRealtime();
+        TimestampedValue<NitzData> baseSignal =
+                new TimestampedValue<>(baseElapsedRealtimeMillis, baseNitzData);
+
+        // Two identical signals: no spacing so the new signal should not be processed.
+        {
+            assertFalse(triPredicate.mustProcessNitzSignal(baseSignal, baseSignal));
+        }
+
+        // Two signals not spaced apart enough: the new signal should not processed.
+        {
+            int elapsedTimeIncrement = nitzSpacingThreshold - 1;
+            TimestampedValue<NitzData> newSignal =
+                    createIncrementedNitzSignal(baseSignal, elapsedTimeIncrement);
+            assertFalse(triPredicate.mustProcessNitzSignal(baseSignal, newSignal));
+        }
+
+        // Two signals spaced apart: the new signal should be processed.
+        {
+            int elapsedTimeIncrement = nitzSpacingThreshold + 1;
+            TimestampedValue<NitzData> newSignal =
+                    createIncrementedNitzSignal(baseSignal, elapsedTimeIncrement);
+            assertTrue(triPredicate.mustProcessNitzSignal(baseSignal, newSignal));
+        }
+    }
+
+    @Test
+    public void testTrivalentPredicate_rateLimitCheck_offsetDifference() {
+        Scenario scenario = UNIQUE_US_ZONE_SCENARIO1;
+        int nitzSpacingThreshold = mFakeDeviceState.getNitzUpdateSpacingMillis();
+        NitzData baseNitzData = scenario.createNitzData();
+
+        TrivalentPredicate triPredicate = createRateLimitCheck(mFakeDeviceState);
+
+        long baseElapsedRealtimeMillis = mFakeDeviceState.elapsedRealtime();
+        TimestampedValue<NitzData> baseSignal =
+                new TimestampedValue<>(baseElapsedRealtimeMillis, baseNitzData);
+
+        // Create a new NitzSignal that should be filtered.
+        int elapsedTimeIncrement = nitzSpacingThreshold - 1;
+        TimestampedValue<NitzData> intermediateNitzSignal =
+                createIncrementedNitzSignal(baseSignal, elapsedTimeIncrement);
+        NitzData intermediateNitzData = intermediateNitzSignal.getValue();
+        assertFalse(triPredicate.mustProcessNitzSignal(baseSignal, intermediateNitzSignal));
+
+        // Two signals spaced apart so that the second would be filtered, but they contain different
+        // offset information so should be detected as "different" and processed.
+        {
+            // Modifying the local offset should be enough to recognize the NitzData as different.
+            NitzData differentOffsetNitzData = NitzData.createForTests(
+                    intermediateNitzData.getLocalOffsetMillis() + 1,
+                    intermediateNitzData.getDstAdjustmentMillis(),
+                    intermediateNitzData.getCurrentTimeInMillis(),
+                    intermediateNitzData.getEmulatorHostTimeZone());
+            TimestampedValue<NitzData> differentOffsetSignal = new TimestampedValue<>(
+                    baseSignal.getReferenceTimeMillis() + elapsedTimeIncrement,
+                    differentOffsetNitzData);
+            assertTrue(triPredicate.mustProcessNitzSignal(baseSignal, differentOffsetSignal));
+        }
+    }
+
+    @Test
+    public void testTrivalentPredicate_rateLimitCheck_utcTimeDifferences() {
+        Scenario scenario = UNIQUE_US_ZONE_SCENARIO1;
+        int nitzSpacingThreshold = mFakeDeviceState.getNitzUpdateSpacingMillis();
+        int nitzUtcDiffThreshold = mFakeDeviceState.getNitzUpdateDiffMillis();
+        NitzData baseNitzData = scenario.createNitzData();
+
+        TrivalentPredicate triPredicate = createRateLimitCheck(mFakeDeviceState);
+
+        long baseElapsedRealtimeMillis = mFakeDeviceState.elapsedRealtime();
+        TimestampedValue<NitzData> baseSignal =
+                new TimestampedValue<>(baseElapsedRealtimeMillis, baseNitzData);
+
+        // Create a new NitzSignal that should be filtered.
+        int elapsedTimeIncrement = nitzSpacingThreshold - 1;
+        TimestampedValue<NitzData> intermediateSignal =
+                createIncrementedNitzSignal(baseSignal, elapsedTimeIncrement);
+        NitzData intermediateNitzData = intermediateSignal.getValue();
+        assertFalse(triPredicate.mustProcessNitzSignal(baseSignal, intermediateSignal));
+
+        // Two signals spaced apart so that the second would normally be filtered and it contains
+        // a UTC time that is not sufficiently different.
+        {
+            NitzData incrementedUtcTimeNitzData = NitzData.createForTests(
+                    intermediateNitzData.getLocalOffsetMillis(),
+                    intermediateNitzData.getDstAdjustmentMillis(),
+                    intermediateNitzData.getCurrentTimeInMillis() + nitzUtcDiffThreshold - 1,
+                    intermediateNitzData.getEmulatorHostTimeZone());
+
+            TimestampedValue<NitzData> incrementedNitzSignal = new TimestampedValue<>(
+                    intermediateSignal.getReferenceTimeMillis(), incrementedUtcTimeNitzData);
+            assertFalse(triPredicate.mustProcessNitzSignal(baseSignal, incrementedNitzSignal));
+        }
+
+        // Two signals spaced apart so that the second would normally be filtered but it contains
+        // a UTC time that is sufficiently different.
+        {
+            NitzData incrementedUtcTimeNitzData = NitzData.createForTests(
+                    intermediateNitzData.getLocalOffsetMillis(),
+                    intermediateNitzData.getDstAdjustmentMillis(),
+                    intermediateNitzData.getCurrentTimeInMillis() + nitzUtcDiffThreshold + 1,
+                    intermediateNitzData.getEmulatorHostTimeZone());
+
+            TimestampedValue<NitzData> incrementedNitzSignal = new TimestampedValue<>(
+                    intermediateSignal.getReferenceTimeMillis(), incrementedUtcTimeNitzData);
+            assertTrue(triPredicate.mustProcessNitzSignal(baseSignal, incrementedNitzSignal));
+        }
+
+        // Two signals spaced apart so that the second would normally be filtered and it contains
+        // a UTC time that is not sufficiently different.
+        {
+            NitzData decrementedUtcTimeNitzData = NitzData.createForTests(
+                    intermediateNitzData.getLocalOffsetMillis(),
+                    intermediateNitzData.getDstAdjustmentMillis(),
+                    intermediateNitzData.getCurrentTimeInMillis() - nitzUtcDiffThreshold + 1,
+                    intermediateNitzData.getEmulatorHostTimeZone());
+
+            TimestampedValue<NitzData> decrementedNitzSignal = new TimestampedValue<>(
+                    intermediateSignal.getReferenceTimeMillis(), decrementedUtcTimeNitzData);
+            assertFalse(triPredicate.mustProcessNitzSignal(baseSignal, decrementedNitzSignal));
+        }
+
+        // Two signals spaced apart so that the second would normally be filtered but it contains
+        // a UTC time that is sufficiently different.
+        {
+            NitzData decrementedUtcTimeNitzData = NitzData.createForTests(
+                    intermediateNitzData.getLocalOffsetMillis(),
+                    intermediateNitzData.getDstAdjustmentMillis(),
+                    intermediateNitzData.getCurrentTimeInMillis() + nitzUtcDiffThreshold + 1,
+                    intermediateNitzData.getEmulatorHostTimeZone());
+
+            TimestampedValue<NitzData> decrementedNitzSignal = new TimestampedValue<>(
+                    intermediateSignal.getReferenceTimeMillis(), decrementedUtcTimeNitzData);
+            assertTrue(triPredicate.mustProcessNitzSignal(baseSignal, decrementedNitzSignal));
+        }
+    }
+
+    /**
+     * Creates an NITZ signal based on the the supplied signal but with all the fields related to
+     * elapsed time incremented by the specified number of milliseconds.
+     */
+    private static TimestampedValue<NitzData> createIncrementedNitzSignal(
+            TimestampedValue<NitzData> baseSignal, int incrementMillis) {
+        NitzData baseData = baseSignal.getValue();
+        return new TimestampedValue<>(baseSignal.getReferenceTimeMillis() + incrementMillis,
+                NitzData.createForTests(
+                        baseData.getLocalOffsetMillis(),
+                        baseData.getDstAdjustmentMillis(),
+                        baseData.getCurrentTimeInMillis() + incrementMillis,
+                        baseData.getEmulatorHostTimeZone()));
+    }
+}
diff --git a/tests/telephonytests/src/com/android/internal/telephony/nitz/NitzStateMachineImplTest.java b/tests/telephonytests/src/com/android/internal/telephony/nitz/NitzStateMachineImplTest.java
new file mode 100644
index 0000000..19d57f9
--- /dev/null
+++ b/tests/telephonytests/src/com/android/internal/telephony/nitz/NitzStateMachineImplTest.java
@@ -0,0 +1,690 @@
+/*
+ * Copyright 2019 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.nitz;
+
+import static android.app.timezonedetector.TelephonyTimeZoneSuggestion.MATCH_TYPE_TEST_NETWORK_OFFSET_ONLY;
+import static android.app.timezonedetector.TelephonyTimeZoneSuggestion.QUALITY_MULTIPLE_ZONES_WITH_SAME_OFFSET;
+
+import static com.android.internal.telephony.nitz.NitzStateMachineTestSupport.ARBITRARY_SYSTEM_CLOCK_TIME;
+import static com.android.internal.telephony.nitz.NitzStateMachineTestSupport.UNIQUE_US_ZONE_SCENARIO1;
+import static com.android.internal.telephony.nitz.NitzStateMachineTestSupport.UNITED_KINGDOM_SCENARIO;
+import static com.android.internal.telephony.nitz.NitzStateMachineTestSupport.createEmptyTimeSuggestion;
+import static com.android.internal.telephony.nitz.NitzStateMachineTestSupport.createEmptyTimeZoneSuggestion;
+import static com.android.internal.telephony.nitz.NitzStateMachineTestSupport.createTimeSuggestionFromNitzSignal;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+import android.app.timedetector.TelephonyTimeSuggestion;
+import android.app.timezonedetector.TelephonyTimeZoneSuggestion;
+import android.os.TimestampedValue;
+
+import com.android.internal.telephony.IndentingPrintWriter;
+import com.android.internal.telephony.NitzData;
+import com.android.internal.telephony.TelephonyTest;
+import com.android.internal.telephony.nitz.NitzStateMachineImpl.NitzSignalInputFilterPredicate;
+import com.android.internal.telephony.nitz.NitzStateMachineTestSupport.FakeDeviceState;
+import com.android.internal.telephony.nitz.NitzStateMachineTestSupport.Scenario;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.io.PrintWriter;
+import java.util.LinkedList;
+import java.util.concurrent.TimeUnit;
+
+public class NitzStateMachineImplTest extends TelephonyTest {
+
+    private static final int SLOT_INDEX = 99999;
+    private static final TelephonyTimeZoneSuggestion EMPTY_TIME_ZONE_SUGGESTION =
+            createEmptyTimeZoneSuggestion(SLOT_INDEX);
+    private static final TelephonyTimeSuggestion EMPTY_TIME_SUGGESTION =
+            createEmptyTimeSuggestion(SLOT_INDEX);
+
+    private FakeTimeServiceHelper mFakeTimeServiceHelper;
+    private FakeDeviceState mFakeDeviceState;
+    private TimeZoneSuggesterImpl mRealTimeZoneSuggester;
+
+    private NitzStateMachineImpl mNitzStateMachineImpl;
+
+
+    @Before
+    public void setUp() throws Exception {
+        TelephonyTest.logd("NitzStateMachineImplTest +Setup!");
+        super.setUp("NitzStateMachineImplTest");
+
+        // In tests we use a fake impls for NewTimeServiceHelper and DeviceState.
+        mFakeDeviceState = new FakeDeviceState();
+        mFakeTimeServiceHelper = new FakeTimeServiceHelper(mFakeDeviceState);
+
+        // In tests we disable NITZ signal input filtering. The real NITZ signal filter is tested
+        // independently. This makes constructing test data simpler: we can be sure the signals
+        // won't be filtered for reasons like rate-limiting.
+        NitzSignalInputFilterPredicate mFakeNitzSignalInputFilter = (oldSignal, newSignal) -> true;
+
+        // In tests a real TimeZoneSuggesterImpl is used with the real TimeZoneLookupHelper and real
+        // country time zone data. A fake device state is used (which allows tests to fake the
+        // system clock / user settings). The tests can perform the expected lookups and confirm the
+        // state machine takes the correct action. Picking real examples from the past is easier
+        // than inventing countries / scenarios and configuring fakes.
+        TimeZoneLookupHelper timeZoneLookupHelper = new TimeZoneLookupHelper();
+        mRealTimeZoneSuggester = new TimeZoneSuggesterImpl(mFakeDeviceState, timeZoneLookupHelper);
+
+        mNitzStateMachineImpl = new NitzStateMachineImpl(
+                SLOT_INDEX, mFakeNitzSignalInputFilter, mRealTimeZoneSuggester,
+                mFakeTimeServiceHelper);
+
+        TelephonyTest.logd("NewNitzStateMachineImplTest -Setup!");
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        super.tearDown();
+    }
+
+    @Test
+    public void test_countryThenNitz() throws Exception {
+        Scenario scenario = UNIQUE_US_ZONE_SCENARIO1;
+        String networkCountryIsoCode = scenario.getNetworkCountryIsoCode();
+        TimestampedValue<NitzData> nitzSignal =
+                scenario.createNitzSignal(mFakeDeviceState.elapsedRealtime());
+
+        // Capture expected results from the real suggester and confirm we can tell the difference
+        // between them.
+        TelephonyTimeZoneSuggestion expectedTimeZoneSuggestion1 =
+                mRealTimeZoneSuggester.getTimeZoneSuggestion(
+                        SLOT_INDEX, networkCountryIsoCode, null /* nitzSignal */);
+        TelephonyTimeZoneSuggestion expectedTimeZoneSuggestion2 =
+                mRealTimeZoneSuggester.getTimeZoneSuggestion(
+                        SLOT_INDEX, networkCountryIsoCode, nitzSignal);
+        assertNotNull(expectedTimeZoneSuggestion2);
+        assertNotEquals(expectedTimeZoneSuggestion1, expectedTimeZoneSuggestion2);
+
+        Script script = new Script()
+                .initializeSystemClock(ARBITRARY_SYSTEM_CLOCK_TIME)
+                .networkAvailable();
+
+        // Simulate country being known.
+        script.countryReceived(networkCountryIsoCode);
+
+        script.verifyOnlyTimeZoneWasSuggestedAndReset(expectedTimeZoneSuggestion1);
+
+        // Check NitzStateMachine exposed state.
+        assertNull(mNitzStateMachineImpl.getCachedNitzData());
+
+        // Simulate NITZ being received and verify the behavior.
+        script.nitzReceived(nitzSignal);
+
+        TelephonyTimeSuggestion expectedTimeSuggestion =
+                createTimeSuggestionFromNitzSignal(SLOT_INDEX, nitzSignal);
+        script.verifyTimeAndTimeZoneSuggestedAndReset(
+                expectedTimeSuggestion, expectedTimeZoneSuggestion2);
+
+        // Check NitzStateMachine exposed state.
+        assertEquals(nitzSignal.getValue(), mNitzStateMachineImpl.getCachedNitzData());
+    }
+
+    @Test
+    public void test_nitzThenCountry() throws Exception {
+        Scenario scenario = UNIQUE_US_ZONE_SCENARIO1;
+        TimestampedValue<NitzData> nitzSignal =
+                scenario.createNitzSignal(mFakeDeviceState.elapsedRealtime());
+
+        String networkCountryIsoCode = scenario.getNetworkCountryIsoCode();
+
+        // Capture test expectations from the real suggester and confirm we can tell the difference
+        // between them.
+        TelephonyTimeZoneSuggestion expectedTimeZoneSuggestion1 =
+                mRealTimeZoneSuggester.getTimeZoneSuggestion(
+                        SLOT_INDEX, null /* countryIsoCode */, nitzSignal);
+        TelephonyTimeZoneSuggestion expectedTimeZoneSuggestion2 =
+                mRealTimeZoneSuggester.getTimeZoneSuggestion(
+                        SLOT_INDEX, networkCountryIsoCode, nitzSignal);
+        assertNotEquals(expectedTimeZoneSuggestion1, expectedTimeZoneSuggestion2);
+
+        Script script = new Script()
+                .initializeSystemClock(ARBITRARY_SYSTEM_CLOCK_TIME)
+                .networkAvailable();
+
+        // Simulate receiving the NITZ signal.
+        script.nitzReceived(nitzSignal);
+
+        // Verify the state machine did the right thing.
+        TelephonyTimeSuggestion expectedTimeSuggestion =
+                createTimeSuggestionFromNitzSignal(SLOT_INDEX, nitzSignal);
+        script.verifyTimeAndTimeZoneSuggestedAndReset(
+                expectedTimeSuggestion, expectedTimeZoneSuggestion1);
+
+        // Check NitzStateMachine exposed state.
+        assertEquals(nitzSignal.getValue(), mNitzStateMachineImpl.getCachedNitzData());
+
+        // Simulate country being known and verify the behavior.
+        script.countryReceived(networkCountryIsoCode)
+                .verifyOnlyTimeZoneWasSuggestedAndReset(expectedTimeZoneSuggestion2);
+
+        // Check NitzStateMachine exposed state.
+        assertEquals(nitzSignal.getValue(), mNitzStateMachineImpl.getCachedNitzData());
+    }
+
+    @Test
+    public void test_emptyCountryString_countryReceivedFirst() throws Exception {
+        Scenario scenario = UNIQUE_US_ZONE_SCENARIO1;
+        TimestampedValue<NitzData> nitzSignal =
+                scenario.createNitzSignal(mFakeDeviceState.elapsedRealtime());
+
+        Script script = new Script()
+                .initializeSystemClock(ARBITRARY_SYSTEM_CLOCK_TIME)
+                .networkAvailable();
+
+        // Simulate an empty country being set.
+        script.countryReceived("");
+
+        // Nothing should be set. The country is not valid.
+        script.verifyOnlyTimeZoneWasSuggestedAndReset(EMPTY_TIME_ZONE_SUGGESTION);
+
+        // Check NitzStateMachine exposed state.
+        assertNull(mNitzStateMachineImpl.getCachedNitzData());
+
+        // Simulate receiving the NITZ signal.
+        script.nitzReceived(nitzSignal);
+
+        TelephonyTimeSuggestion expectedTimeSuggestion =
+                createTimeSuggestionFromNitzSignal(SLOT_INDEX, nitzSignal);
+        // Capture output from the real suggester and confirm it meets the test's needs /
+        // expectations.
+        TelephonyTimeZoneSuggestion expectedTimeZoneSuggestion =
+                mRealTimeZoneSuggester.getTimeZoneSuggestion(
+                        SLOT_INDEX, "" /* countryIsoCode */, nitzSignal);
+        assertEquals(MATCH_TYPE_TEST_NETWORK_OFFSET_ONLY,
+                expectedTimeZoneSuggestion.getMatchType());
+        assertEquals(QUALITY_MULTIPLE_ZONES_WITH_SAME_OFFSET,
+                expectedTimeZoneSuggestion.getQuality());
+
+        // Verify the state machine did the right thing.
+        script.verifyTimeAndTimeZoneSuggestedAndReset(
+                expectedTimeSuggestion, expectedTimeZoneSuggestion);
+
+        // Check NitzStateMachine exposed state.
+        assertEquals(nitzSignal.getValue(), mNitzStateMachineImpl.getCachedNitzData());
+    }
+
+    @Test
+    public void test_emptyCountryStringUsTime_nitzReceivedFirst() throws Exception {
+        Scenario scenario = UNIQUE_US_ZONE_SCENARIO1;
+        TimestampedValue<NitzData> nitzSignal =
+                scenario.createNitzSignal(mFakeDeviceState.elapsedRealtime());
+
+        Script script = new Script()
+                .initializeSystemClock(ARBITRARY_SYSTEM_CLOCK_TIME)
+                .networkAvailable();
+
+        // Simulate receiving the NITZ signal.
+        script.nitzReceived(nitzSignal);
+
+        // Verify the state machine did the right thing.
+        // No time zone should be set. A NITZ signal by itself is not enough.
+        TelephonyTimeSuggestion expectedTimeSuggestion =
+                createTimeSuggestionFromNitzSignal(SLOT_INDEX, nitzSignal);
+        script.verifyTimeAndTimeZoneSuggestedAndReset(
+                expectedTimeSuggestion, EMPTY_TIME_ZONE_SUGGESTION);
+
+        // Check NitzStateMachine exposed state.
+        assertEquals(nitzSignal.getValue(), mNitzStateMachineImpl.getCachedNitzData());
+
+        // Simulate an empty country being set.
+        script.countryReceived("");
+
+        // Capture output from the real suggester and confirm it meets the test's needs /
+        // expectations.
+        TelephonyTimeZoneSuggestion expectedTimeZoneSuggestion =
+                mRealTimeZoneSuggester.getTimeZoneSuggestion(
+                        SLOT_INDEX, "" /* countryIsoCode */, nitzSignal);
+        assertEquals(MATCH_TYPE_TEST_NETWORK_OFFSET_ONLY,
+                expectedTimeZoneSuggestion.getMatchType());
+        assertEquals(QUALITY_MULTIPLE_ZONES_WITH_SAME_OFFSET,
+                expectedTimeZoneSuggestion.getQuality());
+
+        // Verify the state machine did the right thing.
+        script.verifyOnlyTimeZoneWasSuggestedAndReset(expectedTimeZoneSuggestion);
+
+        // Check NitzStateMachine exposed state.
+        assertEquals(nitzSignal.getValue(), mNitzStateMachineImpl.getCachedNitzData());
+    }
+
+    @Test
+    public void test_airplaneModeClearsState() throws Exception {
+        Scenario scenario = UNITED_KINGDOM_SCENARIO.mutableCopy();
+        int timeStepMillis = (int) TimeUnit.HOURS.toMillis(3);
+
+        Script script = new Script()
+                .initializeSystemClock(ARBITRARY_SYSTEM_CLOCK_TIME)
+                .networkAvailable();
+
+        // Pre-flight: Simulate a device receiving signals that allow it to detect time and time
+        // zone.
+        TimestampedValue<NitzData> preFlightNitzSignal =
+                scenario.createNitzSignal(mFakeDeviceState.elapsedRealtime());
+        TelephonyTimeSuggestion expectedPreFlightTimeSuggestion =
+                createTimeSuggestionFromNitzSignal(SLOT_INDEX, preFlightNitzSignal);
+        String preFlightCountryIsoCode = scenario.getNetworkCountryIsoCode();
+
+        // Simulate receiving the NITZ signal and country.
+        script.nitzReceived(preFlightNitzSignal)
+                .countryReceived(preFlightCountryIsoCode);
+
+        // Verify the state machine did the right thing.
+        TelephonyTimeZoneSuggestion expectedPreFlightTimeZoneSuggestion =
+                mRealTimeZoneSuggester.getTimeZoneSuggestion(
+                        SLOT_INDEX, preFlightCountryIsoCode, preFlightNitzSignal);
+        script.verifyTimeAndTimeZoneSuggestedAndReset(
+                expectedPreFlightTimeSuggestion, expectedPreFlightTimeZoneSuggestion);
+
+        // Check state that NitzStateMachine must expose.
+        assertEquals(preFlightNitzSignal.getValue(), mNitzStateMachineImpl.getCachedNitzData());
+
+        // Boarded flight: Airplane mode turned on / time zone detection still enabled.
+        // The NitzStateMachine must lose all state and stop having an opinion about time zone.
+
+        // Simulate the passage of time and update the device realtime clock.
+        scenario.incrementTime(timeStepMillis);
+        script.incrementTime(timeStepMillis);
+
+        // Simulate airplane mode being turned on.
+        script.toggleAirplaneMode(true);
+
+        // Verify the state machine did the right thing.
+        // Check the time zone suggestion was withdrawn (time is not currently withdrawn).
+        script.verifyTimeAndTimeZoneSuggestedAndReset(
+                EMPTY_TIME_SUGGESTION, EMPTY_TIME_ZONE_SUGGESTION);
+
+        // Check state that NitzStateMachine must expose.
+        assertNull(mNitzStateMachineImpl.getCachedNitzData());
+
+        // During flight: Airplane mode turned off / time zone detection still enabled.
+        // The NitzStateMachine still must not have an opinion about time zone / hold any state.
+
+        // Simulate the passage of time and update the device realtime clock.
+        scenario.incrementTime(timeStepMillis);
+        script.incrementTime(timeStepMillis);
+
+        // Simulate airplane mode being turned off.
+        script.toggleAirplaneMode(false);
+
+        // Verify nothing was suggested: The last suggestion was empty so nothing has changed.
+        script.verifyNothingWasSuggested();
+
+        // Check the state that NitzStateMachine must expose.
+        assertNull(mNitzStateMachineImpl.getCachedNitzData());
+
+        // Post flight: Device has moved and receives new signals.
+
+        // Simulate the passage of time and update the device realtime clock.
+        scenario.incrementTime(timeStepMillis);
+        script.incrementTime(timeStepMillis);
+
+        // Simulate the movement to the destination.
+        scenario.changeCountry(UNIQUE_US_ZONE_SCENARIO1.getTimeZoneId(),
+                UNIQUE_US_ZONE_SCENARIO1.getNetworkCountryIsoCode());
+
+        // Simulate the device receiving NITZ signal and country again after the flight. Now the
+        // NitzStateMachine should be opinionated again.
+        TimestampedValue<NitzData> postFlightNitzSignal =
+                scenario.createNitzSignal(mFakeDeviceState.elapsedRealtime());
+        String postFlightCountryCode = scenario.getNetworkCountryIsoCode();
+        script.countryReceived(postFlightCountryCode)
+                .nitzReceived(postFlightNitzSignal);
+
+        // Verify the state machine did the right thing.
+        TelephonyTimeSuggestion expectedPostFlightTimeSuggestion =
+                createTimeSuggestionFromNitzSignal(SLOT_INDEX, postFlightNitzSignal);
+        TelephonyTimeZoneSuggestion expectedPostFlightTimeZoneSuggestion =
+                mRealTimeZoneSuggester.getTimeZoneSuggestion(
+                        SLOT_INDEX, postFlightCountryCode, postFlightNitzSignal);
+        script.verifyTimeAndTimeZoneSuggestedAndReset(
+                expectedPostFlightTimeSuggestion, expectedPostFlightTimeZoneSuggestion);
+
+        // Check state that NitzStateMachine must expose.
+        assertEquals(postFlightNitzSignal.getValue(), mNitzStateMachineImpl.getCachedNitzData());
+    }
+
+    /**
+     * Confirm losing the network / NITZ doesn't clear country state.
+     */
+    @Test
+    public void test_handleNetworkUnavailableClearsNetworkState() throws Exception {
+        Scenario scenario = UNIQUE_US_ZONE_SCENARIO1.mutableCopy();
+        int timeStepMillis = (int) TimeUnit.HOURS.toMillis(3);
+        String countryIsoCode = scenario.getNetworkCountryIsoCode();
+
+        Script script = new Script()
+                .initializeSystemClock(ARBITRARY_SYSTEM_CLOCK_TIME)
+                .networkAvailable();
+
+        // Simulate a device receiving signals that allow it to detect time and time zone.
+        TimestampedValue<NitzData> initialNitzSignal =
+                scenario.createNitzSignal(mFakeDeviceState.elapsedRealtime());
+        TelephonyTimeSuggestion expectedInitialTimeSuggestion =
+                createTimeSuggestionFromNitzSignal(SLOT_INDEX, initialNitzSignal);
+
+        // Simulate receiving the NITZ signal and country.
+        script.nitzReceived(initialNitzSignal)
+                .countryReceived(countryIsoCode);
+
+        // Verify the state machine did the right thing.
+        TelephonyTimeZoneSuggestion expectedInitialTimeZoneSuggestion =
+                mRealTimeZoneSuggester.getTimeZoneSuggestion(
+                        SLOT_INDEX, countryIsoCode, initialNitzSignal);
+        script.verifyTimeAndTimeZoneSuggestedAndReset(
+                expectedInitialTimeSuggestion, expectedInitialTimeZoneSuggestion);
+
+        // Check state that NitzStateMachine must expose.
+        assertEquals(initialNitzSignal.getValue(), mNitzStateMachineImpl.getCachedNitzData());
+
+        // Simulate the passage of time and update the device realtime clock.
+        scenario.incrementTime(timeStepMillis);
+        script.incrementTime(timeStepMillis);
+
+        // Simulate network being lost.
+        script.networkUnavailable();
+
+        // Check the "no NITZ" time and time zone suggestions are made.
+        TelephonyTimeZoneSuggestion expectedMiddleTimeZoneSuggestion =
+                mRealTimeZoneSuggester.getTimeZoneSuggestion(
+                        SLOT_INDEX, countryIsoCode, null /* nitzSignal */);
+        script.verifyTimeAndTimeZoneSuggestedAndReset(
+                EMPTY_TIME_SUGGESTION, expectedMiddleTimeZoneSuggestion);
+
+        // Check state that NitzStateMachine must expose.
+        assertNull(mNitzStateMachineImpl.getCachedNitzData());
+
+        // Simulate the passage of time and update the device realtime clock.
+        scenario.incrementTime(timeStepMillis);
+        script.incrementTime(timeStepMillis);
+
+        // Simulate the network being found.
+        script.networkAvailable()
+                .verifyNothingWasSuggested();
+
+        // Check the state that NitzStateMachine must expose.
+        assertNull(mNitzStateMachineImpl.getCachedNitzData());
+
+        // Simulate the passage of time and update the device realtime clock.
+        scenario.incrementTime(timeStepMillis);
+        script.incrementTime(timeStepMillis);
+
+        // Simulate the device receiving NITZ signal again. Now the NitzStateMachine should be
+        // opinionated again.
+        TimestampedValue<NitzData> finalNitzSignal =
+                scenario.createNitzSignal(mFakeDeviceState.elapsedRealtime());
+        script.nitzReceived(finalNitzSignal);
+
+        // Verify the state machine did the right thing.
+        TelephonyTimeSuggestion expectedFinalTimeSuggestion =
+                createTimeSuggestionFromNitzSignal(SLOT_INDEX, finalNitzSignal);
+        TelephonyTimeZoneSuggestion expectedFinalTimeZoneSuggestion =
+                mRealTimeZoneSuggester.getTimeZoneSuggestion(
+                        SLOT_INDEX, countryIsoCode, finalNitzSignal);
+        script.verifyTimeAndTimeZoneSuggestedAndReset(
+                expectedFinalTimeSuggestion, expectedFinalTimeZoneSuggestion);
+
+        // Check state that NitzStateMachine must expose.
+        assertEquals(finalNitzSignal.getValue(), mNitzStateMachineImpl.getCachedNitzData());
+    }
+
+    @Test
+    public void test_countryUnavailableClearsTimeZoneSuggestion() throws Exception {
+        Scenario scenario = UNIQUE_US_ZONE_SCENARIO1;
+        TimestampedValue<NitzData> nitzSignal =
+                scenario.createNitzSignal(mFakeDeviceState.elapsedRealtime());
+
+        Script script = new Script()
+                .initializeSystemClock(ARBITRARY_SYSTEM_CLOCK_TIME)
+                .networkAvailable();
+
+        // Simulate receiving the country and verify the state machine does the right thing.
+        script.countryReceived(scenario.getNetworkCountryIsoCode());
+        TelephonyTimeZoneSuggestion expectedTimeZoneSuggestion1 =
+                mRealTimeZoneSuggester.getTimeZoneSuggestion(
+                        SLOT_INDEX, scenario.getNetworkCountryIsoCode(), null /* nitzSignal */);
+        script.verifyOnlyTimeZoneWasSuggestedAndReset(expectedTimeZoneSuggestion1);
+
+        // Simulate receiving an NITZ signal and verify the state machine does the right thing.
+        script.nitzReceived(nitzSignal);
+        TelephonyTimeSuggestion expectedTimeSuggestion =
+                createTimeSuggestionFromNitzSignal(SLOT_INDEX, nitzSignal);
+        TelephonyTimeZoneSuggestion expectedTimeZoneSuggestion2 =
+                mRealTimeZoneSuggester.getTimeZoneSuggestion(
+                        SLOT_INDEX, scenario.getNetworkCountryIsoCode(), nitzSignal);
+        script.verifyTimeAndTimeZoneSuggestedAndReset(
+                expectedTimeSuggestion, expectedTimeZoneSuggestion2);
+
+        // Check state that NitzStateMachine must expose.
+        assertEquals(nitzSignal.getValue(), mNitzStateMachineImpl.getCachedNitzData());
+
+        // Simulate the country becoming unavailable and verify the state machine does the right
+        // thing.
+        script.countryUnavailable();
+        TelephonyTimeZoneSuggestion expectedTimeZoneSuggestion3 =
+                mRealTimeZoneSuggester.getTimeZoneSuggestion(
+                        SLOT_INDEX, null /* countryIsoCode */, nitzSignal);
+        script.verifyOnlyTimeZoneWasSuggestedAndReset(expectedTimeZoneSuggestion3);
+
+        // Check state that NitzStateMachine must expose.
+        assertEquals(nitzSignal.getValue(), mNitzStateMachineImpl.getCachedNitzData());
+    }
+
+    /**
+     * A "fluent" helper class allowing reuse of logic for test state initialization, simulation of
+     * events, and verification of device state changes with self-describing method names.
+     */
+    private class Script {
+
+        Script() {
+            // Set initial fake device state.
+            mFakeDeviceState.ignoreNitz = false;
+            mFakeDeviceState.nitzUpdateDiffMillis = 2000;
+            mFakeDeviceState.nitzUpdateSpacingMillis = 1000 * 60 * 10;
+        }
+
+        // Initialization methods for setting simulated device state, usually before simulation.
+
+        Script initializeSystemClock(long timeMillis) {
+            mFakeDeviceState.currentTimeMillis = timeMillis;
+            return this;
+        }
+
+        // Simulation methods that are used by tests to pretend that something happens.
+
+        Script incrementTime(int timeIncrementMillis) {
+            mFakeDeviceState.simulateTimeIncrement(timeIncrementMillis);
+            return this;
+        }
+
+        Script networkAvailable() {
+            mNitzStateMachineImpl.handleNetworkAvailable();
+            return this;
+        }
+
+        Script nitzReceived(TimestampedValue<NitzData> nitzSignal) {
+            mNitzStateMachineImpl.handleNitzReceived(nitzSignal);
+            return this;
+        }
+
+        Script networkUnavailable() {
+            mNitzStateMachineImpl.handleNetworkUnavailable();
+            return this;
+        }
+
+        Script countryUnavailable() {
+            mNitzStateMachineImpl.handleCountryUnavailable();
+            return this;
+        }
+
+        Script countryReceived(String countryIsoCode) {
+            mNitzStateMachineImpl.handleCountryDetected(countryIsoCode);
+            return this;
+        }
+
+        Script toggleAirplaneMode(boolean on) {
+            mNitzStateMachineImpl.handleAirplaneModeChanged(on);
+            return this;
+        }
+
+        // Verification methods.
+
+        Script verifyNothingWasSuggested() {
+            justVerifyTimeWasNotSuggested();
+            justVerifyTimeWasNotSuggested();
+            return this;
+        }
+
+        Script verifyOnlyTimeZoneWasSuggestedAndReset(
+                TelephonyTimeZoneSuggestion timeZoneSuggestion) {
+            justVerifyTimeZoneWasSuggested(timeZoneSuggestion);
+            justVerifyTimeWasNotSuggested();
+            commitStateChanges();
+            return this;
+        }
+
+        Script verifyTimeAndTimeZoneSuggestedAndReset(TelephonyTimeSuggestion timeSuggestion,
+                TelephonyTimeZoneSuggestion timeZoneSuggestion) {
+            justVerifyTimeZoneWasSuggested(timeZoneSuggestion);
+            justVerifyTimeWasSuggested(timeSuggestion);
+            commitStateChanges();
+            return this;
+        }
+
+        private void justVerifyTimeWasNotSuggested() {
+            mFakeTimeServiceHelper.suggestedTimes.assertHasNotBeenSet();
+        }
+
+        private void justVerifyTimeZoneWasSuggested(
+                TelephonyTimeZoneSuggestion timeZoneSuggestion) {
+            mFakeTimeServiceHelper.suggestedTimeZones.assertHasBeenSet();
+            mFakeTimeServiceHelper.suggestedTimeZones.assertLatestEquals(timeZoneSuggestion);
+        }
+
+        private void justVerifyTimeWasSuggested(TelephonyTimeSuggestion timeSuggestion) {
+            mFakeTimeServiceHelper.suggestedTimes.assertChangeCount(1);
+            mFakeTimeServiceHelper.suggestedTimes.assertLatestEquals(timeSuggestion);
+        }
+
+        private void commitStateChanges() {
+            mFakeTimeServiceHelper.commitState();
+        }
+    }
+
+    /** Some piece of state that tests want to track. */
+    private static class TestState<T> {
+        private T mInitialValue;
+        private LinkedList<T> mValues = new LinkedList<>();
+
+        void init(T value) {
+            mValues.clear();
+            mInitialValue = value;
+        }
+
+        void set(T value) {
+            mValues.addFirst(value);
+        }
+
+        boolean hasBeenSet() {
+            return mValues.size() > 0;
+        }
+
+        void assertHasNotBeenSet() {
+            assertFalse(hasBeenSet());
+        }
+
+        void assertHasBeenSet() {
+            assertTrue(hasBeenSet());
+        }
+
+        void commitLatest() {
+            if (hasBeenSet()) {
+                mInitialValue = mValues.getLast();
+                mValues.clear();
+            }
+        }
+
+        void assertLatestEquals(T expected) {
+            assertEquals(expected, getLatest());
+        }
+
+        void assertChangeCount(int expectedCount) {
+            assertEquals(expectedCount, mValues.size());
+        }
+
+        public T getLatest() {
+            if (hasBeenSet()) {
+                return mValues.getFirst();
+            }
+            return mInitialValue;
+        }
+    }
+
+    /**
+     * A fake implementation of {@link TimeServiceHelper} that enables tests to detect what
+     * {@link NitzStateMachineImpl} would do to a real device's state.
+     */
+    private static class FakeTimeServiceHelper implements TimeServiceHelper {
+
+        private final FakeDeviceState mFakeDeviceState;
+
+        // State we want to track.
+        public final TestState<TelephonyTimeSuggestion> suggestedTimes = new TestState<>();
+        public final TestState<TelephonyTimeZoneSuggestion> suggestedTimeZones = new TestState<>();
+
+        FakeTimeServiceHelper(FakeDeviceState fakeDeviceState) {
+            mFakeDeviceState = fakeDeviceState;
+        }
+
+        @Override
+        public void suggestDeviceTime(TelephonyTimeSuggestion timeSuggestion) {
+            suggestedTimes.set(timeSuggestion);
+            if (timeSuggestion.getUtcTime() != null) {
+                // The fake time service just uses the latest suggestion.
+                mFakeDeviceState.currentTimeMillis = timeSuggestion.getUtcTime().getValue();
+            }
+        }
+
+        @Override
+        public void maybeSuggestDeviceTimeZone(TelephonyTimeZoneSuggestion timeZoneSuggestion) {
+            suggestedTimeZones.set(timeZoneSuggestion);
+        }
+
+        @Override
+        public void dumpLogs(IndentingPrintWriter ipw) {
+            // No-op in tests
+        }
+
+        @Override
+        public void dumpState(PrintWriter pw) {
+            // No-op in tests
+        }
+
+        void commitState() {
+            suggestedTimeZones.commitLatest();
+            suggestedTimes.commitLatest();
+        }
+    }
+}
diff --git a/tests/telephonytests/src/com/android/internal/telephony/nitz/NitzStateMachineTestSupport.java b/tests/telephonytests/src/com/android/internal/telephony/nitz/NitzStateMachineTestSupport.java
new file mode 100644
index 0000000..ed1c98b
--- /dev/null
+++ b/tests/telephonytests/src/com/android/internal/telephony/nitz/NitzStateMachineTestSupport.java
@@ -0,0 +1,299 @@
+/*
+ * Copyright 2019 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.nitz;
+
+import static org.junit.Assert.fail;
+
+import android.app.timedetector.TelephonyTimeSuggestion;
+import android.app.timezonedetector.TelephonyTimeZoneSuggestion;
+import android.icu.util.Calendar;
+import android.icu.util.GregorianCalendar;
+import android.icu.util.TimeZone;
+import android.os.TimestampedValue;
+
+import com.android.internal.telephony.NitzData;
+import com.android.internal.telephony.NitzStateMachine;
+import com.android.internal.telephony.NitzStateMachine.DeviceState;
+
+/**
+ * An assortment of methods and classes for testing {@link NitzStateMachine} implementations.
+ */
+final class NitzStateMachineTestSupport {
+
+    // Values used to when initializing device state but where the value isn't important.
+    static final long ARBITRARY_SYSTEM_CLOCK_TIME = createUtcTime(1977, 1, 1, 12, 0, 0);
+    static final long ARBITRARY_REALTIME_MILLIS = 123456789L;
+    static final String ARBITRARY_DEBUG_INFO = "Test debug info";
+
+    // A country with a single zone : the zone can be guessed from the country.
+    // The UK uses UTC for part of the year so it is not good for detecting bogus NITZ signals.
+    static final Scenario UNITED_KINGDOM_SCENARIO = new Scenario.Builder()
+            .setTimeZone("Europe/London")
+            .setActualTimeUtc(2018, 1, 1, 12, 0, 0)
+            .setCountryIso("gb")
+            .buildFrozen();
+
+    static final String UNITED_KINGDOM_COUNTRY_DEFAULT_ZONE_ID = "Europe/London";
+
+    // The US is a country that has multiple zones, but there is only one matching time zone at the
+    // time in this scenario: the zone cannot be guessed from the country alone, but can be guessed
+    // from the country + NITZ. The US never uses UTC so it can be used for testing bogus (zero'd
+    // values) NITZ signals.
+    static final Scenario UNIQUE_US_ZONE_SCENARIO1 = new Scenario.Builder()
+            .setTimeZone("America/Los_Angeles")
+            .setActualTimeUtc(2018, 1, 1, 12, 0, 0)
+            .setCountryIso("us")
+            .buildFrozen();
+
+    // An alternative US scenario which also provides a unique time zone answer.
+    static final Scenario UNIQUE_US_ZONE_SCENARIO2 = new Scenario.Builder()
+            .setTimeZone("America/Chicago")
+            .setActualTimeUtc(2018, 1, 1, 12, 0, 0)
+            .setCountryIso("us")
+            .buildFrozen();
+
+    // A non-unique US scenario: the offset information is ambiguous between America/Phoenix and
+    // America/Denver during winter.
+    static final Scenario NON_UNIQUE_US_ZONE_SCENARIO = new Scenario.Builder()
+            .setTimeZone("America/Denver")
+            .setActualTimeUtc(2018, 1, 1, 12, 0, 0)
+            .setCountryIso("us")
+            .buildFrozen();
+    static final String[] NON_UNIQUE_US_ZONE_SCENARIO_ZONES =
+            { "America/Denver", "America/Phoenix" };
+
+    static final String US_COUNTRY_DEFAULT_ZONE_ID = "America/New_York";
+
+    // New Zealand is a country with multiple zones, but the default zone has the "boost" modifier
+    // which means that NITZ isn't required to find the zone.
+    static final Scenario NEW_ZEALAND_DEFAULT_SCENARIO = new Scenario.Builder()
+            .setTimeZone("Pacific/Auckland")
+            .setActualTimeUtc(2018, 1, 1, 12, 0, 0)
+            .setCountryIso("nz")
+            .buildFrozen();
+    static final Scenario NEW_ZEALAND_OTHER_SCENARIO = new Scenario.Builder()
+            .setTimeZone("Pacific/Chatham")
+            .setActualTimeUtc(2018, 1, 1, 12, 0, 0)
+            .setCountryIso("nz")
+            .buildFrozen();
+
+    static final String NEW_ZEALAND_COUNTRY_DEFAULT_ZONE_ID = "Pacific/Auckland";
+
+    // A country with a single zone: the zone can be guessed from the country alone. CZ never uses
+    // UTC so it can be used for testing bogus NITZ signal handling.
+    static final Scenario CZECHIA_SCENARIO = new Scenario.Builder()
+            .setTimeZone("Europe/Prague")
+            .setActualTimeUtc(2018, 1, 1, 12, 0, 0)
+            .setCountryIso("cz")
+            .buildFrozen();
+
+    static final String CZECHIA_COUNTRY_DEFAULT_ZONE_ID = "Europe/Prague";
+
+    /**
+     * A scenario used during tests. Describes a fictional reality.
+     */
+    static class Scenario {
+
+        private final boolean mFrozen;
+        private TimeZone mZone;
+        private String mNetworkCountryIsoCode;
+        private long mActualTimeMillis;
+
+        Scenario(boolean frozen, long timeMillis, String zoneId, String countryIsoCode) {
+            mFrozen = frozen;
+            mActualTimeMillis = timeMillis;
+            mZone = zone(zoneId);
+            mNetworkCountryIsoCode = countryIsoCode;
+        }
+
+        /** Creates an NITZ signal to match the scenario. */
+        TimestampedValue<NitzData> createNitzSignal(long elapsedRealtimeClock) {
+            return new TimestampedValue<>(elapsedRealtimeClock, createNitzData());
+        }
+
+        /** Creates an NITZ signal to match the scenario. */
+        NitzData createNitzData() {
+            int[] offsets = new int[2];
+            mZone.getOffset(mActualTimeMillis, false /* local */, offsets);
+            int zoneOffsetMillis = offsets[0] + offsets[1];
+            return NitzData.createForTests(
+                    zoneOffsetMillis, offsets[1], mActualTimeMillis,
+                    null /* emulatorHostTimeZone */);
+        }
+
+        String getNetworkCountryIsoCode() {
+            return mNetworkCountryIsoCode;
+        }
+
+        String getTimeZoneId() {
+            return mZone.getID();
+        }
+
+        TimeZone getTimeZone() {
+            return mZone;
+        }
+
+        Scenario incrementTime(long timeIncrementMillis) {
+            checkFrozen();
+            mActualTimeMillis += timeIncrementMillis;
+            return this;
+        }
+
+        Scenario changeCountry(String timeZoneId, String networkCountryIsoCode) {
+            checkFrozen();
+            mZone = zone(timeZoneId);
+            mNetworkCountryIsoCode = networkCountryIsoCode;
+            return this;
+        }
+
+        Scenario mutableCopy() {
+            return new Scenario(
+                    false /* frozen */, mActualTimeMillis, mZone.getID(), mNetworkCountryIsoCode);
+        }
+
+        private void checkFrozen() {
+            if (mFrozen) {
+                throw new IllegalStateException("Scenario is frozen. Copy first");
+            }
+        }
+
+        static class Builder {
+
+            private long mActualTimeMillis;
+            private String mZoneId;
+            private String mCountryIsoCode;
+
+            Builder setActualTimeUtc(int year, int monthInYear, int day, int hourOfDay,
+                    int minute, int second) {
+                mActualTimeMillis = createUtcTime(year, monthInYear, day, hourOfDay, minute,
+                        second);
+                return this;
+            }
+
+            Builder setTimeZone(String zoneId) {
+                mZoneId = zoneId;
+                return this;
+            }
+
+            Builder setCountryIso(String isoCode) {
+                mCountryIsoCode = isoCode;
+                return this;
+            }
+
+            Scenario buildFrozen() {
+                return new Scenario(true /* frozen */, mActualTimeMillis, mZoneId, mCountryIsoCode);
+            }
+        }
+    }
+
+    /** A fake implementation of {@link DeviceState}. */
+    static class FakeDeviceState implements DeviceState {
+
+        public boolean ignoreNitz;
+        public int nitzUpdateDiffMillis;
+        public int nitzUpdateSpacingMillis;
+        public long elapsedRealtime;
+        public long currentTimeMillis;
+
+        FakeDeviceState() {
+            // Set sensible defaults fake device state.
+            ignoreNitz = false;
+            nitzUpdateDiffMillis = 2000;
+            nitzUpdateSpacingMillis = 1000 * 60 * 10;
+            elapsedRealtime = ARBITRARY_REALTIME_MILLIS;
+        }
+
+        @Override
+        public int getNitzUpdateSpacingMillis() {
+            return nitzUpdateSpacingMillis;
+        }
+
+        @Override
+        public int getNitzUpdateDiffMillis() {
+            return nitzUpdateDiffMillis;
+        }
+
+        @Override
+        public boolean getIgnoreNitz() {
+            return ignoreNitz;
+        }
+
+        @Override
+        public long elapsedRealtime() {
+            return elapsedRealtime;
+        }
+
+        @Override
+        public long currentTimeMillis() {
+            return currentTimeMillis;
+        }
+
+        void simulateTimeIncrement(int timeIncrementMillis) {
+            if (timeIncrementMillis <= 0) {
+                fail("elapsedRealtime clock must go forwards");
+            }
+            elapsedRealtime += timeIncrementMillis;
+            currentTimeMillis += timeIncrementMillis;
+        }
+
+    }
+
+    private NitzStateMachineTestSupport() {}
+
+    private static long createUtcTime(int year, int monthInYear, int day, int hourOfDay, int minute,
+            int second) {
+        Calendar cal = new GregorianCalendar(zone("Etc/UTC"));
+        cal.clear();
+        cal.set(year, monthInYear - 1, day, hourOfDay, minute, second);
+        return cal.getTimeInMillis();
+    }
+
+    static TelephonyTimeZoneSuggestion createEmptyTimeZoneSuggestion(int slotIndex) {
+        return new TelephonyTimeZoneSuggestion.Builder(slotIndex)
+                .addDebugInfo("Test")
+                .build();
+    }
+
+    static TelephonyTimeSuggestion createEmptyTimeSuggestion(int slotIndex) {
+        return new TelephonyTimeSuggestion.Builder(slotIndex)
+                .addDebugInfo("Test")
+                .build();
+    }
+
+    static TelephonyTimeSuggestion createTimeSuggestionFromNitzSignal(
+            int slotIndex, TimestampedValue<NitzData> nitzSignal) {
+        return new TelephonyTimeSuggestion.Builder(slotIndex)
+                .setUtcTime(createTimeSignalFromNitzSignal(nitzSignal))
+                .addDebugInfo("Test")
+                .build();
+    }
+
+    private static TimestampedValue<Long> createTimeSignalFromNitzSignal(
+            TimestampedValue<NitzData> nitzSignal) {
+        return new TimestampedValue<>(
+                nitzSignal.getReferenceTimeMillis(),
+                nitzSignal.getValue().getCurrentTimeInMillis());
+    }
+
+    private static TimeZone zone(String zoneId) {
+        TimeZone timeZone = TimeZone.getFrozenTimeZone(zoneId);
+        if (timeZone.getID().equals(TimeZone.UNKNOWN_ZONE_ID)) {
+            fail(zoneId + " is not a valid zone");
+        }
+        return timeZone;
+    }
+}
diff --git a/tests/telephonytests/src/com/android/internal/telephony/nitz/NitzStateMachineTestSupportTest.java b/tests/telephonytests/src/com/android/internal/telephony/nitz/NitzStateMachineTestSupportTest.java
new file mode 100644
index 0000000..986c8d6
--- /dev/null
+++ b/tests/telephonytests/src/com/android/internal/telephony/nitz/NitzStateMachineTestSupportTest.java
@@ -0,0 +1,210 @@
+/*
+ * Copyright 2019 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.nitz;
+
+import static com.android.internal.telephony.nitz.NitzStateMachineTestSupport.ARBITRARY_DEBUG_INFO;
+import static com.android.internal.telephony.nitz.NitzStateMachineTestSupport.ARBITRARY_SYSTEM_CLOCK_TIME;
+import static com.android.internal.telephony.nitz.NitzStateMachineTestSupport.CZECHIA_COUNTRY_DEFAULT_ZONE_ID;
+import static com.android.internal.telephony.nitz.NitzStateMachineTestSupport.CZECHIA_SCENARIO;
+import static com.android.internal.telephony.nitz.NitzStateMachineTestSupport.NEW_ZEALAND_COUNTRY_DEFAULT_ZONE_ID;
+import static com.android.internal.telephony.nitz.NitzStateMachineTestSupport.NEW_ZEALAND_DEFAULT_SCENARIO;
+import static com.android.internal.telephony.nitz.NitzStateMachineTestSupport.NEW_ZEALAND_OTHER_SCENARIO;
+import static com.android.internal.telephony.nitz.NitzStateMachineTestSupport.NON_UNIQUE_US_ZONE_SCENARIO;
+import static com.android.internal.telephony.nitz.NitzStateMachineTestSupport.NON_UNIQUE_US_ZONE_SCENARIO_ZONES;
+import static com.android.internal.telephony.nitz.NitzStateMachineTestSupport.UNIQUE_US_ZONE_SCENARIO1;
+import static com.android.internal.telephony.nitz.NitzStateMachineTestSupport.UNIQUE_US_ZONE_SCENARIO2;
+import static com.android.internal.telephony.nitz.NitzStateMachineTestSupport.UNITED_KINGDOM_COUNTRY_DEFAULT_ZONE_ID;
+import static com.android.internal.telephony.nitz.NitzStateMachineTestSupport.UNITED_KINGDOM_SCENARIO;
+import static com.android.internal.telephony.nitz.NitzStateMachineTestSupport.US_COUNTRY_DEFAULT_ZONE_ID;
+import static com.android.internal.telephony.nitz.TimeZoneLookupHelper.CountryResult.QUALITY_DEFAULT_BOOSTED;
+import static com.android.internal.telephony.nitz.TimeZoneLookupHelper.CountryResult.QUALITY_MULTIPLE_ZONES_DIFFERENT_OFFSETS;
+import static com.android.internal.telephony.nitz.TimeZoneLookupHelper.CountryResult.QUALITY_SINGLE_ZONE;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import android.timezone.CountryTimeZones.OffsetResult;
+
+import com.android.internal.telephony.nitz.TimeZoneLookupHelper.CountryResult;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import java.util.Arrays;
+import java.util.List;
+
+public class NitzStateMachineTestSupportTest {
+
+    private TimeZoneLookupHelper mTimeZoneLookupHelper;
+
+    @Before
+    public void setUp() throws Exception {
+        // In tests we use the real TimeZoneLookupHelper implementation.
+        mTimeZoneLookupHelper = new TimeZoneLookupHelper();
+    }
+
+    @Test
+    public void test_uniqueUs_assumptions() {
+        // Check we'll get the expected behavior from TimeZoneLookupHelper.
+
+        // quality == QUALITY_MULTIPLE_ZONES_DIFFERENT_OFFSETS, therefore the country's default zone
+        // shouldn't be considered a good match.
+        CountryResult expectedCountryLookupResult = new CountryResult(
+                US_COUNTRY_DEFAULT_ZONE_ID, QUALITY_MULTIPLE_ZONES_DIFFERENT_OFFSETS,
+                ARBITRARY_DEBUG_INFO);
+        CountryResult actualCountryLookupResult =
+                mTimeZoneLookupHelper.lookupByCountry(
+                        UNIQUE_US_ZONE_SCENARIO1.getNetworkCountryIsoCode(),
+                        ARBITRARY_SYSTEM_CLOCK_TIME);
+        assertEquals(expectedCountryLookupResult, actualCountryLookupResult);
+
+        // isOnlyMatch == true, so the combination of country + NITZ should be enough for a match.
+        {
+            OffsetResult expectedLookupResult = new OffsetResult(
+                    UNIQUE_US_ZONE_SCENARIO1.getTimeZone(), true /* isOnlyMatch */);
+            OffsetResult actualLookupResult = mTimeZoneLookupHelper.lookupByNitzCountry(
+                    UNIQUE_US_ZONE_SCENARIO1.createNitzData(),
+                    UNIQUE_US_ZONE_SCENARIO1.getNetworkCountryIsoCode());
+            assertEquals(expectedLookupResult, actualLookupResult);
+        }
+
+        // isOnlyMatch == true, so the combination of country + NITZ should be enough for a match.
+        {
+            OffsetResult expectedLookupResult = new OffsetResult(
+                    UNIQUE_US_ZONE_SCENARIO2.getTimeZone(), true /* isOnlyMatch */);
+            OffsetResult actualLookupResult = mTimeZoneLookupHelper.lookupByNitzCountry(
+                    UNIQUE_US_ZONE_SCENARIO2.createNitzData(),
+                    UNIQUE_US_ZONE_SCENARIO2.getNetworkCountryIsoCode());
+            assertEquals(expectedLookupResult, actualLookupResult);
+        }
+    }
+
+    @Test
+    public void test_nonUniqueUs_assumptions() {
+        // Check we'll get the expected behavior from TimeZoneLookupHelper.
+
+        // quality == QUALITY_MULTIPLE_ZONES_DIFFERENT_OFFSETS, therefore the country's default zone
+        // shouldn't be considered a good match.
+        CountryResult expectedCountryLookupResult = new CountryResult(
+                US_COUNTRY_DEFAULT_ZONE_ID, QUALITY_MULTIPLE_ZONES_DIFFERENT_OFFSETS,
+                ARBITRARY_DEBUG_INFO);
+        CountryResult actualCountryLookupResult =
+                mTimeZoneLookupHelper.lookupByCountry(
+                        NON_UNIQUE_US_ZONE_SCENARIO.getNetworkCountryIsoCode(),
+                        ARBITRARY_SYSTEM_CLOCK_TIME);
+        assertEquals(expectedCountryLookupResult, actualCountryLookupResult);
+
+        // By definition, there are multiple matching zones for the NON_UNIQUE_US_ZONE_SCENARIO.
+        {
+            OffsetResult actualLookupResult = mTimeZoneLookupHelper.lookupByNitzCountry(
+                    NON_UNIQUE_US_ZONE_SCENARIO.createNitzData(),
+                    NON_UNIQUE_US_ZONE_SCENARIO.getNetworkCountryIsoCode());
+            List<String> possibleZones = Arrays.asList(NON_UNIQUE_US_ZONE_SCENARIO_ZONES);
+            assertTrue(possibleZones.contains(actualLookupResult.getTimeZone().getID()));
+            assertFalse(actualLookupResult.isOnlyMatch());
+        }
+    }
+
+    @Test
+    public void test_unitedKingdom_assumptions() {
+        assertEquals(UNITED_KINGDOM_SCENARIO.getTimeZone().getID(),
+                UNITED_KINGDOM_COUNTRY_DEFAULT_ZONE_ID);
+
+        // Check we'll get the expected behavior from TimeZoneLookupHelper.
+
+        // quality == QUALITY_SINGLE_ZONE, so the default zone is a good match.
+        CountryResult expectedCountryLookupResult = new CountryResult(
+                UNITED_KINGDOM_COUNTRY_DEFAULT_ZONE_ID, QUALITY_SINGLE_ZONE, ARBITRARY_DEBUG_INFO);
+        CountryResult actualCountryLookupResult =
+                mTimeZoneLookupHelper.lookupByCountry(
+                        UNITED_KINGDOM_SCENARIO.getNetworkCountryIsoCode(),
+                        ARBITRARY_SYSTEM_CLOCK_TIME);
+        assertEquals(expectedCountryLookupResult, actualCountryLookupResult);
+
+        // isOnlyMatch == true, so the combination of country + NITZ should be enough for a match.
+        OffsetResult expectedLookupResult = new OffsetResult(
+                UNITED_KINGDOM_SCENARIO.getTimeZone(), true /* isOnlyMatch */);
+        OffsetResult actualLookupResult = mTimeZoneLookupHelper.lookupByNitzCountry(
+                UNITED_KINGDOM_SCENARIO.createNitzData(),
+                UNITED_KINGDOM_SCENARIO.getNetworkCountryIsoCode());
+        assertEquals(expectedLookupResult, actualLookupResult);
+    }
+
+    @Test
+    public void test_newZealand_assumptions() {
+        assertEquals(NEW_ZEALAND_DEFAULT_SCENARIO.getTimeZone().getID(),
+                NEW_ZEALAND_COUNTRY_DEFAULT_ZONE_ID);
+
+        // Check we'll get the expected behavior from TimeZoneLookupHelper.
+
+        // quality == QUALITY_DEFAULT_BOOSTED, so the default zone is a good match.
+        CountryResult expectedCountryLookupResult = new CountryResult(
+                NEW_ZEALAND_COUNTRY_DEFAULT_ZONE_ID, QUALITY_DEFAULT_BOOSTED, ARBITRARY_DEBUG_INFO);
+        CountryResult actualCountryLookupResult =
+                mTimeZoneLookupHelper.lookupByCountry(
+                        NEW_ZEALAND_DEFAULT_SCENARIO.getNetworkCountryIsoCode(),
+                        ARBITRARY_SYSTEM_CLOCK_TIME);
+        assertEquals(expectedCountryLookupResult, actualCountryLookupResult);
+
+        // Check NEW_ZEALAND_DEFAULT_SCENARIO.
+        {
+            // isOnlyMatch == true, so the combination of country + NITZ should be enough for a
+            // match.
+            OffsetResult expectedLookupResult = new OffsetResult(
+                    NEW_ZEALAND_DEFAULT_SCENARIO.getTimeZone(), true /* isOnlyMatch */);
+            OffsetResult actualLookupResult = mTimeZoneLookupHelper.lookupByNitzCountry(
+                    NEW_ZEALAND_DEFAULT_SCENARIO.createNitzData(),
+                    NEW_ZEALAND_DEFAULT_SCENARIO.getNetworkCountryIsoCode());
+            assertEquals(expectedLookupResult, actualLookupResult);
+        }
+
+        // Check NEW_ZEALAND_OTHER_SCENARIO.
+        {
+            // isOnlyMatch == true, so the combination of country + NITZ should be enough for a
+            // match.
+            OffsetResult expectedLookupResult = new OffsetResult(
+                    NEW_ZEALAND_OTHER_SCENARIO.getTimeZone(), true /* isOnlyMatch */);
+            OffsetResult actualLookupResult = mTimeZoneLookupHelper.lookupByNitzCountry(
+                    NEW_ZEALAND_OTHER_SCENARIO.createNitzData(),
+                    NEW_ZEALAND_OTHER_SCENARIO.getNetworkCountryIsoCode());
+            assertEquals(expectedLookupResult, actualLookupResult);
+        }
+    }
+
+    @Test
+    public void test_czechia_assumptions() {
+        assertEquals(CZECHIA_SCENARIO.getTimeZone().getID(), CZECHIA_COUNTRY_DEFAULT_ZONE_ID);
+
+        // quality == QUALITY_SINGLE_ZONE, so the default zone is a good match.
+        CountryResult expectedCountryLookupResult = new CountryResult(
+                CZECHIA_COUNTRY_DEFAULT_ZONE_ID, QUALITY_SINGLE_ZONE, ARBITRARY_DEBUG_INFO);
+        CountryResult actualCountryLookupResult =
+                mTimeZoneLookupHelper.lookupByCountry(
+                        CZECHIA_SCENARIO.getNetworkCountryIsoCode(),
+                        ARBITRARY_SYSTEM_CLOCK_TIME);
+        assertEquals(expectedCountryLookupResult, actualCountryLookupResult);
+
+        // isOnlyMatch == true, so the combination of country + NITZ should be enough for a match.
+        OffsetResult expectedLookupResult = new OffsetResult(
+                CZECHIA_SCENARIO.getTimeZone(), true /* isOnlyMatch */);
+        OffsetResult actualLookupResult = mTimeZoneLookupHelper.lookupByNitzCountry(
+                CZECHIA_SCENARIO.createNitzData(),
+                CZECHIA_SCENARIO.getNetworkCountryIsoCode());
+        assertEquals(expectedLookupResult, actualLookupResult);
+    }
+}
diff --git a/tests/telephonytests/src/com/android/internal/telephony/nitz/TimeZoneLookupHelperTest.java b/tests/telephonytests/src/com/android/internal/telephony/nitz/TimeZoneLookupHelperTest.java
new file mode 100644
index 0000000..af1be4c
--- /dev/null
+++ b/tests/telephonytests/src/com/android/internal/telephony/nitz/TimeZoneLookupHelperTest.java
@@ -0,0 +1,488 @@
+/*
+ * Copyright 2017 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.nitz;
+
+import static com.android.internal.telephony.nitz.NitzStateMachineTestSupport.ARBITRARY_DEBUG_INFO;
+import static com.android.internal.telephony.nitz.TimeZoneLookupHelper.CountryResult.QUALITY_DEFAULT_BOOSTED;
+import static com.android.internal.telephony.nitz.TimeZoneLookupHelper.CountryResult.QUALITY_MULTIPLE_ZONES_DIFFERENT_OFFSETS;
+import static com.android.internal.telephony.nitz.TimeZoneLookupHelper.CountryResult.QUALITY_MULTIPLE_ZONES_SAME_OFFSET;
+import static com.android.internal.telephony.nitz.TimeZoneLookupHelper.CountryResult.QUALITY_SINGLE_ZONE;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+import android.icu.util.GregorianCalendar;
+import android.icu.util.TimeZone;
+import android.timezone.CountryTimeZones.OffsetResult;
+
+import com.android.internal.telephony.NitzData;
+import com.android.internal.telephony.nitz.TimeZoneLookupHelper.CountryResult;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import java.util.Calendar;
+import java.util.Date;
+import java.util.concurrent.TimeUnit;
+
+public class TimeZoneLookupHelperTest {
+    // Note: Historical dates are used to avoid the test breaking due to data changes.
+    /* Arbitrary summer date in the Northern hemisphere. */
+    private static final long NH_SUMMER_TIME_MILLIS = createUtcTime(2015, 6, 20, 1, 2, 3);
+    /* Arbitrary winter date in the Northern hemisphere. */
+    private static final long NH_WINTER_TIME_MILLIS = createUtcTime(2015, 1, 20, 1, 2, 3);
+
+    private TimeZoneLookupHelper mTimeZoneLookupHelper;
+
+    @Before
+    public void setUp() {
+        mTimeZoneLookupHelper = new TimeZoneLookupHelper();
+    }
+
+    @Test
+    public void testLookupByNitzByNitz() {
+        // Historical dates are used to avoid the test breaking due to data changes.
+        // However, algorithm updates may change the exact time zone returned, though it shouldn't
+        // ever be a less exact match.
+        long nhSummerTimeMillis = createUtcTime(2015, 6, 20, 1, 2, 3);
+        long nhWinterTimeMillis = createUtcTime(2015, 1, 20, 1, 2, 3);
+
+        String nhSummerTimeString = "15/06/20,01:02:03";
+        String nhWinterTimeString = "15/01/20,01:02:03";
+
+        // Tests for London, UK.
+        {
+            String lonSummerTimeString = nhSummerTimeString + "+4";
+            int lonSummerOffsetMillis = (int) TimeUnit.HOURS.toMillis(1);
+            int lonSummerDstOffsetMillis = (int) TimeUnit.HOURS.toMillis(1);
+
+            String lonWinterTimeString = nhWinterTimeString + "+0";
+            int lonWinterOffsetMillis = 0;
+            int lonWinterDstOffsetMillis = 0;
+
+            OffsetResult lookupResult;
+
+            // Summer, known DST state (DST == true).
+            NitzData lonSummerNitzDataWithOffset = NitzData.parse(lonSummerTimeString + ",4");
+            lookupResult = mTimeZoneLookupHelper.lookupByNitz(lonSummerNitzDataWithOffset);
+            assertOffsetResultZoneOffsets(nhSummerTimeMillis, lonSummerOffsetMillis,
+                    lonSummerDstOffsetMillis, lookupResult);
+            assertOffsetResultMetadata(false, lookupResult);
+
+            // Winter, known DST state (DST == false).
+            NitzData lonWinterNitzDataWithOffset = NitzData.parse(lonWinterTimeString + ",0");
+            lookupResult = mTimeZoneLookupHelper.lookupByNitz(lonWinterNitzDataWithOffset);
+            assertOffsetResultZoneOffsets(nhWinterTimeMillis, lonWinterOffsetMillis,
+                    lonWinterDstOffsetMillis, lookupResult);
+            assertOffsetResultMetadata(false, lookupResult);
+
+            // Summer, unknown DST state
+            NitzData lonSummerNitzDataWithoutOffset = NitzData.parse(lonSummerTimeString);
+            lookupResult = mTimeZoneLookupHelper.lookupByNitz(lonSummerNitzDataWithoutOffset);
+            assertOffsetResultZoneOffsets(nhSummerTimeMillis, lonSummerOffsetMillis, null,
+                    lookupResult);
+            assertOffsetResultMetadata(false, lookupResult);
+
+            // Winter, unknown DST state
+            NitzData lonWinterNitzDataWithoutOffset = NitzData.parse(lonWinterTimeString);
+            lookupResult = mTimeZoneLookupHelper.lookupByNitz(lonWinterNitzDataWithoutOffset);
+            assertOffsetResultZoneOffsets(nhWinterTimeMillis, lonWinterOffsetMillis, null,
+                    lookupResult);
+            assertOffsetResultMetadata(false, lookupResult);
+        }
+
+        // Tests for Mountain View, CA, US.
+        {
+            String mtvSummerTimeString = nhSummerTimeString + "-32";
+            int mtvSummerOffsetMillis = (int) TimeUnit.HOURS.toMillis(-8);
+            int mtvSummerDstOffsetMillis = (int) TimeUnit.HOURS.toMillis(1);
+
+            String mtvWinterTimeString = nhWinterTimeString + "-28";
+            int mtvWinterOffsetMillis = (int) TimeUnit.HOURS.toMillis(-7);
+            int mtvWinterDstOffsetMillis = 0;
+
+            OffsetResult lookupResult;
+
+            // Summer, known DST state (DST == true).
+            NitzData mtvSummerNitzDataWithOffset = NitzData.parse(mtvSummerTimeString + ",4");
+            lookupResult = mTimeZoneLookupHelper.lookupByNitz(mtvSummerNitzDataWithOffset);
+            assertOffsetResultZoneOffsets(nhSummerTimeMillis, mtvSummerOffsetMillis,
+                    mtvSummerDstOffsetMillis, lookupResult);
+            assertOffsetResultMetadata(false, lookupResult);
+
+            // Winter, known DST state (DST == false).
+            NitzData mtvWinterNitzDataWithOffset = NitzData.parse(mtvWinterTimeString + ",0");
+            lookupResult = mTimeZoneLookupHelper.lookupByNitz(mtvWinterNitzDataWithOffset);
+            assertOffsetResultZoneOffsets(nhWinterTimeMillis, mtvWinterOffsetMillis,
+                    mtvWinterDstOffsetMillis, lookupResult);
+            assertOffsetResultMetadata(false, lookupResult);
+
+            // Summer, unknown DST state
+            NitzData mtvSummerNitzDataWithoutOffset = NitzData.parse(mtvSummerTimeString);
+            lookupResult = mTimeZoneLookupHelper.lookupByNitz(mtvSummerNitzDataWithoutOffset);
+            assertOffsetResultZoneOffsets(nhSummerTimeMillis, mtvSummerOffsetMillis, null,
+                    lookupResult);
+            assertOffsetResultMetadata(false, lookupResult);
+
+            // Winter, unknown DST state
+            NitzData mtvWinterNitzDataWithoutOffset = NitzData.parse(mtvWinterTimeString);
+            lookupResult = mTimeZoneLookupHelper.lookupByNitz(mtvWinterNitzDataWithoutOffset);
+            assertOffsetResultZoneOffsets(nhWinterTimeMillis, mtvWinterOffsetMillis, null,
+                    lookupResult);
+            assertOffsetResultMetadata(false, lookupResult);
+        }
+    }
+
+    @Test
+    public void testLookupByNitzCountry_filterByEffectiveDate() {
+        // America/North_Dakota/Beulah was on Mountain Time until 2010-11-07, when it switched to
+        // Central Time.
+        String usIso = "US";
+
+        // Try MDT / -6 hours in summer before America/North_Dakota/Beulah switched to Central Time.
+        {
+            String nitzString = "10/11/05,00:00:00-24,1"; // 2010-11-05 00:00:00 UTC, UTC-6, DST
+            NitzData nitzData = NitzData.parse(nitzString);
+            // The zone chosen is a side effect of zone ordering in the data files so we just check
+            // the isOnlyMatch value.
+            OffsetResult offsetResult = mTimeZoneLookupHelper.lookupByNitzCountry(nitzData, usIso);
+            assertFalse(offsetResult.isOnlyMatch());
+        }
+
+        // Try MDT / -6 hours in summer after America/North_Dakota/Beulah switched to central time.
+        {
+            String nitzString = "11/11/05,00:00:00-24,1"; // 2011-11-05 00:00:00 UTC, UTC-6, DST
+            NitzData nitzData = NitzData.parse(nitzString);
+            OffsetResult offsetResult = mTimeZoneLookupHelper.lookupByNitzCountry(nitzData, usIso);
+            assertTrue(offsetResult.isOnlyMatch());
+        }
+    }
+
+    @Test
+    public void testLookupByNitzCountry_multipleMatches() {
+        // America/Denver & America/Phoenix share the same Mountain Standard Time offset (i.e.
+        // during winter).
+        String usIso = "US";
+
+        // Try MDT for a recent summer date: No ambiguity here.
+        {
+            String nitzString = "15/06/01,00:00:00-24,1"; // 2015-06-01 00:00:00 UTC, UTC-6, DST
+            NitzData nitzData = NitzData.parse(nitzString);
+            OffsetResult offsetResult = mTimeZoneLookupHelper.lookupByNitzCountry(nitzData, usIso);
+            assertTrue(offsetResult.isOnlyMatch());
+        }
+
+        // Try MST for a recent summer date: No ambiguity here.
+        {
+            String nitzString = "15/06/01,00:00:00-28,0"; // 2015-06-01 00:00:00 UTC, UTC-7, not DST
+            NitzData nitzData = NitzData.parse(nitzString);
+            OffsetResult offsetResult = mTimeZoneLookupHelper.lookupByNitzCountry(nitzData, usIso);
+            assertTrue(offsetResult.isOnlyMatch());
+        }
+
+        // Try MST for a recent winter date: There are multiple zones to pick from because of the
+        // America/Denver & America/Phoenix ambiguity.
+        {
+            String nitzString = "15/01/01,00:00:00-28,0"; // 2015-01-01 00:00:00 UTC, UTC-7, not DST
+            NitzData nitzData = NitzData.parse(nitzString);
+            OffsetResult offsetResult = mTimeZoneLookupHelper.lookupByNitzCountry(nitzData, usIso);
+            assertFalse(offsetResult.isOnlyMatch());
+        }
+    }
+
+    @Test
+    public void testLookupByNitzCountry_dstKnownAndUnknown() {
+        // Historical dates are used to avoid the test breaking due to data changes.
+        // However, algorithm updates may change the exact time zone returned, though it shouldn't
+        // ever be a less exact match.
+        long nhSummerTimeMillis = createUtcTime(2015, 6, 20, 1, 2, 3);
+        long nhWinterTimeMillis = createUtcTime(2015, 1, 20, 1, 2, 3);
+
+        // A country in the northern hemisphere with one time zone.
+        String adIso = "AD"; // Andora
+        String summerTimeNitzString = "15/06/20,01:02:03+8"; // 2015-06-20 01:02:03 UTC, UTC+2
+        String winterTimeNitzString = "15/01/20,01:02:03+4"; // 2015-01-20 01:02:03 UTC, UTC+1
+
+        // Summer, known & correct DST state (DST == true).
+        {
+            String summerTimeNitzStringWithDst = summerTimeNitzString + ",1";
+            NitzData nitzData = NitzData.parse(summerTimeNitzStringWithDst);
+            int expectedUtcOffset = (int) TimeUnit.HOURS.toMillis(2);
+            Integer expectedDstOffset = (int) TimeUnit.HOURS.toMillis(1);
+            assertEquals(expectedUtcOffset, nitzData.getLocalOffsetMillis());
+            assertEquals(expectedDstOffset, nitzData.getDstAdjustmentMillis());
+
+            OffsetResult adSummerWithDstResult =
+                    mTimeZoneLookupHelper.lookupByNitzCountry(nitzData, adIso);
+            OffsetResult expectedResult =
+                    new OffsetResult(zone("Europe/Andorra"), true /* isOnlyMatch */);
+            assertEquals(expectedResult, adSummerWithDstResult);
+            assertOffsetResultZoneOffsets(nhSummerTimeMillis, expectedUtcOffset, expectedDstOffset,
+                    adSummerWithDstResult);
+        }
+
+        // Summer, known & incorrect DST state (DST == false)
+        {
+            String summerTimeNitzStringWithNoDst = summerTimeNitzString + ",0";
+            NitzData nitzData = NitzData.parse(summerTimeNitzStringWithNoDst);
+
+            OffsetResult adSummerWithNoDstResult =
+                    mTimeZoneLookupHelper.lookupByNitzCountry(nitzData, adIso);
+            assertNull(adSummerWithNoDstResult);
+        }
+
+        // Winter, known & correct DST state (DST == false)
+        {
+            String winterTimeNitzStringWithNoDst = winterTimeNitzString + ",0";
+            NitzData nitzData = NitzData.parse(winterTimeNitzStringWithNoDst);
+            int expectedUtcOffset = (int) TimeUnit.HOURS.toMillis(1);
+            Integer expectedDstOffset = 0;
+            assertEquals(expectedUtcOffset, nitzData.getLocalOffsetMillis());
+            assertEquals(expectedDstOffset, nitzData.getDstAdjustmentMillis());
+
+            OffsetResult adWinterWithDstResult =
+                    mTimeZoneLookupHelper.lookupByNitzCountry(nitzData, adIso);
+            OffsetResult expectedResult =
+                    new OffsetResult(zone("Europe/Andorra"), true /* isOnlyMatch */);
+            assertEquals(expectedResult, adWinterWithDstResult);
+            assertOffsetResultZoneOffsets(nhWinterTimeMillis, expectedUtcOffset, expectedDstOffset,
+                    adWinterWithDstResult);
+        }
+
+        // Winter, known & incorrect DST state (DST == true)
+        {
+            String winterTimeNitzStringWithDst = winterTimeNitzString + ",1";
+            NitzData nitzData = NitzData.parse(winterTimeNitzStringWithDst);
+
+            OffsetResult adWinterWithDstResult =
+                    mTimeZoneLookupHelper.lookupByNitzCountry(nitzData, adIso);
+            assertNull(adWinterWithDstResult);
+        }
+
+        // Summer, unknown DST state (will match any DST state with the correct offset).
+        {
+            NitzData nitzData = NitzData.parse(summerTimeNitzString);
+            int expectedUtcOffset = (int) TimeUnit.HOURS.toMillis(2);
+            Integer expectedDstOffset = null; // Unknown
+            assertEquals(expectedUtcOffset, nitzData.getLocalOffsetMillis());
+            assertEquals(expectedDstOffset, nitzData.getDstAdjustmentMillis());
+
+            OffsetResult adSummerUnknownDstResult =
+                    mTimeZoneLookupHelper.lookupByNitzCountry(nitzData, adIso);
+            OffsetResult expectedResult =
+                    new OffsetResult(zone("Europe/Andorra"), true /* isOnlyMatch */);
+            assertEquals(expectedResult, adSummerUnknownDstResult);
+            assertOffsetResultZoneOffsets(nhSummerTimeMillis, expectedUtcOffset, expectedDstOffset,
+                    adSummerUnknownDstResult);
+        }
+
+        // Winter, unknown DST state (will match any DST state with the correct offset)
+        {
+            NitzData nitzData = NitzData.parse(winterTimeNitzString);
+            int expectedUtcOffset = (int) TimeUnit.HOURS.toMillis(1);
+            Integer expectedDstOffset = null; // Unknown
+            assertEquals(expectedUtcOffset, nitzData.getLocalOffsetMillis());
+            assertEquals(expectedDstOffset, nitzData.getDstAdjustmentMillis());
+
+            OffsetResult adWinterUnknownDstResult =
+                    mTimeZoneLookupHelper.lookupByNitzCountry(nitzData, adIso);
+            OffsetResult expectedResult =
+                    new OffsetResult(zone("Europe/Andorra"), true /* isOnlyMatch */);
+            assertEquals(expectedResult, adWinterUnknownDstResult);
+            assertOffsetResultZoneOffsets(nhWinterTimeMillis, expectedUtcOffset, expectedDstOffset,
+                    adWinterUnknownDstResult);
+        }
+    }
+
+    @Test
+    public void testLookupByCountry_oneZone() {
+        // GB has one time zone.
+        CountryResult expectedResult =
+                new CountryResult("Europe/London", QUALITY_SINGLE_ZONE, ARBITRARY_DEBUG_INFO);
+        assertEquals(expectedResult,
+                mTimeZoneLookupHelper.lookupByCountry("gb", NH_SUMMER_TIME_MILLIS));
+        assertEquals(expectedResult,
+                mTimeZoneLookupHelper.lookupByCountry("gb", NH_WINTER_TIME_MILLIS));
+    }
+
+    @Test
+    public void testLookupByCountry_oneEffectiveZone() {
+        // Historical dates are used to avoid the test breaking due to data changes.
+
+        // DE has two time zones according to IANA data: Europe/Berlin and Europe/Busingen, but they
+        // become effectively identical after 338950800000 millis (Sun, 28 Sep 1980 01:00:00 GMT).
+        // Android data tells us that Europe/Berlin the one that was "kept".
+        long nhSummerTimeMillis = createUtcTime(1975, 6, 20, 1, 2, 3);
+        long nhWinterTimeMillis = createUtcTime(1975, 1, 20, 1, 2, 3);
+
+        // Before 1980, quality == QUALITY_MULTIPLE_ZONES_SAME_OFFSET because Europe/Busingen was
+        // relevant.
+        CountryResult expectedResult = new CountryResult(
+                "Europe/Berlin", QUALITY_MULTIPLE_ZONES_SAME_OFFSET, ARBITRARY_DEBUG_INFO);
+        assertEquals(expectedResult,
+                mTimeZoneLookupHelper.lookupByCountry("de", nhSummerTimeMillis));
+        assertEquals(expectedResult,
+                mTimeZoneLookupHelper.lookupByCountry("de", nhWinterTimeMillis));
+
+        // And in 2015, quality == QUALITY_SINGLE_ZONE because Europe/Busingen became irrelevant
+        // after 1980.
+        nhSummerTimeMillis = createUtcTime(2015, 6, 20, 1, 2, 3);
+        nhWinterTimeMillis = createUtcTime(2015, 1, 20, 1, 2, 3);
+
+        expectedResult =
+                new CountryResult("Europe/Berlin", QUALITY_SINGLE_ZONE, ARBITRARY_DEBUG_INFO);
+        assertEquals(expectedResult,
+                mTimeZoneLookupHelper.lookupByCountry("de", nhSummerTimeMillis));
+        assertEquals(expectedResult,
+                mTimeZoneLookupHelper.lookupByCountry("de", nhWinterTimeMillis));
+    }
+
+    @Test
+    public void testDefaultBoostBehavior() {
+        long timeMillis = createUtcTime(2015, 6, 20, 1, 2, 3);
+
+        // An example known to be explicitly boosted. New Zealand has two zones but the vast
+        // majority of the population use one of them so Android's data file explicitly boosts the
+        // country default. If that changes in future this test will need to be changed to use
+        // another example.
+        String countryIsoCode = "nz";
+
+        CountryResult expectedResult = new CountryResult(
+                "Pacific/Auckland", QUALITY_DEFAULT_BOOSTED, ARBITRARY_DEBUG_INFO);
+        assertEquals(expectedResult,
+                mTimeZoneLookupHelper.lookupByCountry(countryIsoCode, timeMillis));
+
+        // Data correct for the North and South Island.
+        int majorityWinterOffset = (int) TimeUnit.HOURS.toMillis(12);
+        NitzData majorityNitzData = NitzData.createForTests(
+                majorityWinterOffset, 0, timeMillis, null /* emulatorTimeZone */);
+
+        // Boost doesn't directly affect lookupByNitzCountry()
+        OffsetResult majorityOffsetResult =
+                mTimeZoneLookupHelper.lookupByNitzCountry(majorityNitzData, countryIsoCode);
+        assertEquals(zone("Pacific/Auckland"), majorityOffsetResult.getTimeZone());
+        assertTrue(majorityOffsetResult.isOnlyMatch());
+
+        // Data correct for the Chatham Islands.
+        int chathamWinterOffset = majorityWinterOffset + ((int) TimeUnit.MINUTES.toMillis(45));
+        NitzData chathamNitzData = NitzData.createForTests(
+                chathamWinterOffset, 0, timeMillis, null /* emulatorTimeZone */);
+        OffsetResult chathamOffsetResult =
+                mTimeZoneLookupHelper.lookupByNitzCountry(chathamNitzData, countryIsoCode);
+        assertEquals(zone("Pacific/Chatham"), chathamOffsetResult.getTimeZone());
+        assertTrue(chathamOffsetResult.isOnlyMatch());
+
+        // NITZ data that makes no sense for NZ results in no match.
+        int nonsenseOffset = (int) TimeUnit.HOURS.toMillis(5);
+        NitzData nonsenseNitzData = NitzData.createForTests(
+                nonsenseOffset, 0, timeMillis, null /* emulatorTimeZone */);
+        OffsetResult nonsenseOffsetResult =
+                mTimeZoneLookupHelper.lookupByNitzCountry(nonsenseNitzData, countryIsoCode);
+        assertNull(nonsenseOffsetResult);
+    }
+
+    @Test
+    public void testNoDefaultBoostBehavior() {
+        long timeMillis = createUtcTime(2015, 6, 20, 1, 2, 3);
+
+        // An example known to not be explicitly boosted. Micronesia is spread out and there's no
+        // suitable default.
+        String countryIsoCode = "fm";
+
+        CountryResult expectedResult = new CountryResult(
+                "Pacific/Pohnpei", QUALITY_MULTIPLE_ZONES_DIFFERENT_OFFSETS, ARBITRARY_DEBUG_INFO);
+        assertEquals(expectedResult,
+                mTimeZoneLookupHelper.lookupByCountry(countryIsoCode, timeMillis));
+
+        // Prove an OffsetResult can be found with the correct offset.
+        int chuukWinterOffset = (int) TimeUnit.HOURS.toMillis(10);
+        NitzData chuukNitzData = NitzData.createForTests(
+                chuukWinterOffset, 0, timeMillis, null /* emulatorTimeZone */);
+        OffsetResult chuukOffsetResult =
+                mTimeZoneLookupHelper.lookupByNitzCountry(chuukNitzData, countryIsoCode);
+        assertEquals(zone("Pacific/Chuuk"), chuukOffsetResult.getTimeZone());
+        assertTrue(chuukOffsetResult.isOnlyMatch());
+
+        // NITZ data that makes no sense for FM: no boost means we should get nothing.
+        int nonsenseOffset = (int) TimeUnit.HOURS.toMillis(5);
+        NitzData nonsenseNitzData = NitzData.createForTests(
+                nonsenseOffset, 0, timeMillis, null /* emulatorTimeZone */);
+        OffsetResult nonsenseOffsetResult =
+                mTimeZoneLookupHelper.lookupByNitzCountry(nonsenseNitzData, countryIsoCode);
+        assertNull(nonsenseOffsetResult);
+    }
+
+    @Test
+    public void testLookupByCountry_multipleZones() {
+        // US has many time zones that have different offsets.
+        CountryResult expectedResult = new CountryResult(
+                "America/New_York", QUALITY_MULTIPLE_ZONES_DIFFERENT_OFFSETS, ARBITRARY_DEBUG_INFO);
+        assertEquals(expectedResult,
+                mTimeZoneLookupHelper.lookupByCountry("us", NH_SUMMER_TIME_MILLIS));
+        assertEquals(expectedResult,
+                mTimeZoneLookupHelper.lookupByCountry("us", NH_WINTER_TIME_MILLIS));
+    }
+
+    @Test
+    public void testCountryUsesUtc() {
+        assertFalse(mTimeZoneLookupHelper.countryUsesUtc("us", NH_SUMMER_TIME_MILLIS));
+        assertFalse(mTimeZoneLookupHelper.countryUsesUtc("us", NH_WINTER_TIME_MILLIS));
+        assertFalse(mTimeZoneLookupHelper.countryUsesUtc("gb", NH_SUMMER_TIME_MILLIS));
+        assertTrue(mTimeZoneLookupHelper.countryUsesUtc("gb", NH_WINTER_TIME_MILLIS));
+    }
+
+    /**
+     * Assert the time zone in the OffsetResult has the expected properties at the specified time.
+     */
+    private static void assertOffsetResultZoneOffsets(long time, int expectedOffsetAtTime,
+            Integer expectedDstAtTime, OffsetResult lookupResult) {
+
+        TimeZone timeZone = lookupResult.getTimeZone();
+        GregorianCalendar calendar = new GregorianCalendar(timeZone);
+        calendar.setTimeInMillis(time);
+        int actualOffsetAtTime =
+                calendar.get(Calendar.ZONE_OFFSET) + calendar.get(Calendar.DST_OFFSET);
+        assertEquals(expectedOffsetAtTime, actualOffsetAtTime);
+
+        if (expectedDstAtTime != null) {
+            Date date = new Date(time);
+            assertEquals(expectedDstAtTime > 0, timeZone.inDaylightTime(date));
+
+            // The code under test assumes DST means +1 in all cases,
+            // This code makes fewer assumptions.
+            assertEquals(expectedDstAtTime.intValue(), calendar.get(Calendar.DST_OFFSET));
+        }
+    }
+
+    private static void assertOffsetResultMetadata(boolean isOnlyMatch, OffsetResult lookupResult) {
+        assertEquals(isOnlyMatch, lookupResult.isOnlyMatch());
+    }
+
+    private static long createUtcTime(
+            int year, int monthOfYear, int dayOfMonth, int hourOfDay, int minute, int second) {
+        GregorianCalendar calendar = new GregorianCalendar(zone("UTC"));
+        calendar.clear(); // Clear millis, etc.
+        calendar.set(year, monthOfYear - 1, dayOfMonth, hourOfDay, minute, second);
+        return calendar.getTimeInMillis();
+    }
+
+    private static TimeZone zone(String zoneId) {
+        return TimeZone.getFrozenTimeZone(zoneId);
+    }
+}
diff --git a/tests/telephonytests/src/com/android/internal/telephony/nitz/TimeZoneSuggesterImplTest.java b/tests/telephonytests/src/com/android/internal/telephony/nitz/TimeZoneSuggesterImplTest.java
new file mode 100644
index 0000000..708d3de
--- /dev/null
+++ b/tests/telephonytests/src/com/android/internal/telephony/nitz/TimeZoneSuggesterImplTest.java
@@ -0,0 +1,655 @@
+/*
+ * Copyright 2019 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.nitz;
+
+import static android.app.timezonedetector.TelephonyTimeZoneSuggestion.MATCH_TYPE_EMULATOR_ZONE_ID;
+import static android.app.timezonedetector.TelephonyTimeZoneSuggestion.MATCH_TYPE_NETWORK_COUNTRY_AND_OFFSET;
+import static android.app.timezonedetector.TelephonyTimeZoneSuggestion.MATCH_TYPE_NETWORK_COUNTRY_ONLY;
+import static android.app.timezonedetector.TelephonyTimeZoneSuggestion.MATCH_TYPE_TEST_NETWORK_OFFSET_ONLY;
+import static android.app.timezonedetector.TelephonyTimeZoneSuggestion.QUALITY_MULTIPLE_ZONES_WITH_DIFFERENT_OFFSETS;
+import static android.app.timezonedetector.TelephonyTimeZoneSuggestion.QUALITY_MULTIPLE_ZONES_WITH_SAME_OFFSET;
+import static android.app.timezonedetector.TelephonyTimeZoneSuggestion.QUALITY_SINGLE_ZONE;
+
+import static com.android.internal.telephony.nitz.NitzStateMachineTestSupport.ARBITRARY_REALTIME_MILLIS;
+import static com.android.internal.telephony.nitz.NitzStateMachineTestSupport.CZECHIA_SCENARIO;
+import static com.android.internal.telephony.nitz.NitzStateMachineTestSupport.NEW_ZEALAND_COUNTRY_DEFAULT_ZONE_ID;
+import static com.android.internal.telephony.nitz.NitzStateMachineTestSupport.NEW_ZEALAND_DEFAULT_SCENARIO;
+import static com.android.internal.telephony.nitz.NitzStateMachineTestSupport.NEW_ZEALAND_OTHER_SCENARIO;
+import static com.android.internal.telephony.nitz.NitzStateMachineTestSupport.NON_UNIQUE_US_ZONE_SCENARIO;
+import static com.android.internal.telephony.nitz.NitzStateMachineTestSupport.NON_UNIQUE_US_ZONE_SCENARIO_ZONES;
+import static com.android.internal.telephony.nitz.NitzStateMachineTestSupport.UNIQUE_US_ZONE_SCENARIO1;
+import static com.android.internal.telephony.nitz.NitzStateMachineTestSupport.UNIQUE_US_ZONE_SCENARIO2;
+import static com.android.internal.telephony.nitz.NitzStateMachineTestSupport.UNITED_KINGDOM_SCENARIO;
+import static com.android.internal.telephony.nitz.NitzStateMachineTestSupport.US_COUNTRY_DEFAULT_ZONE_ID;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import android.app.timezonedetector.TelephonyTimeZoneSuggestion;
+import android.os.TimestampedValue;
+
+import com.android.internal.telephony.NitzData;
+import com.android.internal.telephony.TelephonyTest;
+import com.android.internal.telephony.nitz.NitzStateMachineImpl.TimeZoneSuggester;
+import com.android.internal.telephony.nitz.NitzStateMachineTestSupport.FakeDeviceState;
+import com.android.internal.telephony.nitz.NitzStateMachineTestSupport.Scenario;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.util.Arrays;
+import java.util.List;
+
+public class TimeZoneSuggesterImplTest extends TelephonyTest {
+
+    private static final int SLOT_INDEX = 99999;
+    private static final TelephonyTimeZoneSuggestion EMPTY_TIME_ZONE_SUGGESTION =
+            new TelephonyTimeZoneSuggestion.Builder(SLOT_INDEX).build();
+
+    private FakeDeviceState mFakeDeviceState;
+    private TimeZoneSuggester mTimeZoneSuggester;
+
+    @Before
+    public void setUp() throws Exception {
+        TelephonyTest.logd("TimeZoneSuggesterImplTest +Setup!");
+        super.setUp("TimeZoneSuggesterImplTest");
+
+        // In tests a fake impl is used for DeviceState, which allows historic data to be used.
+        mFakeDeviceState = new FakeDeviceState();
+
+        // In tests the real TimeZoneLookupHelper implementation is used: this makes it easy to
+        // construct tests using known historic examples.
+        TimeZoneLookupHelper timeZoneLookupHelper = new TimeZoneLookupHelper();
+        mTimeZoneSuggester = new TimeZoneSuggesterImpl(mFakeDeviceState, timeZoneLookupHelper);
+
+        TelephonyTest.logd("TimeZoneSuggesterImplTest -Setup!");
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        super.tearDown();
+    }
+
+    @Test
+    public void test_emptySuggestionForNullCountryNullNitz() throws Exception {
+        assertEquals(EMPTY_TIME_ZONE_SUGGESTION,
+                mTimeZoneSuggester.getTimeZoneSuggestion(
+                        SLOT_INDEX, null /* countryIsoCode */, null /* nitzSignal */));
+    }
+
+    @Test
+    public void test_emptySuggestionForNullCountryWithNitz() throws Exception {
+        Scenario scenario = UNIQUE_US_ZONE_SCENARIO1;
+        TimestampedValue<NitzData> nitzSignal =
+                scenario.createNitzSignal(ARBITRARY_REALTIME_MILLIS);
+        assertEquals(EMPTY_TIME_ZONE_SUGGESTION,
+                mTimeZoneSuggester.getTimeZoneSuggestion(
+                        SLOT_INDEX, null /* countryIsoCode */, nitzSignal));
+    }
+
+    @Test
+    public void test_emptySuggestionForEmptyCountryNullNitz() throws Exception {
+        assertEquals(EMPTY_TIME_ZONE_SUGGESTION,
+                mTimeZoneSuggester.getTimeZoneSuggestion(
+                        SLOT_INDEX, "" /* countryIsoCoe */, null /* nitzSignal */));
+    }
+
+    /**
+     * Tests behavior for various scenarios for a user in the US. The US is a complicated case
+     * with multiple time zones, some overlapping and with no good default. The scenario used here
+     * is a "unique" scenario, meaning it is possible to determine the correct zone using both
+     * country and NITZ information.
+     */
+    @Test
+    public void test_uniqueUsZone() throws Exception {
+        Scenario scenario = UNIQUE_US_ZONE_SCENARIO1;
+
+        // Country won't be enough to get a quality result for time zone detection but a suggestion
+        // will be made.
+        {
+            TelephonyTimeZoneSuggestion expectedTimeZoneSuggestion =
+                    new TelephonyTimeZoneSuggestion.Builder(SLOT_INDEX)
+                            .setZoneId(US_COUNTRY_DEFAULT_ZONE_ID)
+                            .setMatchType(MATCH_TYPE_NETWORK_COUNTRY_ONLY)
+                            .setQuality(QUALITY_MULTIPLE_ZONES_WITH_DIFFERENT_OFFSETS)
+                            .build();
+
+            TelephonyTimeZoneSuggestion actualSuggestion = mTimeZoneSuggester.getTimeZoneSuggestion(
+                    SLOT_INDEX, scenario.getNetworkCountryIsoCode(), null /* nitzSignal */);
+            assertEquals(expectedTimeZoneSuggestion, actualSuggestion);
+        }
+
+        // NITZ with a "" country code is interpreted as a test network so only offset is used
+        // to get a match.
+        {
+            TelephonyTimeZoneSuggestion actualSuggestion = mTimeZoneSuggester.getTimeZoneSuggestion(
+                    SLOT_INDEX, "" /* countryIsoCode */,
+                    scenario.createNitzSignal(mFakeDeviceState.elapsedRealtime()));
+            assertEquals(SLOT_INDEX, actualSuggestion.getSlotIndex());
+            assertEquals(MATCH_TYPE_TEST_NETWORK_OFFSET_ONLY, actualSuggestion.getMatchType());
+            assertEquals(QUALITY_MULTIPLE_ZONES_WITH_SAME_OFFSET, actualSuggestion.getQuality());
+        }
+
+        // NITZ alone is not enough to get a result when the country is not available.
+        {
+            TelephonyTimeZoneSuggestion actualSuggestion = mTimeZoneSuggester.getTimeZoneSuggestion(
+                    SLOT_INDEX, null /* countryIsoCode */,
+                    scenario.createNitzSignal(mFakeDeviceState.elapsedRealtime()));
+            assertEquals(EMPTY_TIME_ZONE_SUGGESTION, actualSuggestion);
+        }
+
+        // Country + NITZ is enough for a unique time zone detection result for this scenario.
+        {
+            TelephonyTimeZoneSuggestion expectedTimeZoneSuggestion =
+                    new TelephonyTimeZoneSuggestion.Builder(SLOT_INDEX)
+                            .setZoneId(scenario.getTimeZoneId())
+                            .setMatchType(MATCH_TYPE_NETWORK_COUNTRY_AND_OFFSET)
+                            .setQuality(QUALITY_SINGLE_ZONE)
+                            .build();
+            TelephonyTimeZoneSuggestion actualSuggestion = mTimeZoneSuggester.getTimeZoneSuggestion(
+                    SLOT_INDEX, scenario.getNetworkCountryIsoCode(),
+                    scenario.createNitzSignal(mFakeDeviceState.elapsedRealtime()));
+            assertEquals(expectedTimeZoneSuggestion, actualSuggestion);
+        }
+
+        // Country + NITZ with a bad offset should not trigger fall back, country-only behavior
+        // since there are multiple zones to choose from.
+        {
+            // We use an NITZ from CZ to generate an NITZ signal with a bad offset.
+            TimestampedValue<NitzData> badNitzSignal =
+                    CZECHIA_SCENARIO.createNitzSignal(mFakeDeviceState.elapsedRealtime());
+            TelephonyTimeZoneSuggestion expectedTimeZoneSuggestion = EMPTY_TIME_ZONE_SUGGESTION;
+            TelephonyTimeZoneSuggestion actualSuggestion = mTimeZoneSuggester.getTimeZoneSuggestion(
+                    SLOT_INDEX, scenario.getNetworkCountryIsoCode(),
+                    badNitzSignal);
+            assertEquals(expectedTimeZoneSuggestion, actualSuggestion);
+        }
+    }
+
+    /**
+     * Tests behavior for various scenarios for a user in the US. The US is a complicated case
+     * with multiple time zones, some overlapping and with no good default. The scenario used here
+     * is a "non unique" scenario, meaning it is not possible to determine the a single zone using
+     * both country and NITZ information.
+     */
+    @Test
+    public void test_nonUniqueUsZone() throws Exception {
+        Scenario scenario = NON_UNIQUE_US_ZONE_SCENARIO;
+
+        // Country won't be enough to get a quality result for time zone detection but a suggestion
+        // will be made.
+        {
+            TelephonyTimeZoneSuggestion expectedTimeZoneSuggestion =
+                    new TelephonyTimeZoneSuggestion.Builder(SLOT_INDEX)
+                            .setZoneId(US_COUNTRY_DEFAULT_ZONE_ID)
+                            .setMatchType(MATCH_TYPE_NETWORK_COUNTRY_ONLY)
+                            .setQuality(QUALITY_MULTIPLE_ZONES_WITH_DIFFERENT_OFFSETS)
+                            .build();
+
+            TelephonyTimeZoneSuggestion actualSuggestion = mTimeZoneSuggester.getTimeZoneSuggestion(
+                    SLOT_INDEX, scenario.getNetworkCountryIsoCode(), null /* nitzSignal */);
+            assertEquals(expectedTimeZoneSuggestion, actualSuggestion);
+        }
+
+        // NITZ with a "" country code is interpreted as a test network so only offset is used
+        // to get a match.
+        {
+            TelephonyTimeZoneSuggestion actualSuggestion = mTimeZoneSuggester.getTimeZoneSuggestion(
+                    SLOT_INDEX, "" /* countryIsoCode */,
+                    scenario.createNitzSignal(mFakeDeviceState.elapsedRealtime()));
+            assertEquals(SLOT_INDEX, actualSuggestion.getSlotIndex());
+            assertEquals(MATCH_TYPE_TEST_NETWORK_OFFSET_ONLY, actualSuggestion.getMatchType());
+            assertEquals(QUALITY_MULTIPLE_ZONES_WITH_SAME_OFFSET, actualSuggestion.getQuality());
+        }
+
+        // NITZ alone is not enough to get a result when the country is not available.
+        {
+            TelephonyTimeZoneSuggestion actualSuggestion = mTimeZoneSuggester.getTimeZoneSuggestion(
+                    SLOT_INDEX, null /* countryIsoCode */,
+                    scenario.createNitzSignal(mFakeDeviceState.elapsedRealtime()));
+            assertEquals(EMPTY_TIME_ZONE_SUGGESTION, actualSuggestion);
+        }
+
+        // Country + NITZ is not enough for a unique time zone detection result for this scenario.
+        {
+            TelephonyTimeZoneSuggestion actualSuggestion = mTimeZoneSuggester.getTimeZoneSuggestion(
+                    SLOT_INDEX, scenario.getNetworkCountryIsoCode(),
+                    scenario.createNitzSignal(mFakeDeviceState.elapsedRealtime()));
+            assertEquals(SLOT_INDEX, actualSuggestion.getSlotIndex());
+            assertEquals(MATCH_TYPE_NETWORK_COUNTRY_AND_OFFSET, actualSuggestion.getMatchType());
+            assertEquals(QUALITY_MULTIPLE_ZONES_WITH_SAME_OFFSET, actualSuggestion.getQuality());
+            List<String> allowedZoneIds = Arrays.asList(NON_UNIQUE_US_ZONE_SCENARIO_ZONES);
+            assertTrue(allowedZoneIds.contains(actualSuggestion.getZoneId()));
+        }
+
+        // Country + NITZ with a bad offset should not trigger fall back, country-only behavior
+        // since there are multiple zones to choose from.
+        {
+            // We use an NITZ from CZ to generate an NITZ signal with a bad offset.
+            TimestampedValue<NitzData> badNitzSignal =
+                    CZECHIA_SCENARIO.createNitzSignal(mFakeDeviceState.elapsedRealtime());
+            TelephonyTimeZoneSuggestion expectedTimeZoneSuggestion = EMPTY_TIME_ZONE_SUGGESTION;
+            TelephonyTimeZoneSuggestion actualSuggestion = mTimeZoneSuggester.getTimeZoneSuggestion(
+                    SLOT_INDEX, scenario.getNetworkCountryIsoCode(),
+                    badNitzSignal);
+            assertEquals(expectedTimeZoneSuggestion, actualSuggestion);
+        }
+    }
+
+    /**
+     * Tests behavior for various scenarios for a user in the UK. The UK is simple: it has a single
+     * time zone so only the country needs to be known to find a time zone. It is special in that
+     * it uses UTC for some of the year, which makes it difficult to detect bogus NITZ signals with
+     * zero'd offset information.
+     */
+    @Test
+    public void test_unitedKingdom() throws Exception {
+        Scenario scenario = UNITED_KINGDOM_SCENARIO;
+
+        // Country alone is enough to guess the time zone.
+        {
+            TelephonyTimeZoneSuggestion expectedTimeZoneSuggestion =
+                    new TelephonyTimeZoneSuggestion.Builder(SLOT_INDEX)
+                            .setZoneId(scenario.getTimeZoneId())
+                            .setMatchType(MATCH_TYPE_NETWORK_COUNTRY_ONLY)
+                            .setQuality(QUALITY_SINGLE_ZONE)
+                            .build();
+
+            TelephonyTimeZoneSuggestion actualSuggestion = mTimeZoneSuggester.getTimeZoneSuggestion(
+                    SLOT_INDEX, scenario.getNetworkCountryIsoCode(), null /* nitzSignal */);
+            assertEquals(expectedTimeZoneSuggestion, actualSuggestion);
+        }
+
+        // NITZ with a "" country code is interpreted as a test network so only offset is used
+        // to get a match.
+        {
+            TelephonyTimeZoneSuggestion actualSuggestion = mTimeZoneSuggester.getTimeZoneSuggestion(
+                    SLOT_INDEX, "" /* countryIsoCode */,
+                    scenario.createNitzSignal(mFakeDeviceState.elapsedRealtime()));
+            assertEquals(SLOT_INDEX, actualSuggestion.getSlotIndex());
+            assertEquals(MATCH_TYPE_TEST_NETWORK_OFFSET_ONLY, actualSuggestion.getMatchType());
+            assertEquals(QUALITY_MULTIPLE_ZONES_WITH_SAME_OFFSET, actualSuggestion.getQuality());
+
+        }
+
+        // NITZ alone is not enough to get a result when the country is not available.
+        {
+            TelephonyTimeZoneSuggestion actualSuggestion = mTimeZoneSuggester.getTimeZoneSuggestion(
+                    SLOT_INDEX, null /* countryIsoCode */,
+                    scenario.createNitzSignal(mFakeDeviceState.elapsedRealtime()));
+            assertEquals(EMPTY_TIME_ZONE_SUGGESTION, actualSuggestion);
+        }
+
+        // Country + NITZ is enough for both time + time zone detection.
+        {
+            TelephonyTimeZoneSuggestion expectedTimeZoneSuggestion =
+                    new TelephonyTimeZoneSuggestion.Builder(SLOT_INDEX)
+                            .setZoneId(scenario.getTimeZoneId())
+                            .setMatchType(MATCH_TYPE_NETWORK_COUNTRY_AND_OFFSET)
+                            .setQuality(QUALITY_SINGLE_ZONE)
+                            .build();
+
+            TelephonyTimeZoneSuggestion actualSuggestion = mTimeZoneSuggester.getTimeZoneSuggestion(
+                    SLOT_INDEX, scenario.getNetworkCountryIsoCode(),
+                    scenario.createNitzSignal(mFakeDeviceState.elapsedRealtime()));
+            assertEquals(expectedTimeZoneSuggestion, actualSuggestion);
+        }
+
+        // Country + NITZ with a bad offset should trigger fall back, country-only behavior since
+        // there's only one zone.
+        {
+            // We use an NITZ from Czechia to generate an NITZ signal with a bad offset.
+            TimestampedValue<NitzData> badNitzSignal =
+                    CZECHIA_SCENARIO.createNitzSignal(mFakeDeviceState.elapsedRealtime());
+            TelephonyTimeZoneSuggestion expectedTimeZoneSuggestion =
+                    new TelephonyTimeZoneSuggestion.Builder(SLOT_INDEX)
+                            .setZoneId(scenario.getTimeZoneId())
+                            .setMatchType(MATCH_TYPE_NETWORK_COUNTRY_ONLY)
+                            .setQuality(QUALITY_SINGLE_ZONE)
+                            .build();
+
+            TelephonyTimeZoneSuggestion actualSuggestion = mTimeZoneSuggester.getTimeZoneSuggestion(
+                    SLOT_INDEX, scenario.getNetworkCountryIsoCode(),
+                    badNitzSignal);
+            assertEquals(expectedTimeZoneSuggestion, actualSuggestion);
+        }
+    }
+
+    /**
+     * Tests behavior for various scenarios for a user in Czechia. CZ is simple: it has a single
+     * time zone so only the country needs to be known to find a time zone. It never uses UTC so it
+     * is useful to contrast with the UK and can be used for bogus signal detection.
+     */
+    @Test
+    public void test_cz() throws Exception {
+        Scenario scenario = CZECHIA_SCENARIO;
+
+        // Country alone is enough to guess the time zone.
+        {
+            TelephonyTimeZoneSuggestion expectedTimeZoneSuggestion =
+                    new TelephonyTimeZoneSuggestion.Builder(SLOT_INDEX)
+                            .setZoneId(scenario.getTimeZoneId())
+                            .setMatchType(MATCH_TYPE_NETWORK_COUNTRY_ONLY)
+                            .setQuality(QUALITY_SINGLE_ZONE)
+                            .build();
+
+            TelephonyTimeZoneSuggestion actualSuggestion = mTimeZoneSuggester.getTimeZoneSuggestion(
+                    SLOT_INDEX, scenario.getNetworkCountryIsoCode(), null /* nitzSignal */);
+            assertEquals(expectedTimeZoneSuggestion, actualSuggestion);
+        }
+
+        // NITZ with a "" country code is interpreted as a test network so only offset is used
+        // to get a match.
+        {
+            TelephonyTimeZoneSuggestion actualSuggestion =
+                    mTimeZoneSuggester.getTimeZoneSuggestion(
+                            SLOT_INDEX, "" /* countryIsoCode */,
+                            scenario.createNitzSignal(mFakeDeviceState.elapsedRealtime()));
+            assertEquals(SLOT_INDEX, actualSuggestion.getSlotIndex());
+            assertEquals(MATCH_TYPE_TEST_NETWORK_OFFSET_ONLY, actualSuggestion.getMatchType());
+            assertEquals(QUALITY_MULTIPLE_ZONES_WITH_SAME_OFFSET, actualSuggestion.getQuality());
+
+        }
+
+        // NITZ alone is not enough to get a result when the country is not available.
+        {
+            TelephonyTimeZoneSuggestion actualSuggestion = mTimeZoneSuggester.getTimeZoneSuggestion(
+                    SLOT_INDEX, null /* countryIsoCode */,
+                    scenario.createNitzSignal(mFakeDeviceState.elapsedRealtime()));
+            assertEquals(EMPTY_TIME_ZONE_SUGGESTION, actualSuggestion);
+        }
+
+        // Country + NITZ is enough for both time + time zone detection.
+        {
+            TelephonyTimeZoneSuggestion expectedTimeZoneSuggestion =
+                    new TelephonyTimeZoneSuggestion.Builder(SLOT_INDEX)
+                            .setZoneId(scenario.getTimeZoneId())
+                            .setMatchType(MATCH_TYPE_NETWORK_COUNTRY_AND_OFFSET)
+                            .setQuality(QUALITY_SINGLE_ZONE)
+                            .build();
+
+            TelephonyTimeZoneSuggestion actualSuggestion = mTimeZoneSuggester.getTimeZoneSuggestion(
+                    SLOT_INDEX, scenario.getNetworkCountryIsoCode(),
+                    scenario.createNitzSignal(mFakeDeviceState.elapsedRealtime()));
+            assertEquals(expectedTimeZoneSuggestion, actualSuggestion);
+        }
+
+        // Country + NITZ with a bad offset should trigger fall back, country-only behavior since
+        // there's only one zone.
+        {
+            // We use an NITZ from the US to generate an NITZ signal with a bad offset.
+            TimestampedValue<NitzData> badNitzSignal =
+                    UNIQUE_US_ZONE_SCENARIO1.createNitzSignal(mFakeDeviceState.elapsedRealtime());
+            TelephonyTimeZoneSuggestion expectedTimeZoneSuggestion =
+                    new TelephonyTimeZoneSuggestion.Builder(SLOT_INDEX)
+                            .setZoneId(scenario.getTimeZoneId())
+                            .setMatchType(MATCH_TYPE_NETWORK_COUNTRY_ONLY)
+                            .setQuality(QUALITY_SINGLE_ZONE)
+                            .build();
+
+            TelephonyTimeZoneSuggestion actualSuggestion = mTimeZoneSuggester.getTimeZoneSuggestion(
+                    SLOT_INDEX, scenario.getNetworkCountryIsoCode(),
+                    badNitzSignal);
+            assertEquals(expectedTimeZoneSuggestion, actualSuggestion);
+        }
+    }
+
+    @Test
+    public void test_bogusCzNitzSignal() throws Exception {
+        Scenario scenario = CZECHIA_SCENARIO;
+
+        // Country alone is enough to guess the time zone.
+        {
+            TelephonyTimeZoneSuggestion expectedTimeZoneSuggestion =
+                    new TelephonyTimeZoneSuggestion.Builder(SLOT_INDEX)
+                            .setZoneId(scenario.getTimeZoneId())
+                            .setMatchType(MATCH_TYPE_NETWORK_COUNTRY_ONLY)
+                            .setQuality(QUALITY_SINGLE_ZONE)
+                            .build();
+
+            TelephonyTimeZoneSuggestion actualSuggestion = mTimeZoneSuggester.getTimeZoneSuggestion(
+                    SLOT_INDEX, scenario.getNetworkCountryIsoCode(), null /* nitzSignal */);
+            assertEquals(expectedTimeZoneSuggestion, actualSuggestion);
+        }
+
+        // NITZ + bogus NITZ is not enough to get a result.
+        {
+            // Create a corrupted NITZ signal, where the offset information has been lost.
+            TimestampedValue<NitzData> goodNitzSignal =
+                    scenario.createNitzSignal(mFakeDeviceState.elapsedRealtime());
+            NitzData bogusNitzData = NitzData.createForTests(
+                    0 /* UTC! */, null /* dstOffsetMillis */,
+                    goodNitzSignal.getValue().getCurrentTimeInMillis(),
+                    null /* emulatorHostTimeZone */);
+            TimestampedValue<NitzData> badNitzSignal = new TimestampedValue<>(
+                    goodNitzSignal.getReferenceTimeMillis(), bogusNitzData);
+
+            TelephonyTimeZoneSuggestion actualSuggestion = mTimeZoneSuggester.getTimeZoneSuggestion(
+                    SLOT_INDEX, scenario.getNetworkCountryIsoCode(), badNitzSignal);
+            assertEquals(EMPTY_TIME_ZONE_SUGGESTION, actualSuggestion);
+        }
+    }
+
+    @Test
+    public void test_bogusUniqueUsNitzSignal() throws Exception {
+        Scenario scenario = UNIQUE_US_ZONE_SCENARIO1;
+
+        // Country alone is not enough to guess the time zone.
+        {
+            TelephonyTimeZoneSuggestion expectedTimeZoneSuggestion =
+                    new TelephonyTimeZoneSuggestion.Builder(SLOT_INDEX)
+                            .setZoneId(US_COUNTRY_DEFAULT_ZONE_ID)
+                            .setMatchType(MATCH_TYPE_NETWORK_COUNTRY_ONLY)
+                            .setQuality(QUALITY_MULTIPLE_ZONES_WITH_DIFFERENT_OFFSETS)
+                            .build();
+
+            TelephonyTimeZoneSuggestion actualSuggestion = mTimeZoneSuggester.getTimeZoneSuggestion(
+                    SLOT_INDEX, scenario.getNetworkCountryIsoCode(), null /* nitzSignal */);
+            assertEquals(expectedTimeZoneSuggestion, actualSuggestion);
+        }
+
+        // NITZ + bogus NITZ is not enough to get a result.
+        {
+            // Create a corrupted NITZ signal, where the offset information has been lost.
+            TimestampedValue<NitzData> goodNitzSignal =
+                    scenario.createNitzSignal(mFakeDeviceState.elapsedRealtime());
+            NitzData bogusNitzData = NitzData.createForTests(
+                    0 /* UTC! */, null /* dstOffsetMillis */,
+                    goodNitzSignal.getValue().getCurrentTimeInMillis(),
+                    null /* emulatorHostTimeZone */);
+            TimestampedValue<NitzData> badNitzSignal = new TimestampedValue<>(
+                    goodNitzSignal.getReferenceTimeMillis(), bogusNitzData);
+
+            TelephonyTimeZoneSuggestion actualSuggestion = mTimeZoneSuggester.getTimeZoneSuggestion(
+                    SLOT_INDEX, scenario.getNetworkCountryIsoCode(), badNitzSignal);
+            assertEquals(EMPTY_TIME_ZONE_SUGGESTION, actualSuggestion);
+        }
+    }
+
+    @Test
+    public void test_emulatorNitzExtensionUsedForTimeZone() throws Exception {
+        Scenario scenario = UNIQUE_US_ZONE_SCENARIO1;
+
+        TimestampedValue<NitzData> originalNitzSignal =
+                scenario.createNitzSignal(mFakeDeviceState.elapsedRealtime());
+
+        // Create an NITZ signal with an explicit time zone (as can happen on emulators).
+        NitzData originalNitzData = originalNitzSignal.getValue();
+
+        // A time zone that is obviously not in the US, but because the explicit value is present it
+        // should not be questioned.
+        String emulatorTimeZoneId = "Europe/London";
+        NitzData emulatorNitzData = NitzData.createForTests(
+                originalNitzData.getLocalOffsetMillis(),
+                originalNitzData.getDstAdjustmentMillis(),
+                originalNitzData.getCurrentTimeInMillis(),
+                java.util.TimeZone.getTimeZone(emulatorTimeZoneId) /* emulatorHostTimeZone */);
+        TimestampedValue<NitzData> emulatorNitzSignal = new TimestampedValue<>(
+                originalNitzSignal.getReferenceTimeMillis(), emulatorNitzData);
+
+        TelephonyTimeZoneSuggestion expectedTimeZoneSuggestion =
+                new TelephonyTimeZoneSuggestion.Builder(SLOT_INDEX)
+                        .setZoneId(emulatorTimeZoneId)
+                        .setMatchType(MATCH_TYPE_EMULATOR_ZONE_ID)
+                        .setQuality(QUALITY_SINGLE_ZONE)
+                        .build();
+
+        TelephonyTimeZoneSuggestion actualSuggestion = mTimeZoneSuggester.getTimeZoneSuggestion(
+                SLOT_INDEX, scenario.getNetworkCountryIsoCode(), emulatorNitzSignal);
+        assertEquals(expectedTimeZoneSuggestion, actualSuggestion);
+    }
+
+    @Test
+    public void test_countryDefaultBoost() throws Exception {
+        // Demonstrate the defaultTimeZoneBoost behavior: we can get a zone only from the
+        // countryIsoCode.
+        {
+            Scenario scenario = NEW_ZEALAND_DEFAULT_SCENARIO;
+            TelephonyTimeZoneSuggestion expectedSuggestion =
+                    new TelephonyTimeZoneSuggestion.Builder(SLOT_INDEX)
+                            .setZoneId(NEW_ZEALAND_COUNTRY_DEFAULT_ZONE_ID)
+                            .setMatchType(MATCH_TYPE_NETWORK_COUNTRY_ONLY)
+                            .setQuality(QUALITY_SINGLE_ZONE)
+                            .build();
+
+            TelephonyTimeZoneSuggestion actualSuggestion = mTimeZoneSuggester.getTimeZoneSuggestion(
+                    SLOT_INDEX, scenario.getNetworkCountryIsoCode(), null /* nitzSignal */);
+            assertEquals(expectedSuggestion, actualSuggestion);
+        }
+
+        // Confirm what happens when NITZ is correct for the country default.
+        {
+            Scenario scenario = NEW_ZEALAND_DEFAULT_SCENARIO;
+            TimestampedValue<NitzData> nitzSignal =
+                    scenario.createNitzSignal(mFakeDeviceState.elapsedRealtime());
+            TelephonyTimeZoneSuggestion expectedSuggestion =
+                    new TelephonyTimeZoneSuggestion.Builder(SLOT_INDEX)
+                            .setZoneId(scenario.getTimeZoneId())
+                            .setMatchType(MATCH_TYPE_NETWORK_COUNTRY_AND_OFFSET)
+                            .setQuality(QUALITY_SINGLE_ZONE)
+                            .build();
+
+            TelephonyTimeZoneSuggestion actualSuggestion = mTimeZoneSuggester.getTimeZoneSuggestion(
+                    SLOT_INDEX, scenario.getNetworkCountryIsoCode(), nitzSignal);
+            assertEquals(expectedSuggestion, actualSuggestion);
+        }
+
+        // A valid NITZ signal for the non-default zone should still be correctly detected.
+        {
+            Scenario scenario = NEW_ZEALAND_OTHER_SCENARIO;
+            TimestampedValue<NitzData> nitzSignal =
+                    scenario.createNitzSignal(mFakeDeviceState.elapsedRealtime());
+            TelephonyTimeZoneSuggestion expectedSuggestion =
+                    new TelephonyTimeZoneSuggestion.Builder(SLOT_INDEX)
+                            .setZoneId(scenario.getTimeZoneId())
+                            .setMatchType(MATCH_TYPE_NETWORK_COUNTRY_AND_OFFSET)
+                            .setQuality(QUALITY_SINGLE_ZONE)
+                            .build();
+
+            TelephonyTimeZoneSuggestion actualSuggestion = mTimeZoneSuggester.getTimeZoneSuggestion(
+                    SLOT_INDEX, scenario.getNetworkCountryIsoCode(), nitzSignal);
+            assertEquals(expectedSuggestion, actualSuggestion);
+        }
+
+        // Demonstrate what happens with a bogus NITZ for NZ: because the default zone is boosted
+        // then we should return to the country default zone.
+        {
+            Scenario scenario = NEW_ZEALAND_DEFAULT_SCENARIO;
+            // Use a scenario that has a different offset than NZ to generate the NITZ signal.
+            TimestampedValue<NitzData> nitzSignal =
+                    CZECHIA_SCENARIO.createNitzSignal(mFakeDeviceState.elapsedRealtime());
+            TelephonyTimeZoneSuggestion expectedSuggestion =
+                    new TelephonyTimeZoneSuggestion.Builder(SLOT_INDEX)
+                            .setZoneId(NEW_ZEALAND_COUNTRY_DEFAULT_ZONE_ID)
+                            .setMatchType(MATCH_TYPE_NETWORK_COUNTRY_ONLY)
+                            .setQuality(QUALITY_SINGLE_ZONE)
+                            .build();
+
+            TelephonyTimeZoneSuggestion actualSuggestion = mTimeZoneSuggester.getTimeZoneSuggestion(
+                    SLOT_INDEX, scenario.getNetworkCountryIsoCode(), nitzSignal);
+            assertEquals(expectedSuggestion, actualSuggestion);
+        }
+    }
+
+    @Test
+    public void test_noCountryDefaultBoost() throws Exception {
+        // Demonstrate the behavior without default country boost for a country with multiple zones:
+        // we cannot get a zone only from the countryIsoCode.
+        {
+            Scenario scenario = UNIQUE_US_ZONE_SCENARIO1;
+            TelephonyTimeZoneSuggestion expectedSuggestion =
+                    new TelephonyTimeZoneSuggestion.Builder(SLOT_INDEX)
+                            .setZoneId(US_COUNTRY_DEFAULT_ZONE_ID)
+                            .setMatchType(MATCH_TYPE_NETWORK_COUNTRY_ONLY)
+                            .setQuality(QUALITY_MULTIPLE_ZONES_WITH_DIFFERENT_OFFSETS)
+                            .build();
+
+            TelephonyTimeZoneSuggestion actualSuggestion = mTimeZoneSuggester.getTimeZoneSuggestion(
+                    SLOT_INDEX, scenario.getNetworkCountryIsoCode(), null /* nitzSignal */);
+            assertEquals(expectedSuggestion, actualSuggestion);
+        }
+
+        // Confirm what happens when NITZ is correct for the country default.
+        {
+            Scenario scenario = UNIQUE_US_ZONE_SCENARIO1;
+            TimestampedValue<NitzData> nitzSignal =
+                    scenario.createNitzSignal(mFakeDeviceState.elapsedRealtime());
+            TelephonyTimeZoneSuggestion expectedSuggestion =
+                    new TelephonyTimeZoneSuggestion.Builder(SLOT_INDEX)
+                            .setZoneId(scenario.getTimeZoneId())
+                            .setMatchType(MATCH_TYPE_NETWORK_COUNTRY_AND_OFFSET)
+                            .setQuality(QUALITY_SINGLE_ZONE)
+                            .build();
+
+            TelephonyTimeZoneSuggestion actualSuggestion = mTimeZoneSuggester.getTimeZoneSuggestion(
+                    SLOT_INDEX, scenario.getNetworkCountryIsoCode(), nitzSignal);
+            assertEquals(expectedSuggestion, actualSuggestion);
+        }
+
+        // A valid NITZ signal for the non-default zone should still be correctly detected.
+        {
+            Scenario scenario = UNIQUE_US_ZONE_SCENARIO2;
+            TimestampedValue<NitzData> nitzSignal =
+                    scenario.createNitzSignal(mFakeDeviceState.elapsedRealtime());
+            TelephonyTimeZoneSuggestion expectedSuggestion =
+                    new TelephonyTimeZoneSuggestion.Builder(SLOT_INDEX)
+                            .setZoneId(scenario.getTimeZoneId())
+                            .setMatchType(MATCH_TYPE_NETWORK_COUNTRY_AND_OFFSET)
+                            .setQuality(QUALITY_SINGLE_ZONE)
+                            .build();
+
+            TelephonyTimeZoneSuggestion actualSuggestion = mTimeZoneSuggester.getTimeZoneSuggestion(
+                    SLOT_INDEX, scenario.getNetworkCountryIsoCode(), nitzSignal);
+            assertEquals(expectedSuggestion, actualSuggestion);
+        }
+
+        // Demonstrate what happens with a bogus NITZ for US: because the default zone is not
+        // boosted we should not get a suggestion.
+        {
+            // A scenario that has a different offset than US.
+            Scenario scenario = UNIQUE_US_ZONE_SCENARIO1;
+            // Use a scenario that has a different offset than the US to generate the NITZ signal.
+            TimestampedValue<NitzData> nitzSignal =
+                    CZECHIA_SCENARIO.createNitzSignal(mFakeDeviceState.elapsedRealtime());
+            TelephonyTimeZoneSuggestion expectedSuggestion = EMPTY_TIME_ZONE_SUGGESTION;
+            TelephonyTimeZoneSuggestion actualSuggestion = mTimeZoneSuggester.getTimeZoneSuggestion(
+                    SLOT_INDEX, scenario.getNetworkCountryIsoCode(), nitzSignal);
+            assertEquals(expectedSuggestion, actualSuggestion);
+        }
+    }
+}
diff --git a/tests/telephonytests/src/com/android/internal/telephony/uicc/IccPhoneBookInterfaceManagerTest.java b/tests/telephonytests/src/com/android/internal/telephony/uicc/IccPhoneBookInterfaceManagerTest.java
index 9510619..72ff321 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/uicc/IccPhoneBookInterfaceManagerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/uicc/IccPhoneBookInterfaceManagerTest.java
@@ -90,6 +90,7 @@
     @After
     public void tearDown() throws Exception {
         mIccPhoneBookInterfaceManagerHandler.quit();
+        mIccPhoneBookInterfaceManagerHandler.join();
         super.tearDown();
     }
     @Test
diff --git a/tests/telephonytests/src/com/android/internal/telephony/uicc/IccRecordsTest.java b/tests/telephonytests/src/com/android/internal/telephony/uicc/IccRecordsTest.java
index b38b5e8..417c532 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/uicc/IccRecordsTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/uicc/IccRecordsTest.java
@@ -29,10 +29,23 @@
 
 package com.android.internal.telephony.uicc;
 
-import static org.junit.Assert.assertEquals;
-import static org.mockito.Mockito.*;
+import static com.android.internal.telephony.uicc.IccRecords.EVENT_APP_DETECTED;
+import static com.android.internal.telephony.uicc.IccRecords.EVENT_APP_READY;
 
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.isNull;
+import static org.mockito.Mockito.verify;
+
+import android.os.AsyncResult;
 import android.os.HandlerThread;
+import android.os.Message;
+import android.os.SystemClock;
+import android.util.Log;
+import android.util.Pair;
 
 import com.android.internal.telephony.TelephonyTest;
 
@@ -61,6 +74,10 @@
         super.setUp(this.getClass().getSimpleName());
         new IccRecordsTestHandler(TAG).start();
         waitUntilReady();
+        verify(mUiccCardApplication3gpp).registerForReady(
+                mIccRecords, EVENT_APP_READY, null);
+        verify(mUiccCardApplication3gpp).registerForDetected(
+                mIccRecords, EVENT_APP_DETECTED, null);
     }
 
     @After
@@ -92,4 +109,139 @@
         mIccRecords.setImsi("123456ABCDEF");
         assertEquals(mIccRecords.getIMSI(), null);
     }
+
+    @Test
+    public void testPendingTansaction() {
+        Message msg = Message.obtain();
+        Object obj = new Object();
+        int key = mIccRecords.storePendingTransaction(msg, obj);
+        Pair<Message, Object> pair = mIccRecords.retrievePendingTransaction(key);
+        assertEquals(msg, pair.first);
+        assertEquals(obj, pair.second);
+        pair = mIccRecords.retrievePendingTransaction(key);
+        assertNull(pair);
+    }
+
+    @Test
+    public void testGetSmsCapacityOnIcc() {
+        // set the number of records to 500
+        int[] records = new int[3];
+        records[2] = 500;
+        Message fetchCapacityDone = mIccRecords.obtainMessage(
+                IccRecords.EVENT_GET_SMS_RECORD_SIZE_DONE);
+        AsyncResult.forMessage(fetchCapacityDone, records, null);
+        fetchCapacityDone.sendToTarget();
+
+        // verify whether the count is 500
+        waitForLastHandlerAction(mIccRecords);
+        assertEquals(mIccRecords.getSmsCapacityOnIcc(), 500);
+    }
+
+    @Test
+    public void testGetIccSimChallengeResponseNull() {
+        long startTime;
+        long timeSpent;
+
+        // EAP-SIM rand is 16 bytes.
+        String base64Challenge = "ECcTqwuo6OfY8ddFRboD9WM=";
+
+        // Test for null result
+        mSimulatedCommands.setAuthenticationMode(mSimulatedCommands.ICC_AUTHENTICATION_MODE_NULL);
+
+        startTime = SystemClock.elapsedRealtime();
+        assertNull("getIccAuthentication should return null for empty data.",
+                mIccRecords.getIccSimChallengeResponse(UiccCardApplication.AUTH_CONTEXT_EAP_AKA,
+                      base64Challenge));
+        timeSpent = SystemClock.elapsedRealtime() - startTime;
+        Log.d("IccRecordsTest", "Time (ms) for getIccSimChallengeResponse is " + timeSpent);
+        assertTrue("getIccAuthentication should not timeout",
+                timeSpent < mSimulatedCommands.ICC_SIM_CHALLENGE_TIMEOUT_MILLIS);
+    }
+
+    @Test
+    public void testGetIccSimChallengeResponseTimeout() {
+        long startTime;
+        long timeSpent;
+
+        // EAP-SIM rand is 16 bytes.
+        String base64Challenge = "ECcTqwuo6OfY8ddFRboD9WM=";
+
+        mSimulatedCommands.setAuthenticationMode(
+                mSimulatedCommands.ICC_AUTHENTICATION_MODE_TIMEOUT);
+        startTime = SystemClock.elapsedRealtime();
+        assertNull("getIccAuthentication should return null for empty data.",
+                mIccRecords.getIccSimChallengeResponse(UiccCardApplication.AUTH_CONTEXT_EAP_AKA,
+                      base64Challenge));
+        timeSpent = SystemClock.elapsedRealtime() - startTime;
+        Log.d("IccRecordsTest", "Time (ms) for getIccSimChallengeResponse is " + timeSpent);
+        assertTrue("getIccAuthentication should timeout",
+                timeSpent >= mSimulatedCommands.ICC_SIM_CHALLENGE_TIMEOUT_MILLIS);
+    }
+
+    @Test
+    public void testAppStateChange() {
+        assertFalse(mIccRecords.isLoaded());
+
+        mIccRecords.obtainMessage(EVENT_APP_READY).sendToTarget();
+        waitForLastHandlerAction(mIccRecords);
+        assertTrue(mIccRecords.isLoaded());
+
+        mIccRecords.obtainMessage(EVENT_APP_DETECTED).sendToTarget();
+        waitForLastHandlerAction(mIccRecords);
+        assertFalse(mIccRecords.isLoaded());
+
+        mIccRecords.obtainMessage(EVENT_APP_READY).sendToTarget();
+        waitForLastHandlerAction(mIccRecords);
+        assertTrue(mIccRecords.isLoaded());
+    }
+
+    @Test
+    public void testGetIccSimChallengeResponseDefault() {
+        long startTime;
+        long timeSpent;
+
+        // EAP-SIM rand is 16 bytes.
+        String base64Challenge = "ECcTqwuo6OfY8ddFRboD9WM=";
+        String base64Challenge2 = "EMNxjsFrPCpm+KcgCmQGnwQ=";
+
+        // Test for default setup
+        mSimulatedCommands.setAuthenticationMode(
+                mSimulatedCommands.ICC_AUTHENTICATION_MODE_DEFAULT);
+
+        // Test for null input
+        startTime = SystemClock.elapsedRealtime();
+        assertNull("getIccAuthentication should return null for empty data.",
+                mIccRecords.getIccSimChallengeResponse(
+                        UiccCardApplication.AUTH_CONTEXT_EAP_AKA, ""));
+        timeSpent = SystemClock.elapsedRealtime() - startTime;
+        Log.d("IccRecordsTest", "Time (ms) for getIccSimChallengeResponse is " + timeSpent);
+        assertTrue("getIccAuthentication should not timeout",
+                timeSpent < mSimulatedCommands.ICC_SIM_CHALLENGE_TIMEOUT_MILLIS);
+
+        // EAP-SIM
+        startTime = SystemClock.elapsedRealtime();
+        String response = mIccRecords.getIccSimChallengeResponse(
+                UiccCardApplication.AUTH_CONTEXT_EAP_SIM, base64Challenge);
+        timeSpent = SystemClock.elapsedRealtime() - startTime;
+        Log.d("IccRecordsTest", "Time (ms) for getIccSimChallengeResponse is " + timeSpent);
+        Log.d("IccRecordsTest", "Result of getIccSimChallengeResponse is " + response);
+        assertTrue("Response to EAP-SIM Challenge must not be Null.", response != null);
+
+        startTime = SystemClock.elapsedRealtime();
+        String response1 = mIccRecords.getIccSimChallengeResponse(
+                UiccCardApplication.AUTH_CONTEXT_EAP_SIM, base64Challenge);
+        timeSpent = SystemClock.elapsedRealtime() - startTime;
+        Log.d("IccRecordsTest", "Time (ms) for getIccSimChallengeResponse is " + timeSpent);
+        Log.d("IccRecordsTest", "Result of getIccSimChallengeResponse is " + response1);
+        assertTrue("Response to EAP-SIM Challenge must be consistent.",
+                response.equals(response1));
+
+        startTime = SystemClock.elapsedRealtime();
+        String response2 = mIccRecords.getIccSimChallengeResponse(
+                UiccCardApplication.AUTH_CONTEXT_EAP_SIM, base64Challenge2);
+        timeSpent = SystemClock.elapsedRealtime() - startTime;
+        Log.d("IccRecordsTest", "Time (ms) for getIccSimChallengeResponse is " + timeSpent);
+        assertTrue("Two responses must be different.", !response.equals(response2));
+    }
+
 }
diff --git a/tests/telephonytests/src/com/android/internal/telephony/uicc/IccUtilsTest.java b/tests/telephonytests/src/com/android/internal/telephony/uicc/IccUtilsTest.java
new file mode 100644
index 0000000..d2cebb3
--- /dev/null
+++ b/tests/telephonytests/src/com/android/internal/telephony/uicc/IccUtilsTest.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2019 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.uicc;
+
+import android.test.AndroidTestCase;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.text.TextUtils;
+
+import java.util.Arrays;
+import java.util.List;
+
+public class IccUtilsTest extends AndroidTestCase {
+    private static final int NUM_FPLMN = 3;
+    private static final List<String> FPLMNS_SAMPLE = Arrays.asList("123456", "12345", "54321");
+    private static final int DATA_LENGTH = 12;
+
+    @SmallTest
+    public void testEncodeFplmns() {
+        byte[] encodedFplmns = IccUtils.encodeFplmns(FPLMNS_SAMPLE, DATA_LENGTH);
+        int numValidPlmns = 0;
+        for (int i = 0; i < NUM_FPLMN; i++) {
+            String parsed = IccUtils.bcdPlmnToString(encodedFplmns, i * IccUtils.FPLMN_BYTE_SIZE);
+            assertEquals(FPLMNS_SAMPLE.get(i), parsed);
+            // we count the valid (non empty) records and only increment if valid
+            if (!TextUtils.isEmpty(parsed)) numValidPlmns++;
+        }
+        assertEquals(NUM_FPLMN, numValidPlmns);
+    }
+}
diff --git a/tests/telephonytests/src/com/android/internal/telephony/uicc/SIMRecordsTest.java b/tests/telephonytests/src/com/android/internal/telephony/uicc/SIMRecordsTest.java
new file mode 100644
index 0000000..80f8867
--- /dev/null
+++ b/tests/telephonytests/src/com/android/internal/telephony/uicc/SIMRecordsTest.java
@@ -0,0 +1,289 @@
+/*
+ * Copyright (C) 2019 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.uicc;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.verify;
+
+import android.content.Context;
+import android.os.AsyncResult;
+import android.os.Handler;
+import android.os.Message;
+import android.os.test.TestLooper;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.internal.telephony.CommandException;
+import com.android.internal.telephony.CommandsInterface;
+import com.android.internal.telephony.TelephonyTest;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+@RunWith(AndroidJUnit4.class)
+public class SIMRecordsTest extends TelephonyTest {
+    private static final List<String> SHORT_FPLMNS_LIST = Arrays.asList("12345", "123456", "09876");
+    private static final List<String> LONG_FPLMNS_LIST =
+            Arrays.asList("12345", "123456", "09876", "123456", "098237");
+    private static final List<String> EMPTY_FPLMN_LIST = new ArrayList<>();
+    private static final int EF_SIZE = 12;
+    private static final int MAX_NUM_FPLMN = 4;
+
+    @Mock private IccFileHandler mFhMock;
+
+    private SIMRecordsUT mSIMRecordsUT;
+    private TestLooper mTestLooper;
+    private Handler mTestHandler;
+    private SIMRecordsReceiver mSIMRecordsReceiver;
+    private SIMRecords mSIMRecord;
+
+    private class SIMRecordsUT extends SIMRecords {
+        SIMRecordsUT(UiccCardApplication app, Context c,
+                CommandsInterface ci, IccFileHandler mFhMock) {
+            super(app, c, ci);
+            mFh = mFhMock;
+        }
+    }
+
+    private class SIMRecordsReceiver {
+        private SIMRecordsUT mSIMRecordsUT;
+
+        public void set(SIMRecordsUT m) {
+            mSIMRecordsUT = m;
+        }
+
+        public SIMRecordsUT get() {
+            return mSIMRecordsUT;
+        }
+    }
+
+    @Before
+    public void setUp() throws Exception {
+        super.setUp(getClass().getSimpleName());
+        mTestLooper = new TestLooper();
+        mTestHandler = new Handler(mTestLooper.getLooper());
+        mSIMRecordsReceiver = new SIMRecordsReceiver();
+        mTestHandler.post(
+                () -> {
+                    SIMRecordsUT mSIMRecords =
+                            new SIMRecordsUT(
+                                    mUiccCardApplication3gpp,
+                                    mContext,
+                                    mSimulatedCommands,
+                                    mFhMock);
+                    mSIMRecordsReceiver.set(mSIMRecords);
+                });
+        mTestLooper.dispatchAll();
+        mSIMRecordsUT = mSIMRecordsReceiver.get();
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        super.tearDown();
+    }
+
+    @Test
+    public void testSetForbiddenPlmnsPad() {
+        setUpSetForbiddenPlmnsTests();
+        Message message = Message.obtain(mTestHandler);
+        mSIMRecordsUT.setForbiddenPlmns(message, SHORT_FPLMNS_LIST);
+        mTestLooper.dispatchAll();
+
+        byte[] encodedFplmn = IccUtils.encodeFplmns(SHORT_FPLMNS_LIST, EF_SIZE);
+        AsyncResult ar = (AsyncResult) message.obj;
+        assertEquals(SHORT_FPLMNS_LIST.size(), (int) ar.result);
+        verify(mFhMock).getEFTransparentRecordSize(eq(mSIMRecordsUT.EF_FPLMN), any(Message.class));
+        verify(mFhMock).updateEFTransparent(
+                eq(mSIMRecordsUT.EF_FPLMN), eq(encodedFplmn), any(Message.class));
+    }
+
+    @Test
+    public void testSetForbiddenPlmnsTruncate() {
+        setUpSetForbiddenPlmnsTests();
+        Message message = Message.obtain(mTestHandler);
+        mSIMRecordsUT.setForbiddenPlmns(message, LONG_FPLMNS_LIST);
+        mTestLooper.dispatchAll();
+
+        byte[] encodedFplmn = IccUtils.encodeFplmns(LONG_FPLMNS_LIST, EF_SIZE);
+        AsyncResult ar = (AsyncResult) message.obj;
+        assertEquals(MAX_NUM_FPLMN, (int) ar.result);
+        verify(mFhMock).getEFTransparentRecordSize(eq(mSIMRecordsUT.EF_FPLMN), any(Message.class));
+        verify(mFhMock).updateEFTransparent(
+                eq(mSIMRecordsUT.EF_FPLMN), eq(encodedFplmn), any(Message.class));
+    }
+
+    @Test
+    public void testSetForbiddenPlmnsClear() {
+        setUpSetForbiddenPlmnsTests();
+        Message message = Message.obtain(mTestHandler);
+        mSIMRecordsUT.setForbiddenPlmns(message, EMPTY_FPLMN_LIST);
+        mTestLooper.dispatchAll();
+
+        byte[] encodedFplmn = IccUtils.encodeFplmns(EMPTY_FPLMN_LIST, EF_SIZE);
+        AsyncResult ar = (AsyncResult) message.obj;
+        assertEquals(EMPTY_FPLMN_LIST.size(), (int) ar.result);
+        verify(mFhMock).getEFTransparentRecordSize(eq(mSIMRecordsUT.EF_FPLMN), any(Message.class));
+        verify(mFhMock).updateEFTransparent(
+                eq(mSIMRecordsUT.EF_FPLMN), eq(encodedFplmn), any(Message.class));
+    }
+
+    private void setUpSetForbiddenPlmnsTests() {
+        doAnswer(
+            invocation -> {
+                Message response = invocation.getArgument(1);
+                AsyncResult.forMessage(response, EF_SIZE, null);
+                response.sendToTarget();
+                return null;
+            })
+            .when(mFhMock)
+            .getEFTransparentRecordSize(anyInt(), any(Message.class));
+        doAnswer(
+            invocation -> {
+                Message response = invocation.getArgument(2);
+                AsyncResult.forMessage(response, true, null);
+                response.sendToTarget();
+                return null;
+            })
+            .when(mFhMock)
+            .updateEFTransparent(anyInt(), any(byte[].class), any(Message.class));
+    }
+
+    @Test
+    public void testGetForbiddenPlmns() {
+        doAnswer(
+            invocation -> {
+                Message response = invocation.getArgument(1);
+                byte[] encodedFplmn = IccUtils.encodeFplmns(SHORT_FPLMNS_LIST, EF_SIZE);
+                AsyncResult.forMessage(response, encodedFplmn, null);
+                response.sendToTarget();
+                return null;
+            })
+            .when(mFhMock)
+            .loadEFTransparent(eq(SIMRecords.EF_FPLMN), any(Message.class));
+
+        Message message = Message.obtain(mTestHandler);
+        mSIMRecordsUT.getForbiddenPlmns(message);
+        mTestLooper.dispatchAll();
+
+        AsyncResult ar = (AsyncResult) message.obj;
+        assertNull(ar.exception);
+        assertEquals(SHORT_FPLMNS_LIST.toArray(new String[SHORT_FPLMNS_LIST.size()]),
+                (String[]) ar.result);
+    }
+
+    @Test
+    public void testGetForbiddenPlmnsException() {
+        doAnswer(
+            invocation -> {
+                Message response = invocation.getArgument(1);
+                AsyncResult.forMessage(response, null, new CommandException(
+                        CommandException.Error.OPERATION_NOT_ALLOWED));
+                response.sendToTarget();
+                return null;
+            })
+            .when(mFhMock)
+            .loadEFTransparent(eq(SIMRecords.EF_FPLMN), any(Message.class));
+
+        Message message = Message.obtain(mTestHandler);
+        mSIMRecordsUT.getForbiddenPlmns(message);
+        mTestLooper.dispatchAll();
+
+        AsyncResult ar = (AsyncResult) message.obj;
+        assertTrue(ar.exception instanceof CommandException);
+        assertTrue(((CommandException) ar.exception).getCommandError() ==
+                CommandException.Error.OPERATION_NOT_ALLOWED);
+        assertNull(ar.result);
+    }
+
+    @Test
+    public void testGetForbiddenPlmnsNull() {
+        doAnswer(
+            invocation -> {
+                Message response = invocation.getArgument(1);
+                AsyncResult.forMessage(response);
+                response.sendToTarget();
+                return null;
+            })
+            .when(mFhMock)
+            .loadEFTransparent(eq(SIMRecords.EF_FPLMN), any(Message.class));
+
+        Message message = Message.obtain(mTestHandler);
+        mSIMRecordsUT.getForbiddenPlmns(message);
+        mTestLooper.dispatchAll();
+
+        AsyncResult ar = (AsyncResult) message.obj;
+        assertNull(ar.exception);
+        assertNull(ar.result);
+    }
+
+    @Test
+    public void testGetForbiddenPlmnsEmptyList() {
+        doAnswer(
+            invocation -> {
+                Message response = invocation.getArgument(1);
+                byte[] encodedFplmn = IccUtils.encodeFplmns(EMPTY_FPLMN_LIST, EF_SIZE);
+                AsyncResult.forMessage(response, encodedFplmn, null);
+                response.sendToTarget();
+                return null;
+            })
+            .when(mFhMock)
+            .loadEFTransparent(eq(SIMRecords.EF_FPLMN), any(Message.class));
+
+        Message message = Message.obtain(mTestHandler);
+        mSIMRecordsUT.getForbiddenPlmns(message);
+        mTestLooper.dispatchAll();
+
+        AsyncResult ar = (AsyncResult) message.obj;
+        assertNull(ar.exception);
+        assertEquals(EMPTY_FPLMN_LIST.toArray(new String[EMPTY_FPLMN_LIST.size()]),
+                (String[]) ar.result);
+    }
+
+    @Test
+    public void testGetForbiddenPlmnsInvalidLength() {
+        doAnswer(
+            invocation -> {
+                Message response = invocation.getArgument(1);
+                AsyncResult.forMessage(response, new byte[] { (byte) 0xFF, (byte) 0xFF }, null);
+                response.sendToTarget();
+                return null;
+            })
+            .when(mFhMock)
+            .loadEFTransparent(eq(SIMRecords.EF_FPLMN), any(Message.class));
+
+        Message message = Message.obtain(mTestHandler);
+        mSIMRecordsUT.getForbiddenPlmns(message);
+        mTestLooper.dispatchAll();
+
+        AsyncResult ar = (AsyncResult) message.obj;
+        assertNull(ar.exception);
+        assertNull(ar.result);
+    }
+}
diff --git a/tests/telephonytests/src/com/android/internal/telephony/uicc/UiccCardApplicationTest.java b/tests/telephonytests/src/com/android/internal/telephony/uicc/UiccCardApplicationTest.java
index 62b1fc9..5b57765 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/uicc/UiccCardApplicationTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/uicc/UiccCardApplicationTest.java
@@ -26,9 +26,10 @@
 
 import android.os.AsyncResult;
 import android.os.Handler;
-import android.os.HandlerThread;
 import android.os.Message;
 import android.test.suitebuilder.annotation.SmallTest;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
 
 import com.android.internal.telephony.CommandException;
 import com.android.internal.telephony.TelephonyTest;
@@ -37,8 +38,11 @@
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
+import org.junit.runner.RunWith;
 import org.mockito.Mock;
 
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper
 public class UiccCardApplicationTest extends TelephonyTest {
     private UiccCardApplication mUiccCardApplication;
     @Mock
@@ -46,53 +50,15 @@
     @Mock
     private UiccProfile mUiccProfile;
     private Handler mHandler;
-    private UiccCardAppTestHandlerThread mTestHandlerThread;
     private int mAttemptsRemaining = -1;
     private CommandException mException = null;
-    private static final int UICCCARDAPP_UPDATE_EVENT = 1;
-    private static final int UICCCARDAPP_ENABLE_FDN_EVENT = 2;
-    private static final int UICCCARDAPP_ENABLE_LOCK_EVENT = 3;
-    private static final int UICCCARDAPP_CHANGE_PSW_EVENT = 4;
-    private static final int UICCCARDAPP_SUPPLY_PIN_EVENT = 5;
-    private class UiccCardAppTestHandlerThread extends HandlerThread {
-
-        private UiccCardAppTestHandlerThread(String name) {
-            super(name);
-        }
-        @Override
-        public void onLooperPrepared() {
-            mUiccCardApplication = new UiccCardApplication(mUiccProfile, mUiccCardAppStatus,
-                    mContext, mSimulatedCommands);
-            mHandler = new Handler(mTestHandlerThread.getLooper()) {
-                @Override
-                public void handleMessage(Message msg) {
-                    switch (msg.what) {
-                        case UICCCARDAPP_UPDATE_EVENT:
-                            logd("Update UiccCardApplication Status");
-                            mUiccCardApplication.update(mUiccCardAppStatus,
-                                    mContext,
-                                    mSimulatedCommands);
-                            setReady(true);
-                            break;
-                        case UICCCARDAPP_SUPPLY_PIN_EVENT:
-                        case UICCCARDAPP_CHANGE_PSW_EVENT:
-                        case UICCCARDAPP_ENABLE_LOCK_EVENT:
-                        case UICCCARDAPP_ENABLE_FDN_EVENT:
-                            mAttemptsRemaining = msg.arg1;
-                            mException = (CommandException) ((AsyncResult) msg.obj).exception;
-                            if (mAttemptsRemaining != -1) {
-                                logd("remaining Attempt:" + mAttemptsRemaining);
-                            }
-                            setReady(true);
-                            break;
-                        default:
-                            logd("Unknown Event " + msg.what);
-                    }
-                }
-            };
-            setReady(true);
-        }
-    }
+    private IccCardApplicationStatus.AppState mAppState;
+    private static final int UICCCARDAPP_ENABLE_FDN_EVENT = 1;
+    private static final int UICCCARDAPP_ENABLE_LOCK_EVENT = 2;
+    private static final int UICCCARDAPP_CHANGE_PSW_EVENT = 3;
+    private static final int UICCCARDAPP_SUPPLY_PIN_EVENT = 4;
+    private static final int EVENT_APP_STATE_DETECTED     = 5;
+    private static final int EVENT_APP_STATE_READY        = 6;
 
     @Before
     public void setUp() throws Exception {
@@ -104,15 +70,38 @@
         mUiccCardAppStatus.pin1 = IccCardStatus.PinState.PINSTATE_ENABLED_NOT_VERIFIED;
         mUiccCardAppStatus.pin2 = IccCardStatus.PinState.PINSTATE_ENABLED_VERIFIED;
 
-        mTestHandlerThread = new UiccCardAppTestHandlerThread(TAG);
-        mTestHandlerThread.start();
-
-        waitUntilReady();
+        mUiccCardApplication = new UiccCardApplication(mUiccProfile, mUiccCardAppStatus,
+            mContext, mSimulatedCommands);
+        mHandler = new Handler() {
+            @Override
+            public void handleMessage(Message msg) {
+                switch (msg.what) {
+                    case UICCCARDAPP_SUPPLY_PIN_EVENT:
+                    case UICCCARDAPP_CHANGE_PSW_EVENT:
+                    case UICCCARDAPP_ENABLE_LOCK_EVENT:
+                    case UICCCARDAPP_ENABLE_FDN_EVENT:
+                        mAttemptsRemaining = msg.arg1;
+                        mException = (CommandException) ((AsyncResult) msg.obj).exception;
+                        if (mAttemptsRemaining != -1) {
+                            logd("remaining Attempt:" + mAttemptsRemaining);
+                        }
+                        break;
+                    case EVENT_APP_STATE_DETECTED:
+                        mAppState = IccCardApplicationStatus.AppState.APPSTATE_DETECTED;
+                        break;
+                    case EVENT_APP_STATE_READY:
+                        mAppState = IccCardApplicationStatus.AppState.APPSTATE_READY;
+                        break;
+                    default:
+                        logd("Unknown Event " + msg.what);
+                }
+            }
+        };
+        processAllMessages();
     }
 
     @After
     public void tearDown() throws Exception {
-        mTestHandlerThread.quit();
         super.tearDown();
     }
 
@@ -121,11 +110,9 @@
     public void testGetSetAppType() {
         assertEquals(IccCardApplicationStatus.AppType.APPTYPE_SIM, mUiccCardApplication.getType());
         mUiccCardAppStatus.app_type = IccCardApplicationStatus.AppType.APPTYPE_USIM;
-        Message mCardAppUpdate = mHandler.obtainMessage(UICCCARDAPP_UPDATE_EVENT);
-        setReady(false);
-        mCardAppUpdate.sendToTarget();
-
-        waitUntilReady();
+        logd("Update UiccCardApplication Status");
+        mUiccCardApplication.update(mUiccCardAppStatus, mContext, mSimulatedCommands);
+        processAllMessages();
         assertEquals(IccCardApplicationStatus.AppType.APPTYPE_USIM, mUiccCardApplication.getType());
     }
 
@@ -135,11 +122,9 @@
         assertEquals(IccCardApplicationStatus.AppState.APPSTATE_PIN,
                 mUiccCardApplication.getState());
         mUiccCardAppStatus.app_state = IccCardApplicationStatus.AppState.APPSTATE_PUK;
-        Message mCardAppUpdate = mHandler.obtainMessage(UICCCARDAPP_UPDATE_EVENT);
-        setReady(false);
-        mCardAppUpdate.sendToTarget();
-
-        waitUntilReady();
+        logd("Update UiccCardApplication Status");
+        mUiccCardApplication.update(mUiccCardAppStatus, mContext, mSimulatedCommands);
+        processAllMessages();
         assertEquals(IccCardApplicationStatus.AppState.APPSTATE_PUK,
                 mUiccCardApplication.getState());
     }
@@ -151,16 +136,14 @@
         //enable FDN
         Message mFDNenabled = mHandler.obtainMessage(UICCCARDAPP_ENABLE_FDN_EVENT);
         //wrong PIN2Code
-        setReady(false);
         mUiccCardApplication.setIccFdnEnabled(true, "XXXX", mFDNenabled);
-        waitUntilReady();
+        processAllMessages();
         assertFalse(mUiccCardApplication.getIccFdnEnabled());
 
-        setReady(false);
         mFDNenabled = mHandler.obtainMessage(UICCCARDAPP_ENABLE_FDN_EVENT);
         mUiccCardApplication.setIccFdnEnabled(true, mSimulatedCommands.DEFAULT_SIM_PIN2_CODE,
                 mFDNenabled);
-        waitUntilReady();
+        processAllMessages();
         assertTrue(mUiccCardApplication.getIccFdnEnabled());
     }
 
@@ -169,16 +152,14 @@
     public void testGetSetIccLockedEnabled() {
         assertFalse(mUiccCardApplication.getIccLockEnabled());
         Message mLockEnabled = mHandler.obtainMessage(UICCCARDAPP_ENABLE_LOCK_EVENT);
-        setReady(false);
         mUiccCardApplication.setIccLockEnabled(true, "XXXX", mLockEnabled);
-        waitUntilReady();
+        processAllMessages();
         assertFalse(mUiccCardApplication.getIccLockEnabled());
 
         mLockEnabled = mHandler.obtainMessage(UICCCARDAPP_ENABLE_LOCK_EVENT);
-        setReady(false);
         mUiccCardApplication.setIccLockEnabled(true, mSimulatedCommands.DEFAULT_SIM_PIN_CODE,
                 mLockEnabled);
-        waitUntilReady();
+        processAllMessages();
         assertTrue(mUiccCardApplication.getIccLockEnabled());
     }
 
@@ -186,10 +167,9 @@
     @SmallTest
     public void testChangeIccLockPassword() {
         Message mChangePsw = mHandler.obtainMessage(UICCCARDAPP_CHANGE_PSW_EVENT);
-        setReady(false);
         mUiccCardApplication.changeIccLockPassword(mSimulatedCommands.DEFAULT_SIM_PIN_CODE,
                 "1111", mChangePsw);
-        waitUntilReady();
+        processAllMessages();
         verify(mSimulatedCommandsVerifier).changeIccPinForApp(
                 eq(mSimulatedCommands.DEFAULT_SIM_PIN_CODE), eq("1111"), eq(TAG), (Message) any());
         assertNull(mException);
@@ -200,18 +180,16 @@
     public void testSupplyPin() {
         //Supply with default PIN1
         Message mSupplyPin = mHandler.obtainMessage(UICCCARDAPP_SUPPLY_PIN_EVENT);
-        setReady(false);
         mUiccCardApplication.supplyPin(mSimulatedCommands.DEFAULT_SIM_PIN_CODE, mSupplyPin);
-        waitUntilReady();
+        processAllMessages();
         assertEquals(-1, mAttemptsRemaining);
         verify(mSimulatedCommandsVerifier).supplyIccPinForApp(
                 eq(SimulatedCommands.DEFAULT_SIM_PIN_CODE), eq(TAG), (Message) any());
 
         //Supply with wrong PIN1
         mSupplyPin = mHandler.obtainMessage(UICCCARDAPP_SUPPLY_PIN_EVENT);
-        setReady(false);
         mUiccCardApplication.supplyPin("1111", mSupplyPin);
-        waitUntilReady();
+        processAllMessages();
         assertEquals(mSimulatedCommands.DEFAULT_PIN1_ATTEMPT - 1, mAttemptsRemaining);
         assertNotNull(mException);
         assertEquals(CommandException.Error.PASSWORD_INCORRECT, mException.getCommandError());
@@ -219,9 +197,34 @@
         testChangeIccLockPassword();
         //Supply with the updated PIN1
         mSupplyPin = mHandler.obtainMessage(UICCCARDAPP_SUPPLY_PIN_EVENT);
-        setReady(false);
         mUiccCardApplication.supplyPin("1111", mSupplyPin);
-        waitUntilReady();
+        processAllMessages();
         assertEquals(-1, mAttemptsRemaining);
     }
+
+    @Test
+    @SmallTest
+    public void testAppStateChangeNotification() {
+        mUiccCardApplication.registerForDetected(mHandler, EVENT_APP_STATE_DETECTED, null);
+        mUiccCardApplication.registerForReady(mHandler, EVENT_APP_STATE_READY, null);
+        processAllMessages();
+        assertEquals(null, mAppState);
+
+        // Change to DETECTED state.
+        mUiccCardAppStatus.app_state = IccCardApplicationStatus.AppState.APPSTATE_DETECTED;
+        mUiccCardApplication.update(mUiccCardAppStatus, mContext, mSimulatedCommands);
+        processAllMessages();
+        assertEquals(IccCardApplicationStatus.AppState.APPSTATE_DETECTED, mAppState);
+        assertEquals(IccCardApplicationStatus.AppState.APPSTATE_DETECTED,
+                mUiccCardApplication.getState());
+
+        // Change to READY state.
+        mUiccCardAppStatus.app_state = IccCardApplicationStatus.AppState.APPSTATE_READY;
+        mUiccCardAppStatus.pin1 = IccCardStatus.PinState.PINSTATE_ENABLED_VERIFIED;
+        mUiccCardApplication.update(mUiccCardAppStatus, mContext, mSimulatedCommands);
+        processAllMessages();
+        assertEquals(IccCardApplicationStatus.AppState.APPSTATE_READY, mAppState);
+        assertEquals(IccCardApplicationStatus.AppState.APPSTATE_READY,
+                mUiccCardApplication.getState());
+    }
 }
diff --git a/tests/telephonytests/src/com/android/internal/telephony/uicc/UiccCardTest.java b/tests/telephonytests/src/com/android/internal/telephony/uicc/UiccCardTest.java
index 27b8531..23a7ec9 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/uicc/UiccCardTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/uicc/UiccCardTest.java
@@ -15,85 +15,31 @@
  */
 package com.android.internal.telephony.uicc;
 
-import static com.android.internal.telephony.TelephonyTestUtils.waitForMs;
-
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNull;
 
-import android.os.Handler;
-import android.os.HandlerThread;
-import android.os.Message;
 import android.test.suitebuilder.annotation.SmallTest;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
 
 import com.android.internal.telephony.TelephonyTest;
 
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
+import org.junit.runner.RunWith;
 import org.mockito.Mock;
 
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper
 public class UiccCardTest extends TelephonyTest {
-    private UiccCard mUicccard;
-
-    public UiccCardTest() {
-        super();
-    }
+    private UiccCard mUiccCard;
 
     private IccIoResult mIccIoResult;
 
-    private UiccCardHandlerThread mTestHandlerThread;
-    private Handler mHandler;
-    private static final int UICCCARD_UPDATE_CARD_STATE_EVENT = 1;
-    private static final int UICCCARD_UPDATE_CARD_APPLICATION_EVENT = 2;
-    private static final int UICCCARD_CARRIER_PRIVILEDGE_LOADED_EVENT = 3;
-
     @Mock
     private IccCardStatus mIccCardStatus;
-    @Mock
-    private Handler mMockedHandler;
-
-
-    private class UiccCardHandlerThread extends HandlerThread {
-
-        private UiccCardHandlerThread(String name) {
-            super(name);
-        }
-
-        @Override
-        public void onLooperPrepared() {
-            mUicccard = new UiccCard(mContextFixture.getTestDouble(),
-                                     mSimulatedCommands, mIccCardStatus, 0 /* phoneId */,
-                                     new Object());
-            /* create a custom handler for the Handler Thread */
-            mHandler = new Handler(mTestHandlerThread.getLooper()) {
-                @Override
-                public void handleMessage(Message msg) {
-                    switch (msg.what) {
-                        case UICCCARD_UPDATE_CARD_STATE_EVENT:
-                            /* Upon handling this event, new CarrierPrivilegeRule
-                            will be created with the looper of HandlerThread */
-                            logd("Update UICC Card State");
-                            mUicccard.update(mContextFixture.getTestDouble(),
-                                    mSimulatedCommands, mIccCardStatus);
-                            setReady(true);
-                            break;
-                        case UICCCARD_UPDATE_CARD_APPLICATION_EVENT:
-                            logd("Update UICC Card Applications");
-                            mUicccard.update(mContextFixture.getTestDouble(),
-                                    mSimulatedCommands, mIccCardStatus);
-                            setReady(true);
-                            break;
-                        default:
-                            logd("Unknown Event " + msg.what);
-                    }
-                }
-            };
-
-            setReady(true);
-            logd("create UiccCard");
-        }
-    }
 
     private IccCardApplicationStatus composeUiccApplicationStatus(
             IccCardApplicationStatus.AppType appType,
@@ -109,7 +55,6 @@
 
     @Before
     public void setUp() throws Exception {
-
         super.setUp(getClass().getSimpleName());
         /* initially there are no application available */
         mIccCardStatus.mApplications = new IccCardApplicationStatus[]{};
@@ -120,16 +65,14 @@
 
         mIccIoResult = new IccIoResult(0x90, 0x00, IccUtils.hexStringToBytes("FF40"));
         mSimulatedCommands.setIccIoResultForApduLogicalChannel(mIccIoResult);
-        /* starting the Handler Thread */
-        mTestHandlerThread = new UiccCardHandlerThread(getClass().getSimpleName());
-        mTestHandlerThread.start();
-
-        waitUntilReady();
+        mUiccCard = new UiccCard(mContext, mSimulatedCommands, mIccCardStatus, 0 /* phoneId */,
+            new Object());
+        processAllMessages();
+        logd("create UiccCard");
     }
 
     @After
     public void tearDown() throws Exception {
-        mTestHandlerThread.quit();
         super.tearDown();
     }
 
@@ -137,35 +80,32 @@
     @SmallTest
     public void tesUiccCartdInfoSanity() {
         /* before update sanity test */
-        assertEquals(0, mUicccard.getNumApplications());
-        assertEquals(IccCardStatus.CardState.CARDSTATE_PRESENT, mUicccard.getCardState());
-        assertNull(mUicccard.getUniversalPinState());
-        assertNull(mUicccard.getOperatorBrandOverride());
+        assertEquals(0, mUiccCard.getNumApplications());
+        assertEquals(IccCardStatus.CardState.CARDSTATE_PRESENT, mUiccCard.getCardState());
+        assertNull(mUiccCard.getUniversalPinState());
+        assertNull(mUiccCard.getOperatorBrandOverride());
         /* UiccProfile mock should return false */
-        assertFalse(mUicccard.areCarrierPriviligeRulesLoaded());
+        assertFalse(mUiccCard.areCarrierPriviligeRulesLoaded());
         for (IccCardApplicationStatus.AppType mAppType :
                 IccCardApplicationStatus.AppType.values()) {
-            assertFalse(mUicccard.isApplicationOnIcc(mAppType));
+            assertFalse(mUiccCard.isApplicationOnIcc(mAppType));
         }
     }
 
-    @Test @SmallTest
+    @Test
+    @SmallTest
     public void testUpdateUiccCardState() {
         int mChannelId = 1;
         /* set card as present */
         mIccCardStatus.mCardState = IccCardStatus.CardState.CARDSTATE_PRESENT;
         /* Mock open Channel ID 1 */
         mSimulatedCommands.setOpenChannelId(mChannelId);
-        Message mCardUpdate = mHandler.obtainMessage(UICCCARD_UPDATE_CARD_STATE_EVENT);
-        setReady(false);
-        mCardUpdate.sendToTarget();
+        logd("Update UICC Card State");
+        mUiccCard.update(mContext, mSimulatedCommands, mIccCardStatus);
         /* try to create a new CarrierPrivilege, loading state -> loaded state */
-        /* wait till the async result and message delay */
-        waitUntilReady();
+        processAllMessages();
 
-        assertEquals(IccCardStatus.CardState.CARDSTATE_PRESENT, mUicccard.getCardState());
-
-        waitForMs(50);
+        assertEquals(IccCardStatus.CardState.CARDSTATE_PRESENT, mUiccCard.getCardState());
 
         /* todo: This part should move to UiccProfileTest
         assertTrue(mUicccard.areCarrierPriviligeRulesLoaded());
diff --git a/tests/telephonytests/src/com/android/internal/telephony/uicc/UiccCarrierPrivilegeRulesTest.java b/tests/telephonytests/src/com/android/internal/telephony/uicc/UiccCarrierPrivilegeRulesTest.java
index 4e56d3e..b383bfa 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/uicc/UiccCarrierPrivilegeRulesTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/uicc/UiccCarrierPrivilegeRulesTest.java
@@ -25,10 +25,10 @@
 
 import android.content.pm.Signature;
 import android.os.AsyncResult;
-import android.os.Handler;
-import android.os.HandlerThread;
 import android.os.Message;
 import android.test.suitebuilder.annotation.SmallTest;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
 
 import com.android.internal.telephony.CommandException;
 import com.android.internal.telephony.TelephonyTest;
@@ -36,67 +36,26 @@
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
+import org.junit.runner.RunWith;
 import org.mockito.Mock;
 import org.mockito.invocation.InvocationOnMock;
 import org.mockito.stubbing.Answer;
 
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper
 public class UiccCarrierPrivilegeRulesTest extends TelephonyTest {
     private UiccCarrierPrivilegeRules mUiccCarrierPrivilegeRules;
-    public UiccCarrierPrivilegeRulesTest() {
-        super();
-    }
-    private UiccCarrierPrivilegeRulesHandlerThread mTestHandlerThread;
-    private Handler mHandler;
-
-    private static final int EVENT_OPEN_LOGICAL_CHANNEL_DONE = 1;
-    private static final int EVENT_TEST_DONE = 2;
 
     @Mock
     private UiccProfile mUiccProfile;
 
-    private class UiccCarrierPrivilegeRulesHandlerThread extends HandlerThread {
-
-        private UiccCarrierPrivilegeRulesHandlerThread(String name) {
-            super(name);
-        }
-
-        @Override
-        public void onLooperPrepared() {
-            /* create a custom handler for the Handler Thread */
-            mHandler = new Handler(mTestHandlerThread.getLooper()) {
-                @Override
-                public void handleMessage(Message msg) {
-                    switch (msg.what) {
-                        case EVENT_OPEN_LOGICAL_CHANNEL_DONE:
-                            /* Upon handling this event, new CarrierPrivilegeRule
-                            will be created with the looper of HandlerThread */
-                            mUiccCarrierPrivilegeRules = new UiccCarrierPrivilegeRules(
-                                    mUiccProfile, mHandler.obtainMessage(EVENT_TEST_DONE));
-                            break;
-                        case EVENT_TEST_DONE:
-                            setReady(true);
-                            break;
-                        default:
-                            logd("Unknown Event " + msg.what);
-                    }
-                }
-            };
-            setReady(true);
-        }
-    }
-
     @Before
     public void setUp() throws Exception {
         super.setUp(getClass().getSimpleName());
-        mTestHandlerThread = new UiccCarrierPrivilegeRulesHandlerThread(TAG);
-        mTestHandlerThread.start();
-
-        waitUntilReady();
     }
 
     @After
     public void tearDown() throws Exception {
-        mTestHandlerThread.quit();
         super.tearDown();
         mUiccCarrierPrivilegeRules = null;
     }
@@ -135,10 +94,8 @@
             }
         }).when(mUiccProfile).iccCloseLogicalChannel(anyInt(), any(Message.class));
 
-        Message mCardOpenLogicalChannel = mHandler.obtainMessage(EVENT_OPEN_LOGICAL_CHANNEL_DONE);
-        setReady(false);
-        mCardOpenLogicalChannel.sendToTarget();
-        waitUntilReady();
+        mUiccCarrierPrivilegeRules = new UiccCarrierPrivilegeRules(mUiccProfile, null);
+        processAllMessages();
     }
 
     @Test
@@ -384,10 +341,8 @@
         }).when(mUiccProfile).iccCloseLogicalChannel(anyInt(), any(Message.class));
 
 
-        Message mCardOpenLogicalChannel = mHandler.obtainMessage(EVENT_OPEN_LOGICAL_CHANNEL_DONE);
-        setReady(false);
-        mCardOpenLogicalChannel.sendToTarget();
-        waitUntilReady();
+        mUiccCarrierPrivilegeRules = new UiccCarrierPrivilegeRules(mUiccProfile, null);
+        processAllMessages();
 
         assertTrue(mUiccCarrierPrivilegeRules.hasCarrierPrivilegeRules());
         assertEquals(1, mUiccCarrierPrivilegeRules.getPackageNames().size());
@@ -446,11 +401,8 @@
             }
         }).when(mUiccProfile).iccCloseLogicalChannel(anyInt(), any(Message.class));
 
-
-        Message mCardOpenLogicalChannel = mHandler.obtainMessage(EVENT_OPEN_LOGICAL_CHANNEL_DONE);
-        setReady(false);
-        mCardOpenLogicalChannel.sendToTarget();
-        waitUntilReady();
+        mUiccCarrierPrivilegeRules = new UiccCarrierPrivilegeRules(mUiccProfile, null);
+        processAllMessages();
 
         assertTrue(mUiccCarrierPrivilegeRules.hasCarrierPrivilegeRules());
         assertEquals(1, mUiccCarrierPrivilegeRules.getPackageNames().size());
@@ -506,11 +458,8 @@
             }
         }).when(mUiccProfile).iccCloseLogicalChannel(anyInt(), any(Message.class));
 
-
-        Message mCardOpenLogicalChannel = mHandler.obtainMessage(EVENT_OPEN_LOGICAL_CHANNEL_DONE);
-        setReady(false);
-        mCardOpenLogicalChannel.sendToTarget();
-        waitUntilReady();
+        mUiccCarrierPrivilegeRules = new UiccCarrierPrivilegeRules(mUiccProfile, null);
+        processAllMessages();
 
         Signature signature = new Signature("abcd92cbb156b280fa4e1429a6eceeb6e5c1bfe4");
         assertTrue(mUiccCarrierPrivilegeRules.hasCarrierPrivilegeRules());
@@ -560,10 +509,8 @@
             }
         }).when(mUiccProfile).iccCloseLogicalChannel(anyInt(), any(Message.class));
 
-        Message mCardOpenLogicalChannel = mHandler.obtainMessage(EVENT_OPEN_LOGICAL_CHANNEL_DONE);
-        setReady(false);
-        mCardOpenLogicalChannel.sendToTarget();
-        waitUntilReady();
+        mUiccCarrierPrivilegeRules = new UiccCarrierPrivilegeRules(mUiccProfile, null);
+        processAllMessages();
 
         assertTrue(!mUiccCarrierPrivilegeRules.hasCarrierPrivilegeRules());
     }
@@ -636,11 +583,8 @@
             }
         }).when(mUiccProfile).iccCloseLogicalChannel(anyInt(), any(Message.class));
 
-
-        Message mCardOpenLogicalChannel = mHandler.obtainMessage(EVENT_OPEN_LOGICAL_CHANNEL_DONE);
-        setReady(false);
-        mCardOpenLogicalChannel.sendToTarget();
-        waitUntilReady();
+        mUiccCarrierPrivilegeRules = new UiccCarrierPrivilegeRules(mUiccProfile, null);
+        processAllMessages();
 
         assertTrue(mUiccCarrierPrivilegeRules.hasCarrierPrivilegeRules());
         assertEquals(1, mUiccCarrierPrivilegeRules.getPackageNames().size());
diff --git a/tests/telephonytests/src/com/android/internal/telephony/uicc/UiccControllerTest.java b/tests/telephonytests/src/com/android/internal/telephony/uicc/UiccControllerTest.java
index 5fc115f..e518f3e 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/uicc/UiccControllerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/uicc/UiccControllerTest.java
@@ -15,11 +15,12 @@
  */
 package com.android.internal.telephony.uicc;
 
-import static com.android.internal.telephony.TelephonyTestUtils.waitForMs;
+import static junit.framework.Assert.fail;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
 import static org.mockito.Mockito.any;
 import static org.mockito.Mockito.atLeast;
 import static org.mockito.Mockito.doReturn;
@@ -28,33 +29,38 @@
 
 import android.os.AsyncResult;
 import android.os.Handler;
-import android.os.HandlerThread;
 import android.os.Message;
+import android.preference.PreferenceManager;
 import android.telephony.TelephonyManager;
 import android.telephony.UiccCardInfo;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
 
 import androidx.test.InstrumentationRegistry;
 import androidx.test.filters.SmallTest;
 
-import com.android.internal.telephony.CommandsInterface;
 import com.android.internal.telephony.TelephonyTest;
 import com.android.internal.telephony.uicc.euicc.EuiccCard;
 
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
+import org.junit.runner.RunWith;
 import org.mockito.ArgumentCaptor;
 import org.mockito.Mock;
 
 import java.util.ArrayList;
 
+
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper
 public class UiccControllerTest extends TelephonyTest {
     private UiccController mUiccControllerUT;
-    private UiccControllerHandlerThread mUiccControllerHandlerThread;
     private static final int PHONE_COUNT = 1;
     private static final int ICC_CHANGED_EVENT = 0;
     private static final int EVENT_GET_ICC_STATUS_DONE = 3;
     private static final int EVENT_GET_SLOT_STATUS_DONE = 4;
+    private static final int EVENT_EID_READY = 9;
     @Mock
     private Handler mMockedHandler;
     @Mock
@@ -62,24 +68,12 @@
     @Mock
     private UiccSlot mMockSlot;
     @Mock
+    private UiccSlot mMockRemovableEuiccSlot;
+    @Mock
     private UiccCard mMockCard;
     @Mock
     private EuiccCard mMockEuiccCard;
 
-    private class UiccControllerHandlerThread extends HandlerThread {
-
-        private UiccControllerHandlerThread(String name) {
-            super(name);
-        }
-        @Override
-        public void onLooperPrepared() {
-            /* create a new UICC Controller associated with the simulated Commands */
-            mUiccControllerUT = UiccController.make(mContext,
-                    new CommandsInterface[]{mSimulatedCommands});
-            setReady(true);
-        }
-    }
-
     private IccCardApplicationStatus composeUiccApplicationStatus(
             IccCardApplicationStatus.AppType appType,
             IccCardApplicationStatus.AppState appState, String aid) {
@@ -95,6 +89,9 @@
     @Before
     public void setUp() throws Exception {
         super.setUp(this.getClass().getSimpleName());
+        // some tests use use the shared preferences in the runner context, so reset them here
+        PreferenceManager.getDefaultSharedPreferences(InstrumentationRegistry.getContext())
+                .edit().clear().commit();
 
         doReturn(PHONE_COUNT).when(mTelephonyManager).getPhoneCount();
         doReturn(PHONE_COUNT).when(mTelephonyManager).getSimCount();
@@ -112,21 +109,33 @@
         // for testing we pretend slotIndex is set. In reality it would be invalid on older versions
         // (before 1.2) of hal
         mIccCardStatus.physicalSlotIndex = 0;
-        mUiccControllerHandlerThread = new UiccControllerHandlerThread(TAG);
-        mUiccControllerHandlerThread.start();
-        waitUntilReady();
-        /* expected to get new UiccCards being created
-        wait till the async result and message delay */
-        waitForMs(100);
+        mUiccControllerUT = UiccController.make(mContext);
+        // reset sLastSlotStatus so that onGetSlotStatusDone always sees a change in the slot status
+        mUiccControllerUT.sLastSlotStatus = null;
+        processAllMessages();
     }
 
     @After
     public void tearDown() throws Exception {
-        mUiccControllerHandlerThread.quit();
         super.tearDown();
     }
 
-    @Test @SmallTest
+    /**
+     * Replace num slots and euicc slots resources and reinstantiate the UiccController
+     */
+    private void reconfigureSlots(int numSlots, int[] nonRemovableEuiccSlots) throws Exception {
+        mContextFixture.putIntResource(com.android.internal.R.integer.config_num_physical_slots,
+                numSlots);
+        mContextFixture.putIntArrayResource(
+                com.android.internal.R.array.non_removable_euicc_slots,
+                nonRemovableEuiccSlots);
+        replaceInstance(UiccController.class, "mInstance", null, null);
+        mUiccControllerUT = UiccController.make(mContext);
+        processAllMessages();
+    }
+
+    @Test
+    @SmallTest
     public void testSanity() {
         // radio power is expected to be on which should trigger icc card and slot status requests
         verify(mSimulatedCommandsVerifier, times(1)).getIccCardStatus(any(Message.class));
@@ -152,13 +161,14 @@
         assertNull(mUiccControllerUT.getIccFileHandler(0, UiccController.APP_FAM_IMS));
     }
 
-    @Test @SmallTest
+    @Test
+    @SmallTest
     public void testPowerOff() {
         /* Uicc Controller registered for event off to unavail */
         logd("radio power state transition from off to unavail, dispose UICC Card");
         testSanity();
         mSimulatedCommands.requestShutdown(null);
-        waitForMs(50);
+        processAllMessages();
         assertNull(mUiccControllerUT.getUiccCard(0));
         assertEquals(TelephonyManager.RADIO_POWER_UNAVAILABLE, mSimulatedCommands.getRadioState());
     }
@@ -166,12 +176,13 @@
     @Test @SmallTest
     public void testPowerOn() {
         mSimulatedCommands.setRadioPower(true, null);
-        waitForMs(500);
+        processAllMessages();
         assertNotNull(mUiccControllerUT.getUiccCard(0));
         assertEquals(TelephonyManager.RADIO_POWER_ON, mSimulatedCommands.getRadioState());
     }
 
-    @Test @SmallTest
+    @Test
+    @SmallTest
     public void testPowerOffPowerOnWithApp() {
          /* update app status and index */
         IccCardApplicationStatus cdmaApp = composeUiccApplicationStatus(
@@ -206,7 +217,8 @@
         assertNull(mUiccControllerUT.getIccFileHandler(0, UiccController.APP_FAM_IMS));
     }
 
-    @Test @SmallTest
+    @Test
+    @SmallTest
     public void testIccChangedListener() {
         mUiccControllerUT.registerForIccChanged(mMockedHandler, ICC_CHANGED_EVENT, null);
         testPowerOff();
@@ -441,4 +453,182 @@
         assertEquals(TelephonyManager.UNSUPPORTED_CARD_ID,
                 mUiccControllerUT.getCardIdForDefaultEuicc());
     }
+
+    /**
+     * The default eUICC should not be the removable slot if there is a built-in eUICC.
+     */
+    @Test
+    public void testDefaultEuiccIsNotRemovable() {
+        try {
+            reconfigureSlots(2, new int[]{ 1 } /* non-removable slot */);
+        } catch (Exception e) {
+            fail("Unable to reconfigure slots.");
+        }
+
+        // Give UiccController a real context so it can use shared preferences
+        mUiccControllerUT.mContext = InstrumentationRegistry.getContext();
+
+        // Mock out UiccSlots so that [0] is a removable eUICC and [1] is built-in
+        mUiccControllerUT.mUiccSlots[0] = mMockRemovableEuiccSlot;
+        doReturn(true).when(mMockRemovableEuiccSlot).isEuicc();
+        doReturn(true).when(mMockRemovableEuiccSlot).isRemovable();
+        mUiccControllerUT.mUiccSlots[1] = mMockSlot;
+        doReturn(true).when(mMockSlot).isEuicc();
+        doReturn(false).when(mMockSlot).isRemovable();
+
+        // simulate slot status loaded so that the UiccController sets the card ID
+        IccSlotStatus iss1 = new IccSlotStatus();
+        iss1.setSlotState(1 /* active */);
+        iss1.eid = "AB123456";
+        IccSlotStatus iss2 = new IccSlotStatus();
+        iss2.setSlotState(1 /* active */);
+        iss2.eid = "ZYW13094";
+        ArrayList<IccSlotStatus> status = new ArrayList<IccSlotStatus>();
+        status.add(iss1);
+        status.add(iss2);
+        AsyncResult ar = new AsyncResult(null, status, null);
+        Message msg = Message.obtain(mUiccControllerUT, EVENT_GET_SLOT_STATUS_DONE, ar);
+        mUiccControllerUT.handleMessage(msg);
+
+        // assert that the default eUICC is the non-removable eUICC
+        assertEquals(mUiccControllerUT.convertToPublicCardId(iss2.eid),
+                mUiccControllerUT.getCardIdForDefaultEuicc());
+        assertTrue(mUiccControllerUT.convertToPublicCardId(iss2.eid) >= 0);
+    }
+
+    /**
+     * The default eUICC should not be the removable slot if there is a built-in eUICC. This should
+     * not depend on the order of the slots.
+     */
+    @Test
+    public void testDefaultEuiccIsNotRemovable_swapSlotOrder() {
+        try {
+            reconfigureSlots(2, new int[]{ 0 } /* non-removable slot */);
+        } catch (Exception e) {
+            fail("Unable to reconfigure slots.");
+        }
+
+        // Give UiccController a real context so it can use shared preferences
+        mUiccControllerUT.mContext = InstrumentationRegistry.getContext();
+
+        // Mock out UiccSlots so that [0] is a built-in eUICC and [1] is removable
+        mUiccControllerUT.mUiccSlots[0] = mMockSlot;
+        doReturn(true).when(mMockSlot).isEuicc();
+        doReturn(false).when(mMockSlot).isRemovable();
+        mUiccControllerUT.mUiccSlots[1] = mMockRemovableEuiccSlot;
+        doReturn(true).when(mMockRemovableEuiccSlot).isEuicc();
+        doReturn(true).when(mMockRemovableEuiccSlot).isRemovable();
+
+        // simulate slot status loaded so that the UiccController sets the card ID
+        IccSlotStatus iss1 = new IccSlotStatus();
+        iss1.setSlotState(1 /* active */);
+        iss1.eid = "AB123456";
+        IccSlotStatus iss2 = new IccSlotStatus();
+        iss2.setSlotState(1 /* active */);
+        iss2.eid = "ZYW13094";
+        ArrayList<IccSlotStatus> status = new ArrayList<IccSlotStatus>();
+        status.add(iss1);
+        status.add(iss2);
+        AsyncResult ar = new AsyncResult(null, status, null);
+        Message msg = Message.obtain(mUiccControllerUT, EVENT_GET_SLOT_STATUS_DONE, ar);
+        mUiccControllerUT.handleMessage(msg);
+
+        // assert that the default eUICC is the non-removable eUICC
+        assertEquals(mUiccControllerUT.convertToPublicCardId(iss1.eid),
+                mUiccControllerUT.getCardIdForDefaultEuicc());
+        assertTrue(mUiccControllerUT.convertToPublicCardId(iss1.eid) >= 0);
+    }
+
+    /**
+     * The default eUICC should not be the removable slot if there is a built-in eUICC, unless that
+     * eUICC is inactive.
+     * When there is a built-in eUICC which is inactive, we set the mDefaultEuiccCardId to
+     * the removable eUICC.
+     */
+    @Test
+    public void testDefaultEuiccIsNotRemovable_EuiccIsInactive() {
+        try {
+            reconfigureSlots(2, new int[]{ 1 } /* non-removable slot */);
+        } catch (Exception e) {
+            fail();
+        }
+
+        // Give UiccController a real context so it can use shared preferences
+        mUiccControllerUT.mContext = InstrumentationRegistry.getContext();
+
+        // Mock out UiccSlots. Slot 0 is inactive here.
+        mUiccControllerUT.mUiccSlots[0] = mMockSlot;
+        doReturn(true).when(mMockSlot).isEuicc();
+        doReturn(false).when(mMockSlot).isRemovable();
+        mUiccControllerUT.mUiccSlots[1] = mMockRemovableEuiccSlot;
+        doReturn(true).when(mMockRemovableEuiccSlot).isEuicc();
+        doReturn(true).when(mMockRemovableEuiccSlot).isRemovable();
+
+        // simulate slot status loaded with no EID provided (like HAL < 1.4)
+        IccSlotStatus iss1 = new IccSlotStatus();
+        iss1.setSlotState(0 /* inactive */);
+        iss1.eid = "";
+        IccSlotStatus iss2 = new IccSlotStatus();
+        iss2.setSlotState(1 /* active */);
+        iss2.eid = "";
+        ArrayList<IccSlotStatus> status = new ArrayList<IccSlotStatus>();
+        status.add(iss1);
+        status.add(iss2);
+        AsyncResult ar = new AsyncResult(null, status, null);
+        Message msg = Message.obtain(mUiccControllerUT, EVENT_GET_SLOT_STATUS_DONE, ar);
+        mUiccControllerUT.handleMessage(msg);
+
+        // slot status update will not set the default euicc because all EIDs are ""
+        assertEquals(TelephonyManager.UNINITIALIZED_CARD_ID,
+                mUiccControllerUT.getCardIdForDefaultEuicc());
+
+        // when EID is loaded for slot 1, then the default euicc should be set
+        ar = new AsyncResult(1 /* slot */, null, null);
+        msg = Message.obtain(mUiccControllerUT, EVENT_EID_READY, ar);
+        doReturn(mMockEuiccCard).when(mMockRemovableEuiccSlot).getUiccCard();
+        String eidForRemovableEuicc = "ASLDKFJLKSJDF";
+        doReturn(eidForRemovableEuicc).when(mMockEuiccCard).getEid();
+        mUiccControllerUT.handleMessage(msg);
+
+        assertEquals(mUiccControllerUT.convertToPublicCardId(eidForRemovableEuicc),
+                mUiccControllerUT.getCardIdForDefaultEuicc());
+    }
+
+    /**
+     * When IccCardStatus is received, if the EID is known from previous APDU, use it to set the
+     * mDefaultEuiccCardId.
+     */
+    @Test
+    public void testEidFromPreviousApduSetsDefaultEuicc() {
+        // Give UiccController a real context so it can use shared preferences
+        mUiccControllerUT.mContext = InstrumentationRegistry.getContext();
+
+        // Mock out UiccSlots
+        mUiccControllerUT.mUiccSlots[0] = mMockSlot;
+        doReturn(true).when(mMockSlot).isEuicc();
+        doReturn(null).when(mMockSlot).getUiccCard();
+        doReturn("123451234567890").when(mMockSlot).getIccId();
+        doReturn(false).when(mMockSlot).isRemovable();
+
+        // If APDU has already happened, the EuiccCard already knows EID
+        String knownEidFromApdu = "A1B2C3D4E5";
+        doReturn(mMockEuiccCard).when(mMockSlot).getUiccCard();
+        doReturn(knownEidFromApdu).when(mMockEuiccCard).getEid();
+
+        // simulate card status loaded so that the UiccController sets the card ID
+        IccCardStatus ics = new IccCardStatus();
+        ics.setCardState(1 /* present */);
+        ics.setUniversalPinState(3 /* disabled */);
+        ics.atr = "abcdef0123456789abcdef";
+        ics.iccid = "123451234567890";
+        // the IccCardStatus does not contain EID, but it is known from previous APDU
+        ics.eid = null;
+        AsyncResult ar = new AsyncResult(null, ics, null);
+        Message msg = Message.obtain(mUiccControllerUT, EVENT_GET_ICC_STATUS_DONE, ar);
+        mUiccControllerUT.handleMessage(msg);
+
+        // since EID is known and we've gotten card status, the default eUICC card ID should be set
+        assertEquals(mUiccControllerUT.convertToPublicCardId(knownEidFromApdu),
+                mUiccControllerUT.getCardIdForDefaultEuicc());
+    }
 }
diff --git a/tests/telephonytests/src/com/android/internal/telephony/uicc/UiccProfileTest.java b/tests/telephonytests/src/com/android/internal/telephony/uicc/UiccProfileTest.java
index f625237..61a5e13 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/uicc/UiccProfileTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/uicc/UiccProfileTest.java
@@ -21,6 +21,8 @@
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.anyInt;
 import static org.mockito.Mockito.anyString;
 import static org.mockito.Mockito.atLeast;
@@ -32,10 +34,12 @@
 import android.content.Intent;
 import android.content.pm.PackageManager;
 import android.os.Handler;
-import android.os.HandlerThread;
 import android.os.Message;
 import android.os.PersistableBundle;
 import android.telephony.CarrierConfigManager;
+import android.telephony.SubscriptionInfo;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
 
 import androidx.test.filters.SmallTest;
 
@@ -47,11 +51,14 @@
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
+import org.junit.runner.RunWith;
 import org.mockito.ArgumentCaptor;
 import org.mockito.Mock;
 
 import java.util.Map;
 
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper
 public class UiccProfileTest extends TelephonyTest {
     private UiccProfile mUiccProfile;
 
@@ -60,13 +67,8 @@
     }
 
     private IccIoResult mIccIoResult;
-    private static final int SCARY_SLEEP_MS = 200;
 
-    private UiccProfileHandlerThread mTestHandlerThread;
-    private Handler mHandler;
-    private static final int UICCPROFILE_UPDATE_PROFILE_EVENT = 1;
-    private static final int UICCPROFILE_UPDATE_APPLICATION_EVENT = 2;
-    private static final int UICCPROFILE_CARRIER_PRIVILEDGE_LOADED_EVENT = 3;
+    private static final int UICCPROFILE_CARRIER_PRIVILEGE_LOADED_EVENT = 3;
 
     @Mock
     private CatService mCAT;
@@ -77,48 +79,6 @@
     @Mock
     private UiccCard mUiccCard;
 
-    private class UiccProfileHandlerThread extends HandlerThread {
-
-        private UiccProfileHandlerThread(String name) {
-            super(name);
-        }
-
-        @Override
-        public void onLooperPrepared() {
-            mUiccProfile = new UiccProfile(mContextFixture.getTestDouble(),
-                                           mSimulatedCommands, mIccCardStatus, 0 /* phoneId */,
-                                           mUiccCard, new Object());
-            /* create a custom handler for the Handler Thread */
-            mHandler = new Handler(mTestHandlerThread.getLooper()) {
-                @Override
-                public void handleMessage(Message msg) {
-                    switch (msg.what) {
-                        case UICCPROFILE_UPDATE_PROFILE_EVENT:
-                            /* Upon handling this event, new CarrierPrivilegeRule
-                            will be created with the looper of HandlerThread */
-                            logd("Update UICC Profile");
-                            mUiccProfile.update(mContextFixture.getTestDouble(),
-                                    mSimulatedCommands, mIccCardStatus);
-                            setReady(true);
-                            break;
-                        case UICCPROFILE_UPDATE_APPLICATION_EVENT:
-                            logd("Update UICC Profile Applications");
-                            mUiccProfile.update(mContextFixture.getTestDouble(),
-                                    mSimulatedCommands, mIccCardStatus);
-                            setReady(true);
-                            break;
-                        default:
-                            logd("Unknown Event " + msg.what);
-                    }
-                }
-            };
-            /* wait for the carrier privilege rules to be loaded */
-            waitForMs(50);
-            setReady(true);
-            logd("Create UiccProfile");
-        }
-    }
-
     private IccCardApplicationStatus composeUiccApplicationStatus(
             IccCardApplicationStatus.AppType appType,
             IccCardApplicationStatus.AppState appState, String aid) {
@@ -144,21 +104,16 @@
                         mIccCardStatus.mGsmUmtsSubscriptionAppIndex = -1;
         mIccIoResult = new IccIoResult(0x90, 0x00, IccUtils.hexStringToBytes("FF40"));
         mSimulatedCommands.setIccIoResultForApduLogicalChannel(mIccIoResult);
-        /* starting the Handler Thread */
-        mTestHandlerThread = new UiccProfileHandlerThread(TAG);
-        mTestHandlerThread.start();
-
-        waitUntilReady();
-
-        /* wait for the carrier privilege rules to be loaded */
-        waitForMs(50);
+        mUiccProfile = new UiccProfile(mContext, mSimulatedCommands, mIccCardStatus,
+              0 /* phoneId */, mUiccCard, new Object());
+        processAllMessages();
+        logd("Create UiccProfile");
 
         replaceInstance(UiccProfile.class, "mCatService", mUiccProfile, mCAT);
     }
 
     @After
     public void tearDown() throws Exception {
-        mTestHandlerThread.quit();
         super.tearDown();
     }
 
@@ -175,7 +130,6 @@
             } else {
                 assertFalse(mUiccProfile.isApplicationOnIcc(mAppType));
             }
-
         }
     }
 
@@ -218,14 +172,9 @@
         mIccCardStatus.mCdmaSubscriptionAppIndex = 0;
         mIccCardStatus.mImsSubscriptionAppIndex = 1;
         mIccCardStatus.mGsmUmtsSubscriptionAppIndex = 2;
-        Message mProfileUpdate = mHandler.obtainMessage(UICCPROFILE_UPDATE_APPLICATION_EVENT);
-        setReady(false);
-        mProfileUpdate.sendToTarget();
-
-        waitUntilReady();
-
-        /* wait for the carrier privilege rules to be loaded */
-        waitForMs(50);
+        logd("Update UICC Profile Applications");
+        mUiccProfile.update(mContext, mSimulatedCommands, mIccCardStatus);
+        processAllMessages();
 
         assertEquals(3, mUiccProfile.getNumApplications());
         assertTrue(mUiccProfile.isApplicationOnIcc(IccCardApplicationStatus.AppType.APPTYPE_CSIM));
@@ -239,14 +188,8 @@
         int mChannelId = 1;
         mIccCardStatus.mCardState = IccCardStatus.CardState.CARDSTATE_PRESENT;
         mSimulatedCommands.setOpenChannelId(mChannelId);
-        Message mCardUpdate = mHandler.obtainMessage(UICCPROFILE_UPDATE_PROFILE_EVENT);
-        setReady(false);
-        mCardUpdate.sendToTarget();
-        /* try to create a new CarrierPrivilege, loading state -> loaded state */
-        /* wait till the async result and message delay */
-        waitUntilReady();
-        /* wait for the carrier privilege rules to be loaded */
-        waitForMs(50);
+        mUiccProfile.update(mContext, mSimulatedCommands, mIccCardStatus);
+        processAllMessages();
 
         assertTrue(mUiccProfile.areCarrierPriviligeRulesLoaded());
         verify(mSimulatedCommandsVerifier, times(2)).iccOpenLogicalChannel(isA(String.class),
@@ -261,7 +204,7 @@
     @SmallTest
     public void testUpdateUiccProfilePinState() {
         mIccCardStatus.mUniversalPinState = IccCardStatus.PinState.PINSTATE_ENABLED_VERIFIED;
-        mUiccProfile.update(mContextFixture.getTestDouble(), mSimulatedCommands, mIccCardStatus);
+        mUiccProfile.update(mContext, mSimulatedCommands, mIccCardStatus);
         assertEquals(IccCardStatus.PinState.PINSTATE_ENABLED_VERIFIED,
                 mUiccProfile.getUniversalPinState());
     }
@@ -270,13 +213,13 @@
     @SmallTest
     public void testCarrierPriviledgeLoadedListener() {
         mUiccProfile.registerForCarrierPrivilegeRulesLoaded(mMockedHandler,
-                UICCPROFILE_CARRIER_PRIVILEDGE_LOADED_EVENT, null);
+                UICCPROFILE_CARRIER_PRIVILEGE_LOADED_EVENT, null);
         ArgumentCaptor<Message> mCaptorMessage = ArgumentCaptor.forClass(Message.class);
         ArgumentCaptor<Long> mCaptorLong = ArgumentCaptor.forClass(Long.class);
         testUpdateUiccProfile();
         verify(mMockedHandler, atLeast(1)).sendMessageDelayed(mCaptorMessage.capture(),
                 mCaptorLong.capture());
-        assertEquals(UICCPROFILE_CARRIER_PRIVILEDGE_LOADED_EVENT, mCaptorMessage.getValue().what);
+        assertEquals(UICCPROFILE_CARRIER_PRIVILEGE_LOADED_EVENT, mCaptorMessage.getValue().what);
     }
 
     @Test
@@ -303,19 +246,16 @@
         mIccCardStatus.mCdmaSubscriptionAppIndex = 0;
         mIccCardStatus.mImsSubscriptionAppIndex = 1;
         mIccCardStatus.mGsmUmtsSubscriptionAppIndex = 2;
-        Message mProfileUpdate = mHandler.obtainMessage(UICCPROFILE_UPDATE_APPLICATION_EVENT);
-        setReady(false);
-        mProfileUpdate.sendToTarget();
+        logd("Update UICC Profile Applications");
+        mUiccProfile.update(mContext, mSimulatedCommands, mIccCardStatus);
+        processAllMessages();
 
-        waitUntilReady();
-
-        /* wait for the carrier privilege rules to be loaded */
-        waitForMs(50);
         assertEquals(3, mUiccProfile.getNumApplications());
 
         mUiccProfile.mHandler.sendMessage(
                 mUiccProfile.mHandler.obtainMessage(UiccProfile.EVENT_APP_READY));
-        waitForMs(SCARY_SLEEP_MS);
+        waitForMs(100);
+        processAllMessages();
         assertEquals(mUiccProfile.getState(), State.NOT_READY);
     }
 
@@ -336,19 +276,16 @@
         mIccCardStatus.mCdmaSubscriptionAppIndex = 0;
         mIccCardStatus.mImsSubscriptionAppIndex = 1;
         mIccCardStatus.mGsmUmtsSubscriptionAppIndex = 2;
-        Message mProfileUpdate = mHandler.obtainMessage(UICCPROFILE_UPDATE_APPLICATION_EVENT);
-        setReady(false);
-        mProfileUpdate.sendToTarget();
+        logd("Update UICC Profile Applications");
+        mUiccProfile.update(mContext, mSimulatedCommands, mIccCardStatus);
+        processAllMessages();
 
-        waitUntilReady();
-
-        /* wait for the carrier privilege rules to be loaded */
-        waitForMs(50);
         assertEquals(3, mUiccProfile.getNumApplications());
 
         mUiccProfile.mHandler.sendMessage(
                 mUiccProfile.mHandler.obtainMessage(UiccProfile.EVENT_APP_READY));
-        waitForMs(SCARY_SLEEP_MS);
+        waitForMs(100);
+        processAllMessages();
         // state is loaded as all records are loaded right away as SimulatedCommands returns
         // response for them right away. Ideally applications and records should be mocked.
         assertEquals(State.LOADED, mUiccProfile.getState());
@@ -371,19 +308,16 @@
         mIccCardStatus.mCdmaSubscriptionAppIndex = -1;
         mIccCardStatus.mImsSubscriptionAppIndex = 0;
         mIccCardStatus.mGsmUmtsSubscriptionAppIndex = 1;
-        Message mProfileUpdate = mHandler.obtainMessage(UICCPROFILE_UPDATE_APPLICATION_EVENT);
-        setReady(false);
-        mProfileUpdate.sendToTarget();
+        logd("Update UICC Profile Applications");
+        mUiccProfile.update(mContext, mSimulatedCommands, mIccCardStatus);
+        processAllMessages();
 
-        waitUntilReady();
-
-        /* wait for the carrier privilege rules to be loaded */
-        waitForMs(50);
         assertEquals(3, mUiccProfile.getNumApplications());
 
         mUiccProfile.mHandler.sendMessage(
                 mUiccProfile.mHandler.obtainMessage(UiccProfile.EVENT_APP_READY));
-        waitForMs(SCARY_SLEEP_MS);
+        waitForMs(100);
+        processAllMessages();
         // state is loaded as all records are loaded right away as SimulatedCommands returns
         // response for them right away. Ideally applications and records should be mocked.
         assertEquals(State.LOADED, mUiccProfile.getState());
@@ -410,19 +344,16 @@
         mIccCardStatus.mCdmaSubscriptionAppIndex = -1;
         mIccCardStatus.mImsSubscriptionAppIndex = 0;
         mIccCardStatus.mGsmUmtsSubscriptionAppIndex = 1;
-        Message mProfileUpdate = mHandler.obtainMessage(UICCPROFILE_UPDATE_APPLICATION_EVENT);
-        setReady(false);
-        mProfileUpdate.sendToTarget();
+        logd("Update UICC Profile Applications");
+        mUiccProfile.update(mContext, mSimulatedCommands, mIccCardStatus);
+        processAllMessages();
 
-        waitUntilReady();
-
-        /* wait for the carrier privilege rules to be loaded */
-        waitForMs(50);
         assertEquals(4, mUiccProfile.getNumApplications());
 
         mUiccProfile.mHandler.sendMessage(
                 mUiccProfile.mHandler.obtainMessage(UiccProfile.EVENT_APP_READY));
-        waitForMs(SCARY_SLEEP_MS);
+        waitForMs(100);
+        processAllMessages();
         // state is loaded as all records are loaded right away as SimulatedCommands returns
         // response for them right away. Ideally applications and records should be mocked.
         assertEquals(State.LOADED, mUiccProfile.getState());
@@ -449,19 +380,16 @@
         mIccCardStatus.mCdmaSubscriptionAppIndex = -1;
         mIccCardStatus.mImsSubscriptionAppIndex = 0;
         mIccCardStatus.mGsmUmtsSubscriptionAppIndex = 2;
-        Message mProfileUpdate = mHandler.obtainMessage(UICCPROFILE_UPDATE_APPLICATION_EVENT);
-        setReady(false);
-        mProfileUpdate.sendToTarget();
+        logd("Update UICC Profile Applications");
+        mUiccProfile.update(mContext, mSimulatedCommands, mIccCardStatus);
+        processAllMessages();
 
-        waitUntilReady();
-
-        /* wait for the carrier privilege rules to be loaded */
-        waitForMs(50);
         assertEquals(4, mUiccProfile.getNumApplications());
 
         mUiccProfile.mHandler.sendMessage(
                 mUiccProfile.mHandler.obtainMessage(UiccProfile.EVENT_APP_READY));
-        waitForMs(SCARY_SLEEP_MS);
+        waitForMs(100);
+        processAllMessages();
         // state is loaded as all records are loaded right away as SimulatedCommands returns
         // response for them right away. Ideally applications and records should be mocked.
         assertEquals(State.LOADED, mUiccProfile.getState());
@@ -474,19 +402,16 @@
         mIccCardStatus.mCdmaSubscriptionAppIndex = -1;
         mIccCardStatus.mImsSubscriptionAppIndex = -1;
         mIccCardStatus.mGsmUmtsSubscriptionAppIndex = -1;
-        Message mProfileUpdate = mHandler.obtainMessage(UICCPROFILE_UPDATE_APPLICATION_EVENT);
-        setReady(false);
-        mProfileUpdate.sendToTarget();
+        logd("Update UICC Profile Applications");
+        mUiccProfile.update(mContext, mSimulatedCommands, mIccCardStatus);
+        processAllMessages();
 
-        waitUntilReady();
-
-        /* wait for the carrier privilege rules to be loaded */
-        waitForMs(50);
         assertEquals(0, mUiccProfile.getNumApplications());
 
         mUiccProfile.mHandler.sendMessage(
                 mUiccProfile.mHandler.obtainMessage(UiccProfile.EVENT_APP_READY));
-        waitForMs(SCARY_SLEEP_MS);
+        waitForMs(100);
+        processAllMessages();
         // state is loaded since there is no applications.
         assertEquals(State.NOT_READY, mUiccProfile.getState());
     }
@@ -501,19 +426,16 @@
         mIccCardStatus.mCdmaSubscriptionAppIndex = -1;
         mIccCardStatus.mImsSubscriptionAppIndex = -1;
         mIccCardStatus.mGsmUmtsSubscriptionAppIndex = -1;
-        Message mProfileUpdate = mHandler.obtainMessage(UICCPROFILE_UPDATE_APPLICATION_EVENT);
-        setReady(false);
-        mProfileUpdate.sendToTarget();
+        logd("Update UICC Profile Applications");
+        mUiccProfile.update(mContext, mSimulatedCommands, mIccCardStatus);
+        processAllMessages();
 
-        waitUntilReady();
-
-        /* wait for the carrier privilege rules to be loaded */
-        waitForMs(50);
         assertEquals(1, mUiccProfile.getNumApplications());
 
         mUiccProfile.mHandler.sendMessage(
                 mUiccProfile.mHandler.obtainMessage(UiccProfile.EVENT_APP_READY));
-        waitForMs(SCARY_SLEEP_MS);
+        waitForMs(100);
+        processAllMessages();
         // state is loaded since there is no applications.
         assertEquals(State.NOT_READY, mUiccProfile.getState());
     }
@@ -534,19 +456,16 @@
         mIccCardStatus.mImsSubscriptionAppIndex = 0;
         mIccCardStatus.mGsmUmtsSubscriptionAppIndex = 1;
 
-        Message mProfileUpdate = mHandler.obtainMessage(UICCPROFILE_UPDATE_APPLICATION_EVENT);
-        setReady(false);
-        mProfileUpdate.sendToTarget();
+        logd("Update UICC Profile Applications");
+        mUiccProfile.update(mContext, mSimulatedCommands, mIccCardStatus);
+        processAllMessages();
 
-        waitUntilReady();
-
-        /* wait for the carrier privilege rules to be loaded */
-        waitForMs(50);
         assertEquals(3, mUiccProfile.getNumApplications());
 
         mUiccProfile.mHandler.sendMessage(
                 mUiccProfile.mHandler.obtainMessage(UiccProfile.EVENT_APP_READY));
-        waitForMs(SCARY_SLEEP_MS);
+        waitForMs(100);
+        processAllMessages();
     }
 
     @Test
@@ -615,7 +534,7 @@
 
         // broadcast CARRIER_CONFIG_CHANGED
         mContext.sendBroadcast(new Intent(CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED));
-        waitForMs(200);
+        processAllMessages();
 
         // verify that setSimOperatorNameForPhone() is called with fakeCarrierName
         ArgumentCaptor<String> stringArgumentCaptor = ArgumentCaptor.forClass(String.class);
@@ -631,6 +550,47 @@
         assertTrue(carrierFound);
     }
 
+    @Mock
+    private SubscriptionInfo mSubscriptionInfo;
+
+    @Test
+    public void testSetOperatorBrandOverride() {
+        testUpdateUiccProfileApplication();
+        String fakeIccId = "1234567";
+        String fakeBrand = "operator";
+
+        mUiccProfile.getApplicationIndex(0).getIccRecords().mIccId = fakeIccId;
+        doReturn(fakeIccId).when(mSubscriptionInfo).getIccId();
+        doReturn(mSubscriptionInfo).when(mSubscriptionController)
+                .getActiveSubscriptionInfoForSimSlotIndex(eq(0), any(), any());
+
+        mUiccProfile.setOperatorBrandOverride(fakeBrand);
+        String brandInSharedPreference = mContext.getSharedPreferences("file name", 0)
+                .getString("operator_branding_" + fakeIccId, null);
+        assertEquals(fakeBrand, brandInSharedPreference);
+    }
+
+    @Test
+    public void testSetOperatorBrandOverrideIccNotMatch() {
+        testUpdateUiccProfileApplication();
+        String fakeIccId1 = "1234567";
+        String fakeIccId2 = "7654321";
+        String fakeBrand = "operator";
+
+        mUiccProfile.getApplicationIndex(0).getIccRecords().mIccId = fakeIccId1;
+        doReturn(fakeIccId2).when(mSubscriptionInfo).getIccId();
+        doReturn(mSubscriptionInfo).when(mSubscriptionController)
+                .getActiveSubscriptionInfoForSimSlotIndex(eq(0), any(), any());
+
+        mUiccProfile.setOperatorBrandOverride(fakeBrand);
+        String brandInSharedPreference = mContext.getSharedPreferences("file name", 0)
+                .getString("operator_branding_" + fakeIccId1, null);
+        assertNull(brandInSharedPreference);
+        brandInSharedPreference = mContext.getSharedPreferences("file name", 0)
+                .getString("operator_branding_" + fakeIccId2, null);
+        assertNull(brandInSharedPreference);
+    }
+
     @Test
     @SmallTest
     public void testIsEmptyProfile() {
diff --git a/tests/telephonytests/src/com/android/internal/telephony/uicc/UiccSlotTest.java b/tests/telephonytests/src/com/android/internal/telephony/uicc/UiccSlotTest.java
index b3a296d..2f2b582 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/uicc/UiccSlotTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/uicc/UiccSlotTest.java
@@ -100,6 +100,7 @@
     @After
     public void tearDown() throws Exception {
         mTestHandlerThread.quit();
+        mTestHandlerThread.join();
         super.tearDown();
     }
 
@@ -154,7 +155,7 @@
         assertEquals(IccCardStatus.CardState.CARDSTATE_ABSENT, mUiccSlot.getCardState());
         assertEquals(iss.iccid, mUiccSlot.getIccId());
         verify(mSubInfoRecordUpdater).updateInternalIccState(
-                IccCardConstants.INTENT_VALUE_ICC_ABSENT, null, phoneId, false);
+                IccCardConstants.INTENT_VALUE_ICC_ABSENT, null, phoneId);
 
         // update slot to active
         mUiccSlot.update(mSimulatedCommands, iss, 0 /* slotIndex */);
@@ -242,7 +243,7 @@
         mIccCardStatus.mCardState = IccCardStatus.CardState.CARDSTATE_ABSENT;
         mUiccSlot.update(mSimulatedCommands, mIccCardStatus, phoneId, slotIndex);
         verify(mSubInfoRecordUpdater).updateInternalIccState(
-                IccCardConstants.INTENT_VALUE_ICC_ABSENT, null, phoneId, false);
+                IccCardConstants.INTENT_VALUE_ICC_ABSENT, null, phoneId);
         assertEquals(IccCardStatus.CardState.CARDSTATE_ABSENT, mUiccSlot.getCardState());
         assertNull(mUiccSlot.getUiccCard());
     }
@@ -271,11 +272,10 @@
         assertEquals(IccCardStatus.CardState.CARDSTATE_ABSENT, mUiccSlot.getCardState());
 
         // assert that we tried to update subscriptions
-        verify(mSubInfoRecordUpdater).updateInternalIccState(
-                IccCardConstants.INTENT_VALUE_ICC_ABSENT, null, activeIss.logicalSlotIndex, true);
+        verify(mSubInfoRecordUpdater).updateInternalIccStateForInactiveSlot(
+                activeIss.logicalSlotIndex, inactiveIss.iccid);
     }
 
-
     @Test
     @SmallTest
     public void testUiccSlotCreateAndDispose() {
@@ -295,7 +295,7 @@
         mIccCardStatus.mCardState = IccCardStatus.CardState.CARDSTATE_ABSENT;
         mUiccSlot.update(mSimulatedCommands, mIccCardStatus, phoneId, slotIndex);
         verify(mSubInfoRecordUpdater).updateInternalIccState(
-                IccCardConstants.INTENT_VALUE_ICC_ABSENT, null, phoneId, false);
+                IccCardConstants.INTENT_VALUE_ICC_ABSENT, null, phoneId);
         verify(mUiccProfile).dispose();
         assertEquals(IccCardStatus.CardState.CARDSTATE_ABSENT, mUiccSlot.getCardState());
         assertNull(mUiccSlot.getUiccCard());
@@ -320,7 +320,7 @@
 
         // Verify that UNKNOWN state is sent to SubscriptionInfoUpdater in this case.
         verify(mSubInfoRecordUpdater).updateInternalIccState(
-                IccCardConstants.INTENT_VALUE_ICC_UNKNOWN, null, phoneId, false);
+                IccCardConstants.INTENT_VALUE_ICC_UNKNOWN, null, phoneId);
         assertEquals(IccCardStatus.CardState.CARDSTATE_ABSENT, mUiccSlot.getCardState());
         assertNull(mUiccSlot.getUiccCard());
 
@@ -330,7 +330,7 @@
 
         // Verify that ABSENT state is sent to SubscriptionInfoUpdater in this case.
         verify(mSubInfoRecordUpdater).updateInternalIccState(
-                IccCardConstants.INTENT_VALUE_ICC_ABSENT, null, phoneId, false);
+                IccCardConstants.INTENT_VALUE_ICC_ABSENT, null, phoneId);
         assertEquals(IccCardStatus.CardState.CARDSTATE_ABSENT, mUiccSlot.getCardState());
         assertNull(mUiccSlot.getUiccCard());
     }
diff --git a/tests/telephonytests/src/com/android/internal/telephony/uicc/euicc/EuiccCardTest.java b/tests/telephonytests/src/com/android/internal/telephony/uicc/euicc/EuiccCardTest.java
index 07919c5..383a379 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/uicc/euicc/EuiccCardTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/uicc/euicc/EuiccCardTest.java
@@ -31,7 +31,7 @@
 
 import android.content.res.Resources;
 import android.os.Handler;
-import android.os.HandlerThread;
+import android.os.Looper;
 import android.os.Message;
 import android.service.carrier.CarrierIdentifier;
 import android.service.euicc.EuiccProfileInfo;
@@ -39,6 +39,8 @@
 import android.telephony.euicc.EuiccCardManager;
 import android.telephony.euicc.EuiccNotification;
 import android.telephony.euicc.EuiccRulesAuthTable;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
 import android.util.ExceptionUtils;
 import android.util.Log;
 
@@ -57,14 +59,15 @@
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
+import org.junit.runner.RunWith;
 import org.mockito.Mock;
 
 import java.util.Arrays;
 import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
 
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper
 public class EuiccCardTest extends TelephonyTest {
-    private static final long WAIT_TIMEOUT_MLLIS = 5000;
 
     private static class ResultCaptor<T> extends AsyncResultCallback<T> {
         public T result;
@@ -76,14 +79,6 @@
             mLatch = new CountDownLatch(1);
         }
 
-        public void await() {
-            try {
-                mLatch.await(WAIT_TIMEOUT_MLLIS, TimeUnit.MILLISECONDS);
-            } catch (InterruptedException e) {
-                fail("Execution is interrupted: " + e);
-            }
-        }
-
         @Override
         public void onResult(T r) {
             result = r;
@@ -97,41 +92,11 @@
         }
     }
 
-    private class UiccCardHandlerThread extends HandlerThread {
-        private UiccCardHandlerThread(String name) {
-            super(name);
-        }
-
-        @Override
-        public void onLooperPrepared() {
-            mEuiccCard =
-                    new EuiccCard(mContextFixture.getTestDouble(), mMockCi, mMockIccCardStatus,
-                            0 /* phoneId */, new Object()) {
-                        @Override
-                        protected byte[] getDeviceId() {
-                            return IccUtils.bcdToBytes("987654321012345");
-                        }
-
-                        @Override
-                        protected void loadEidAndNotifyRegistrants() {}
-
-                        @Override
-                        protected Resources getResources() {
-                            return mMockResources;
-                        }
-
-                    };
-            mHandler = new Handler(mTestHandlerThread.getLooper());
-            setReady(true);
-        }
-    }
-
     @Mock
     private CommandsInterface mMockCi;
     @Mock
     private IccCardStatus mMockIccCardStatus;
 
-    private UiccCardHandlerThread mTestHandlerThread;
     private Handler mHandler;
 
     private EuiccCard mEuiccCard;
@@ -149,15 +114,29 @@
                         mMockIccCardStatus.mGsmUmtsSubscriptionAppIndex = -1;
         mMockIccCardStatus.mCardState = IccCardStatus.CardState.CARDSTATE_PRESENT;
 
-        mTestHandlerThread = new UiccCardHandlerThread(getClass().getSimpleName());
-        mTestHandlerThread.start();
+        mEuiccCard =
+            new EuiccCard(mContext, mMockCi, mMockIccCardStatus,
+                0 /* phoneId */, new Object()) {
+                @Override
+                protected byte[] getDeviceId() {
+                    return IccUtils.bcdToBytes("987654321012345");
+                }
 
-        waitUntilReady();
+                @Override
+                protected void loadEidAndNotifyRegistrants() {}
+
+                @Override
+                protected Resources getResources() {
+                    return mMockResources;
+                }
+
+            };
+        mHandler = new Handler(Looper.myLooper());
+        processAllMessages();
     }
 
     @After
     public void tearDown() throws Exception {
-        mTestHandlerThread.quit();
         super.tearDown();
     }
 
@@ -169,56 +148,46 @@
     }
 
     @Test
-    public void testPassEidInContructor() throws InterruptedException {
+    public void testPassEidInConstructor() {
         mMockIccCardStatus.eid = "1A2B3C4D";
         mEuiccCard = new EuiccCard(mContextFixture.getTestDouble(), mMockCi,
                 mMockIccCardStatus, 0 /* phoneId */, new Object());
 
         final int eventEidReady = 0;
-        final CountDownLatch latch = new CountDownLatch(1);
-        Handler handler = new Handler(mTestHandlerThread.getLooper()) {
+        Handler handler = new Handler(Looper.myLooper()) {
             @Override
             public void handleMessage(Message msg) {
                 if (msg.what == eventEidReady) {
                     assertEquals("1A2B3C4D", mEuiccCard.getEid());
-                    latch.countDown();
                 }
             }
         };
         // This will instantly return, since EID is already set
         mEuiccCard.registerForEidReady(handler, eventEidReady, null /* obj */);
-        assertTrue(latch.await(WAIT_TIMEOUT_MLLIS, TimeUnit.MILLISECONDS));
+        processAllMessages();
     }
 
     @Test
-    public void testLoadEidAndNotifyRegistrants() throws InterruptedException {
+    public void testLoadEidAndNotifyRegistrants() {
         int channel = mockLogicalChannelResponses("BF3E065A041A2B3C4D9000");
-
-        {
-            final CountDownLatch latch = new CountDownLatch(1);
-            mHandler.post(() -> {
-                mEuiccCard = new EuiccCard(mContextFixture.getTestDouble(), mMockCi,
-                        mMockIccCardStatus, 0 /* phoneId */, new Object());
-                latch.countDown();
-            });
-            assertTrue(latch.await(WAIT_TIMEOUT_MLLIS, TimeUnit.MILLISECONDS));
-        }
+        mHandler.post(() -> {
+            mEuiccCard = new EuiccCard(mContextFixture.getTestDouble(), mMockCi,
+                    mMockIccCardStatus, 0 /* phoneId */, new Object());
+        });
+        processAllMessages();
 
         final int eventEidReady = 0;
-        final CountDownLatch latch = new CountDownLatch(1);
-        Handler handler = new Handler(mTestHandlerThread.getLooper()) {
+        Handler handler = new Handler(Looper.myLooper()) {
             @Override
             public void handleMessage(Message msg) {
                 if (msg.what == eventEidReady) {
                     assertEquals("1A2B3C4D", mEuiccCard.getEid());
-                    latch.countDown();
                 }
             }
         };
 
         mEuiccCard.registerForEidReady(handler, eventEidReady, null /* obj */);
-        assertTrue(latch.await(WAIT_TIMEOUT_MLLIS, TimeUnit.MILLISECONDS));
-
+        processAllMessages();
         verifyStoreData(channel, "BF3E035C015A");
     }
 
@@ -229,7 +198,7 @@
 
         ResultCaptor<EuiccProfileInfo[]> resultCaptor = new ResultCaptor<>();
         mEuiccCard.getAllProfiles(resultCaptor, mHandler);
-        resultCaptor.await();
+        processAllMessages();
 
         assertUnexpectedException(resultCaptor.exception);
         EuiccProfileInfo[] profiles = resultCaptor.result;
@@ -247,7 +216,7 @@
 
         ResultCaptor<EuiccProfileInfo[]> resultCaptor = new ResultCaptor<>();
         mEuiccCard.getAllProfiles(resultCaptor, mHandler);
-        resultCaptor.await();
+        processAllMessages();
 
         EuiccProfileInfo[] profiles = resultCaptor.result;
         assertEquals(1, profiles.length);
@@ -274,7 +243,7 @@
 
         ResultCaptor<EuiccProfileInfo> resultCaptor = new ResultCaptor<>();
         mEuiccCard.getProfile("98760000000000543210", resultCaptor, mHandler);
-        resultCaptor.await();
+        processAllMessages();
 
         EuiccProfileInfo profile = resultCaptor.result;
         assertEquals("98760000000000543210", profile.getIccid());
@@ -308,7 +277,7 @@
 
         ResultCaptor<Void> resultCaptor = new ResultCaptor<>();
         mEuiccCard.disableProfile("98760000000000543210", true, resultCaptor, mHandler);
-        resultCaptor.await();
+        processAllMessages();
 
         assertUnexpectedException(resultCaptor.exception);
         verifyStoreData(channel, "BF3211A00C5A0A896700000000004523018101FF");
@@ -320,7 +289,7 @@
 
         ResultCaptor<Void> resultCaptor = new ResultCaptor<>();
         mEuiccCard.disableProfile("98760000000000543210", true, resultCaptor, mHandler);
-        resultCaptor.await();
+        processAllMessages();
 
         assertUnexpectedException(resultCaptor.exception);
         verifyStoreData(channel, "BF3211A00C5A0A896700000000004523018101FF");
@@ -332,7 +301,7 @@
 
         ResultCaptor<Void> resultCaptor = new ResultCaptor<>();
         mEuiccCard.disableProfile("98760000000000543210", true, resultCaptor, mHandler);
-        resultCaptor.await();
+        processAllMessages();
 
         assertEquals(3, ((EuiccCardErrorException) resultCaptor.exception).getErrorCode());
         verifyStoreData(channel, "BF3211A00C5A0A896700000000004523018101FF");
@@ -344,7 +313,7 @@
 
         ResultCaptor<Void> resultCaptor = new ResultCaptor<>();
         mEuiccCard.switchToProfile("98760000000000543210", true, resultCaptor, mHandler);
-        resultCaptor.await();
+        processAllMessages();
 
         assertUnexpectedException(resultCaptor.exception);
         verifyStoreData(channel, "BF3111A00C5A0A896700000000004523018101FF");
@@ -356,7 +325,7 @@
 
         ResultCaptor<Void> resultCaptor = new ResultCaptor<>();
         mEuiccCard.switchToProfile("98760000000000543210", true, resultCaptor, mHandler);
-        resultCaptor.await();
+        processAllMessages();
 
         assertUnexpectedException(resultCaptor.exception);
         verifyStoreData(channel, "BF3111A00C5A0A896700000000004523018101FF");
@@ -368,7 +337,7 @@
 
         ResultCaptor<Void> resultCaptor = new ResultCaptor<>();
         mEuiccCard.switchToProfile("98760000000000543210", true, resultCaptor, mHandler);
-        resultCaptor.await();
+        processAllMessages();
 
         assertEquals(3, ((EuiccCardErrorException) resultCaptor.exception).getErrorCode());
         verifyStoreData(channel, "BF3111A00C5A0A896700000000004523018101FF");
@@ -380,7 +349,7 @@
 
         ResultCaptor<String> resultCaptor = new ResultCaptor<>();
         mEuiccCard.getEid(resultCaptor, mHandler);
-        resultCaptor.await();
+        processAllMessages();
 
         assertEquals("1A2B3C4D", resultCaptor.result);
         verifyStoreData(channel, "BF3E035C015A");
@@ -392,7 +361,7 @@
 
         ResultCaptor<Void> resultCaptor = new ResultCaptor<>();
         mEuiccCard.setNickname("98760000000000543210", "new nickname", resultCaptor, mHandler);
-        resultCaptor.await();
+        processAllMessages();
 
         assertUnexpectedException(resultCaptor.exception);
         verifyStoreData(channel, "BF291A5A0A89670000000000452301900C6E6577206E69636B6E616D65");
@@ -404,7 +373,7 @@
 
         ResultCaptor<Void> resultCaptor = new ResultCaptor<>();
         mEuiccCard.deleteProfile("98760000000000543210", resultCaptor, mHandler);
-        resultCaptor.await();
+        processAllMessages();
 
         assertUnexpectedException(resultCaptor.exception);
         verifyStoreData(channel, "BF330C5A0A89670000000000452301");
@@ -416,7 +385,7 @@
 
         ResultCaptor<Void> resultCaptor = new ResultCaptor<>();
         mEuiccCard.deleteProfile("98760000000000543210", resultCaptor, mHandler);
-        resultCaptor.await();
+        processAllMessages();
 
         assertEquals(3, ((EuiccCardErrorException) resultCaptor.exception).getErrorCode());
         verifyStoreData(channel, "BF330C5A0A89670000000000452301");
@@ -429,7 +398,7 @@
 
         ResultCaptor<String> resultCaptor = new ResultCaptor<>();
         mEuiccCard.getDefaultSmdpAddress(resultCaptor, mHandler);
-        resultCaptor.await();
+        processAllMessages();
 
         assertEquals("SMDP.COM", resultCaptor.result);
         verifyStoreData(channel, "BF3C00");
@@ -442,7 +411,7 @@
 
         ResultCaptor<String> resultCaptor = new ResultCaptor<>();
         mEuiccCard.getSmdsAddress(resultCaptor, mHandler);
-        resultCaptor.await();
+        processAllMessages();
 
         assertEquals("smds.com", resultCaptor.result);
         verifyStoreData(channel, "BF3C00");
@@ -454,7 +423,7 @@
 
         ResultCaptor<Void> resultCaptor = new ResultCaptor<>();
         mEuiccCard.setDefaultSmdpAddress("smdp.gsma.com", resultCaptor, mHandler);
-        resultCaptor.await();
+        processAllMessages();
 
         assertUnexpectedException(resultCaptor.exception);
         verifyStoreData(channel, "BF3F0F800D736D64702E67736D612E636F6D");
@@ -479,7 +448,7 @@
 
         ResultCaptor<EuiccRulesAuthTable> resultCaptor = new ResultCaptor<>();
         mEuiccCard.getRulesAuthTable(resultCaptor, mHandler);
-        resultCaptor.await();
+        processAllMessages();
 
         EuiccRulesAuthTable rat = resultCaptor.result;
         assertEquals(-1,
@@ -502,7 +471,7 @@
         ResultCaptor<Void> resultCaptor = new ResultCaptor<>();
         mEuiccCard.resetMemory(EuiccCardManager.RESET_OPTION_DELETE_FIELD_LOADED_TEST_PROFILES,
                 resultCaptor, mHandler);
-        resultCaptor.await();
+        processAllMessages();
 
         assertUnexpectedException(resultCaptor.exception);
         verifyStoreData(channel, "BF340482020640");
@@ -515,7 +484,7 @@
         ResultCaptor<Void> resultCaptor = new ResultCaptor<>();
         mEuiccCard.resetMemory(EuiccCardManager.RESET_OPTION_DELETE_FIELD_LOADED_TEST_PROFILES,
                 resultCaptor, mHandler);
-        resultCaptor.await();
+        processAllMessages();
 
         assertUnexpectedException(resultCaptor.exception);
         verifyStoreData(channel, "BF340482020640");
@@ -527,7 +496,7 @@
 
         ResultCaptor<byte[]> resultCaptor = new ResultCaptor<>();
         mEuiccCard.getEuiccChallenge(resultCaptor, mHandler);
-        resultCaptor.await();
+        processAllMessages();
 
         assertArrayEquals(new byte[] {1, 2, 3}, resultCaptor.result);
         verifyStoreData(channel, "BF2E00");
@@ -539,7 +508,7 @@
 
         ResultCaptor<byte[]> resultCaptor = new ResultCaptor<>();
         mEuiccCard.getEuiccInfo1(resultCaptor, mHandler);
-        resultCaptor.await();
+        processAllMessages();
 
         assertEquals("BF2003010203", IccUtils.bytesToHexString(resultCaptor.result));
         verifyStoreData(channel, "BF2000");
@@ -551,7 +520,7 @@
 
         ResultCaptor<byte[]> resultCaptor = new ResultCaptor<>();
         mEuiccCard.getEuiccInfo2(resultCaptor, mHandler);
-        resultCaptor.await();
+        processAllMessages();
 
         assertEquals("BF2203010203", IccUtils.bytesToHexString(resultCaptor.result));
         verifyStoreData(channel, "BF2200");
@@ -571,7 +540,7 @@
                 Asn1Node.newBuilder(0xA1).build().toBytes(),
                 Asn1Node.newBuilder(0xA2).build().toBytes(),
                 Asn1Node.newBuilder(0xA3).build().toBytes(), resultCaptor, mHandler);
-        resultCaptor.await();
+        processAllMessages();
 
         assertUnexpectedException(resultCaptor.exception);
         assertEquals("BF3802A000", IccUtils.bytesToHexString(resultCaptor.result));
@@ -597,7 +566,7 @@
                 Asn1Node.newBuilder(0xA1).build().toBytes(),
                 Asn1Node.newBuilder(0xA2).build().toBytes(),
                 Asn1Node.newBuilder(0xA3).build().toBytes(), resultCaptor, mHandler);
-        resultCaptor.await();
+        processAllMessages();
 
         assertEquals(3, ((EuiccCardErrorException) resultCaptor.exception).getErrorCode());
         verifyStoreData(channel,
@@ -628,7 +597,7 @@
                 Asn1Node.newBuilder(0xA1).build().toBytes(),
                 Asn1Node.newBuilder(0xA2).build().toBytes(),
                 Asn1Node.newBuilder(0xA3).build().toBytes(), resultCaptor, mHandler);
-        resultCaptor.await();
+        processAllMessages();
 
         assertUnexpectedException(resultCaptor.exception);
         assertEquals("BF3802A000", IccUtils.bytesToHexString(resultCaptor.result));
@@ -651,7 +620,7 @@
                 Asn1Node.newBuilder(0xA0).build().toBytes(),
                 Asn1Node.newBuilder(0xA1).build().toBytes(),
                 Asn1Node.newBuilder(0xA2).build().toBytes(), resultCaptor, mHandler);
-        resultCaptor.await();
+        processAllMessages();
 
         assertEquals("BF2102A000", IccUtils.bytesToHexString(resultCaptor.result));
         verifyStoreData(channel,
@@ -670,7 +639,7 @@
                 Asn1Node.newBuilder(0xA0).build().toBytes(),
                 Asn1Node.newBuilder(0xA1).build().toBytes(),
                 Asn1Node.newBuilder(0xA2).build().toBytes(), resultCaptor, mHandler);
-        resultCaptor.await();
+        processAllMessages();
 
         assertEquals(3, ((EuiccCardErrorException) resultCaptor.exception).getErrorCode());
         verifyStoreData(channel,
@@ -713,7 +682,7 @@
                                 .addChildAsBytes(0x86, new byte[] {0xA, 0xB, 0xC}))
                         .build().toBytes(),
                 resultCaptor, mHandler);
-        resultCaptor.await();
+        processAllMessages();
 
         assertEquals("BF3700", IccUtils.bytesToHexString(resultCaptor.result));
         verifyStoreData(channel, "BF361FBF2300"); // ES8+.InitialiseSecureChannel
@@ -760,7 +729,7 @@
                                 .addChildAsBytes(0x86, new byte[] {0xA, 0xB, 0xC}))
                         .build().toBytes(),
                 resultCaptor, mHandler);
-        resultCaptor.await();
+        processAllMessages();
 
         assertEquals(3, ((EuiccCardErrorException) resultCaptor.exception).getErrorCode());
         verifyStoreData(channel, "BF361FBF2300"); // ES8+.InitialiseSecureChannel
@@ -807,7 +776,7 @@
                                 .addChildAsBytes(0x86, new byte[] {0xA, 0xB, 0xC}))
                         .build().toBytes(),
                 resultCaptor, mHandler);
-        resultCaptor.await();
+        processAllMessages();
 
         assertEquals(3, ((EuiccCardErrorException) resultCaptor.exception).getErrorCode());
         verifyStoreData(channel, "BF361FBF2300"); // ES8+.InitialiseSecureChannel
@@ -850,7 +819,7 @@
                                 .addChildAsBytes(0x86, new byte[] {0xA, 0xB, 0xC}))
                         .build().toBytes(),
                 resultCaptor, mHandler);
-        resultCaptor.await();
+        processAllMessages();
 
         EuiccCardException e = (EuiccCardException) resultCaptor.exception;
         assertEquals(0x6985, ((ApduException) e.getCause()).getApduStatus());
@@ -877,7 +846,7 @@
                         .addChild(Asn1Node.newBuilder(0xA3))
                         .build().toBytes(),
                 resultCaptor, mHandler);
-        resultCaptor.await();
+        processAllMessages();
 
         EuiccCardException e = (EuiccCardException) resultCaptor.exception;
         assertEquals("No profile elements in BPP", e.getCause().getMessage());
@@ -907,7 +876,7 @@
                         .addChild(Asn1Node.newBuilder(0xA4))
                         .build().toBytes(),
                 resultCaptor, mHandler);
-        resultCaptor.await();
+        processAllMessages();
 
         EuiccCardException e = (EuiccCardException) resultCaptor.exception;
         assertEquals(
@@ -927,7 +896,7 @@
         ResultCaptor<byte[]> resultCaptor = new ResultCaptor<>();
         mEuiccCard.cancelSession(IccUtils.hexStringToBytes("A1B2C3"),
                 EuiccCardManager.CANCEL_REASON_POSTPONED, resultCaptor, mHandler);
-        resultCaptor.await();
+        processAllMessages();
 
         assertEquals("BF4100", IccUtils.bytesToHexString(resultCaptor.result));
         verifyStoreData(channel, "BF41088003A1B2C3810101");
@@ -940,7 +909,7 @@
         ResultCaptor<byte[]> resultCaptor = new ResultCaptor<>();
         mEuiccCard.cancelSession(IccUtils.hexStringToBytes("A1B2C3"),
                 EuiccCardManager.CANCEL_REASON_POSTPONED, resultCaptor, mHandler);
-        resultCaptor.await();
+        processAllMessages();
 
         assertEquals(3, ((EuiccCardErrorException) resultCaptor.exception).getErrorCode());
         verifyStoreData(channel, "BF41088003A1B2C3810101");
@@ -957,7 +926,7 @@
         mEuiccCard.listNotifications(
                 EuiccNotification.EVENT_DELETE | EuiccNotification.EVENT_DISABLE,
                 resultCaptor, mHandler);
-        resultCaptor.await();
+        processAllMessages();
 
         assertArrayEquals(
                 new EuiccNotification[] {
@@ -976,7 +945,7 @@
         mEuiccCard.listNotifications(
                 EuiccNotification.EVENT_DELETE | EuiccNotification.EVENT_DISABLE,
                 resultCaptor, mHandler);
-        resultCaptor.await();
+        processAllMessages();
 
         assertEquals(3, ((EuiccCardErrorException) resultCaptor.exception).getErrorCode());
         verifyStoreData(channel, "BF280481020430");
@@ -995,7 +964,7 @@
         mEuiccCard.retrieveNotificationList(
                 EuiccNotification.EVENT_DELETE | EuiccNotification.EVENT_DISABLE,
                 resultCaptor, mHandler);
-        resultCaptor.await();
+        processAllMessages();
 
         assertArrayEquals(
                 new EuiccNotification[] {
@@ -1018,7 +987,7 @@
         mEuiccCard.retrieveNotificationList(
                 EuiccNotification.EVENT_DELETE | EuiccNotification.EVENT_DISABLE,
                 resultCaptor, mHandler);
-        resultCaptor.await();
+        processAllMessages();
 
         assertArrayEquals(new EuiccNotification[0], resultCaptor.result);
         verifyStoreData(channel, "BF2B06A00481020430");
@@ -1032,7 +1001,7 @@
         mEuiccCard.retrieveNotificationList(
                 EuiccNotification.EVENT_DELETE | EuiccNotification.EVENT_DISABLE,
                 resultCaptor, mHandler);
-        resultCaptor.await();
+        processAllMessages();
 
         assertEquals(3, ((EuiccCardErrorException) resultCaptor.exception).getErrorCode());
         verifyStoreData(channel, "BF2B06A00481020430");
@@ -1046,7 +1015,7 @@
 
         ResultCaptor<EuiccNotification> resultCaptor = new ResultCaptor<>();
         mEuiccCard.retrieveNotification(5, resultCaptor, mHandler);
-        resultCaptor.await();
+        processAllMessages();
 
         assertEquals(
                 new EuiccNotification(1, "smdp.com", EuiccNotification.EVENT_DELETE,
@@ -1061,7 +1030,7 @@
 
         ResultCaptor<EuiccNotification> resultCaptor = new ResultCaptor<>();
         mEuiccCard.retrieveNotification(5, resultCaptor, mHandler);
-        resultCaptor.await();
+        processAllMessages();
 
         assertEquals(1, ((EuiccCardErrorException) resultCaptor.exception).getErrorCode());
         verifyStoreData(channel, "BF2B05A003800105");
@@ -1073,7 +1042,7 @@
 
         ResultCaptor<Void> resultCaptor = new ResultCaptor<>();
         mEuiccCard.removeNotificationFromList(5, resultCaptor, mHandler);
-        resultCaptor.await();
+        processAllMessages();
 
         assertUnexpectedException(resultCaptor.exception);
         verifyStoreData(channel, "BF3003800105");
diff --git a/tests/telephonytests/src/com/android/internal/telephony/uicc/euicc/apdu/ApduSenderTest.java b/tests/telephonytests/src/com/android/internal/telephony/uicc/euicc/apdu/ApduSenderTest.java
index 7c102b1..4a8b842 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/uicc/euicc/apdu/ApduSenderTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/uicc/euicc/apdu/ApduSenderTest.java
@@ -19,7 +19,6 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.eq;
@@ -28,42 +27,30 @@
 import static org.mockito.Mockito.verify;
 
 import android.os.Handler;
-import android.os.HandlerThread;
+import android.os.Looper;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
 
 import com.android.internal.telephony.CommandException;
 import com.android.internal.telephony.CommandsInterface;
 import com.android.internal.telephony.uicc.IccIoResult;
 import com.android.internal.telephony.uicc.IccUtils;
 
-import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
+import org.junit.runner.RunWith;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper
 public class ApduSenderTest {
-    private static final long WAIT_TIMEOUT_MLLIS = 5000;
 
     private static class ResponseCaptor extends ApduSenderResultCallback {
         public byte[] response;
         public Throwable exception;
         public int stopApduIndex = -1;
 
-        private final CountDownLatch mLatch = new CountDownLatch(1);
-
-        public void await() {
-            try {
-                if (!mLatch.await(WAIT_TIMEOUT_MLLIS, TimeUnit.MILLISECONDS)) {
-                    fail("Execution timed out.");
-                }
-            } catch (InterruptedException e) {
-                fail("Execution interrupted: " + e);
-            }
-        }
-
         @Override
         public boolean shouldContinueOnIntermediateResult(IccIoResult result) {
             if (stopApduIndex < 0) {
@@ -79,90 +66,76 @@
         @Override
         public void onResult(byte[] bytes) {
             response = bytes;
-            mLatch.countDown();
         }
 
         @Override
         public void onException(Throwable e) {
             exception = e;
-            mLatch.countDown();
         }
     }
 
     @Mock
     private CommandsInterface mMockCi;
 
-    private HandlerThread mThread;
+    private TestableLooper mLooper;
     private Handler mHandler;
     private ResponseCaptor mResponseCaptor;
     private byte[] mSelectResponse;
+    private static final String AID = "B2C3D4";
+    private ApduSender mSender;
 
     @Before
     public void setUp() {
         MockitoAnnotations.initMocks(this);
-
-        mThread = new HandlerThread("ApduSenderTest");
-        mThread.start();
-        mHandler = mThread.getThreadHandler();
+        mHandler = new Handler(Looper.myLooper());
 
         mResponseCaptor = new ResponseCaptor();
         mSelectResponse = null;
-    }
 
-    @After
-    public void tearDown() {
-        mThread.quit();
+        mSender = new ApduSender(mMockCi, AID, false /* supportExtendedApdu */);
+        mLooper = TestableLooper.get(this);
     }
 
     @Test
     public void testSendEmptyCommands() throws InterruptedException {
-        String aid = "B2C3D4";
-        ApduSender sender = new ApduSender(mMockCi, aid, false /* supportExtendedApdu */);
-
         int channel = LogicalChannelMocker.mockOpenLogicalChannelResponse(mMockCi, "A1A1A19000");
         LogicalChannelMocker.mockCloseLogicalChannel(mMockCi, channel);
 
-        sender.send((selectResponse, requestBuilder) -> mSelectResponse = selectResponse,
+        mSender.send((selectResponse, requestBuilder) -> mSelectResponse = selectResponse,
                 mResponseCaptor, mHandler);
-        mResponseCaptor.await();
+        mLooper.processAllMessages();
 
         assertEquals("A1A1A19000", IccUtils.bytesToHexString(mSelectResponse));
         assertNull(mResponseCaptor.response);
         assertNull(mResponseCaptor.exception);
-        verify(mMockCi).iccOpenLogicalChannel(eq(aid), anyInt(), any());
+        verify(mMockCi).iccOpenLogicalChannel(eq(AID), anyInt(), any());
         verify(mMockCi).iccCloseLogicalChannel(eq(channel), any());
     }
 
     @Test
     public void testOpenChannelErrorStatus() throws InterruptedException {
-        String aid = "B2C3D4";
-        ApduSender sender = new ApduSender(mMockCi, aid, false /* supportExtendedApdu */);
-
         LogicalChannelMocker.mockOpenLogicalChannelResponse(mMockCi,
                 new CommandException(CommandException.Error.NO_SUCH_ELEMENT));
 
-        sender.send((selectResponse, requestBuilder) -> mSelectResponse = new byte[0],
+        mSender.send((selectResponse, requestBuilder) -> mSelectResponse = new byte[0],
                 mResponseCaptor, mHandler);
-        mResponseCaptor.await();
+        mLooper.processAllMessages();
 
         assertNull("Request provider should not be called when failed to open channel.",
                 mSelectResponse);
         assertTrue(mResponseCaptor.exception instanceof ApduException);
-        verify(mMockCi).iccOpenLogicalChannel(eq(aid), anyInt(), any());
+        verify(mMockCi).iccOpenLogicalChannel(eq(AID), anyInt(), any());
     }
 
     @Test
     public void testSend() throws InterruptedException {
-        String aid = "B2C3D4";
-        ApduSender sender = new ApduSender(mMockCi, aid, false /* supportExtendedApdu */);
-
         int channel = LogicalChannelMocker.mockOpenLogicalChannelResponse(mMockCi, "9000");
         LogicalChannelMocker.mockSendToLogicalChannel(mMockCi, channel, "A1A1A19000");
         LogicalChannelMocker.mockCloseLogicalChannel(mMockCi, channel);
 
-        sender.send((selectResponse, requestBuilder) -> requestBuilder.addApdu(10, 1, 2, 3, 0, "a"),
-                mResponseCaptor, mHandler);
-        mResponseCaptor.await();
+        mSender.send((selectResponse, requestBuilder) -> requestBuilder.addApdu(
+                10, 1, 2, 3, 0, "a"), mResponseCaptor, mHandler);
+        mLooper.processAllMessages();
 
         assertEquals("A1A1A1", IccUtils.bytesToHexString(mResponseCaptor.response));
         verify(mMockCi).iccTransmitApduLogicalChannel(eq(channel), eq(channel | 10), eq(1), eq(2),
@@ -171,21 +144,18 @@
 
     @Test
     public void testSendMultiApdus() throws InterruptedException {
-        String aid = "B2C3D4";
-        ApduSender sender = new ApduSender(mMockCi, aid, false /* supportExtendedApdu */);
-
         int channel = LogicalChannelMocker.mockOpenLogicalChannelResponse(mMockCi, "9000");
         LogicalChannelMocker.mockSendToLogicalChannel(mMockCi, channel, "A19000", "A29000",
                 "A39000", "A49000");
         LogicalChannelMocker.mockCloseLogicalChannel(mMockCi, channel);
 
-        sender.send((selectResponse, requestBuilder) -> {
+        mSender.send((selectResponse, requestBuilder) -> {
             requestBuilder.addApdu(10, 1, 2, 3, 0, "a");
             requestBuilder.addApdu(10, 1, 2, 3, "ab");
             requestBuilder.addApdu(10, 1, 2, 3);
             requestBuilder.addStoreData("abcd");
         }, mResponseCaptor, mHandler);
-        mResponseCaptor.await();
+        mLooper.processAllMessages();
 
         assertEquals("A4", IccUtils.bytesToHexString(mResponseCaptor.response));
         verify(mMockCi).iccTransmitApduLogicalChannel(eq(channel), eq(channel | 10), eq(1), eq(2),
@@ -200,22 +170,19 @@
 
     @Test
     public void testSendMultiApdusStopEarly() throws InterruptedException {
-        String aid = "B2C3D4";
-        ApduSender sender = new ApduSender(mMockCi, aid, false /* supportExtendedApdu */);
-
         int channel = LogicalChannelMocker.mockOpenLogicalChannelResponse(mMockCi, "9000");
         LogicalChannelMocker.mockSendToLogicalChannel(mMockCi, channel, "A19000", "A29000",
                 "A39000", "A49000");
         LogicalChannelMocker.mockCloseLogicalChannel(mMockCi, channel);
         mResponseCaptor.stopApduIndex = 2;
 
-        sender.send((selectResponse, requestBuilder) -> {
+        mSender.send((selectResponse, requestBuilder) -> {
             requestBuilder.addApdu(10, 1, 2, 3, 0, "a");
             requestBuilder.addApdu(10, 1, 2, 3, "ab");
             requestBuilder.addApdu(10, 1, 2, 3);
             requestBuilder.addStoreData("abcd");
         }, mResponseCaptor, mHandler);
-        mResponseCaptor.await();
+        mLooper.processAllMessages();
 
         assertEquals("A3", IccUtils.bytesToHexString(mResponseCaptor.response));
         verify(mMockCi).iccTransmitApduLogicalChannel(eq(channel), eq(channel | 10), eq(1), eq(2),
@@ -228,17 +195,14 @@
 
     @Test
     public void testSendLongResponse() throws InterruptedException {
-        String aid = "B2C3D4";
-        ApduSender sender = new ApduSender(mMockCi, aid, false /* supportExtendedApdu */);
-
         int channel = LogicalChannelMocker.mockOpenLogicalChannelResponse(mMockCi, "9000");
         LogicalChannelMocker.mockSendToLogicalChannel(mMockCi, channel, "A1A1A16104",
                 "B2B2B2B26102", "C3C39000");
         LogicalChannelMocker.mockCloseLogicalChannel(mMockCi, channel);
 
-        sender.send((selectResponse, requestBuilder) -> requestBuilder.addApdu(10, 1, 2, 3, 0, "a"),
-                mResponseCaptor, mHandler);
-        mResponseCaptor.await();
+        mSender.send((selectResponse, requestBuilder) -> requestBuilder.addApdu(
+                10, 1, 2, 3, 0, "a"), mResponseCaptor, mHandler);
+        mLooper.processAllMessages();
 
         assertEquals("A1A1A1B2B2B2B2C3C3", IccUtils.bytesToHexString(mResponseCaptor.response));
         verify(mMockCi).iccTransmitApduLogicalChannel(eq(channel), eq(channel | 10), eq(1), eq(2),
@@ -251,9 +215,6 @@
 
     @Test
     public void testSendStoreDataLongDataLongResponse() throws InterruptedException {
-        String aid = "B2C3D4";
-        ApduSender sender = new ApduSender(mMockCi, aid, false /* supportExtendedApdu */);
-
         int channel = LogicalChannelMocker.mockOpenLogicalChannelResponse(mMockCi, "9000");
         LogicalChannelMocker.mockSendToLogicalChannel(mMockCi, channel, "A19000", "9000", "9000",
                 "B22B6103", "B2222B9000", "C39000");
@@ -264,12 +225,12 @@
         String s2 = new String(new char[0xFF]).replace("\0", "BB");
         String s3 = new String(new char[16]).replace("\0", "CC");
         String longData = s1 + s2 + s3;
-        sender.send((selectResponse, requestBuilder) -> {
+        mSender.send((selectResponse, requestBuilder) -> {
             requestBuilder.addApdu(10, 1, 2, 3, 0, "a");
             requestBuilder.addApdu(10, 1, 2, 3, 0, "b");
             requestBuilder.addStoreData(longData);
         }, mResponseCaptor, mHandler);
-        mResponseCaptor.await();
+        mLooper.processAllMessages();
 
         assertEquals("C3", IccUtils.bytesToHexString(mResponseCaptor.response));
         verify(mMockCi).iccTransmitApduLogicalChannel(eq(channel), eq(channel | 10), eq(1), eq(2),
@@ -286,9 +247,6 @@
 
     @Test
     public void testSendStoreDataLongDataMod0() throws InterruptedException {
-        String aid = "B2C3D4";
-        ApduSender sender = new ApduSender(mMockCi, aid, false /* supportExtendedApdu */);
-
         int channel = LogicalChannelMocker.mockOpenLogicalChannelResponse(mMockCi, "9000");
         LogicalChannelMocker.mockSendToLogicalChannel(mMockCi, channel, "9000", "B2222B9000");
         LogicalChannelMocker.mockCloseLogicalChannel(mMockCi, channel);
@@ -297,10 +255,10 @@
         String s1 = new String(new char[0xFF]).replace("\0", "AA");
         String s2 = new String(new char[0xFF]).replace("\0", "BB");
         String longData = s1 + s2;
-        sender.send((selectResponse, requestBuilder) -> {
+        mSender.send((selectResponse, requestBuilder) -> {
             requestBuilder.addStoreData(longData);
         }, mResponseCaptor, mHandler);
-        mResponseCaptor.await();
+        mLooper.processAllMessages();
 
         assertEquals("B2222B", IccUtils.bytesToHexString(mResponseCaptor.response));
         verify(mMockCi).iccTransmitApduLogicalChannel(eq(channel), eq(0x81), eq(0xE2), eq(0x11),
@@ -311,17 +269,14 @@
 
     @Test
     public void testSendStoreDataLen0() throws InterruptedException {
-        String aid = "B2C3D4";
-        ApduSender sender = new ApduSender(mMockCi, aid, false /* supportExtendedApdu */);
-
         int channel = LogicalChannelMocker.mockOpenLogicalChannelResponse(mMockCi, "9000");
         LogicalChannelMocker.mockSendToLogicalChannel(mMockCi, channel, "B2222B9000");
         LogicalChannelMocker.mockCloseLogicalChannel(mMockCi, channel);
 
-        sender.send((selectResponse, requestBuilder) -> {
+        mSender.send((selectResponse, requestBuilder) -> {
             requestBuilder.addStoreData("");
         }, mResponseCaptor, mHandler);
-        mResponseCaptor.await();
+        mLooper.processAllMessages();
 
         assertEquals("B2222B", IccUtils.bytesToHexString(mResponseCaptor.response));
         verify(mMockCi).iccTransmitApduLogicalChannel(eq(channel), eq(0x81), eq(0xE2), eq(0x91),
@@ -330,9 +285,6 @@
 
     @Test
     public void testSendErrorResponseInMiddle() throws InterruptedException {
-        String aid = "B2C3D4";
-        ApduSender sender = new ApduSender(mMockCi, aid, false /* supportExtendedApdu */);
-
         int channel = LogicalChannelMocker.mockOpenLogicalChannelResponse(mMockCi, "9000");
         LogicalChannelMocker.mockSendToLogicalChannel(mMockCi, channel, "A19000", "9000",
                 "B22B6103", "6985");
@@ -343,11 +295,11 @@
         String s2 = new String(new char[0xFF]).replace("\0", "BB");
         String s3 = new String(new char[16]).replace("\0", "CC");
         String longData = s1 + s2 + s3;
-        sender.send((selectResponse, requestBuilder) -> {
+        mSender.send((selectResponse, requestBuilder) -> {
             requestBuilder.addApdu(10, 1, 2, 3, 0, "a");
             requestBuilder.addStoreData(longData);
         }, mResponseCaptor, mHandler);
-        mResponseCaptor.await();
+        mLooper.processAllMessages();
 
         assertEquals(0x6985, ((ApduException) mResponseCaptor.exception).getApduStatus());
         verify(mMockCi).iccTransmitApduLogicalChannel(eq(channel), eq(channel | 10), eq(1), eq(2),
@@ -362,24 +314,20 @@
 
     @Test
     public void testChannelAlreadyOpened() throws InterruptedException {
-        String aid = "B2C3D4";
-        ApduSender sender = new ApduSender(mMockCi, aid, false /* supportExtendedApdu */);
-
         int channel = LogicalChannelMocker.mockOpenLogicalChannelResponse(mMockCi, "9000");
         LogicalChannelMocker.mockCloseLogicalChannel(mMockCi, channel);
 
         ResponseCaptor outerResponseCaptor = new ResponseCaptor();
-        sender.send(
-                (selectResponse, requestBuilder) -> sender.send(
+        mSender.send(
+                (selectResponse, requestBuilder) -> mSender.send(
                         (selectResponseOther, requestBuilderOther) ->
                                 mSelectResponse = selectResponseOther,
                         mResponseCaptor, mHandler),
                 outerResponseCaptor, mHandler);
-        mResponseCaptor.await();
-        outerResponseCaptor.await();
+        mLooper.processAllMessages();
 
         assertNull("Should not open channel when another one is already opened.", mSelectResponse);
         assertTrue(mResponseCaptor.exception instanceof ApduException);
-        verify(mMockCi, times(1)).iccOpenLogicalChannel(eq(aid), anyInt(), any());
+        verify(mMockCi, times(1)).iccOpenLogicalChannel(eq(AID), anyInt(), any());
     }
 }