Merge "Cleanup voip-common API usage in Telephony."
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 81072b5..e83674b 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -636,5 +636,33 @@
                 <action android:name="android.telephony.data.DataService" />
             </intent-filter>
         </service>
+
+        <provider
+            android:name="ServiceStateProvider"
+            android:authorities="service-state"
+            android:exported="true"
+            android:multiprocess="false"
+            android:singleUser="true"
+            android:writePermission="android.permission.MODIFY_PHONE_STATE"/>
+
+        <activity
+            android:name=".settings.RadioInfo"
+            android:label="@string/phone_info_label"
+            android:theme="@style/Theme.AppCompat.DayNight">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.DEVELOPMENT_PREFERENCE" />
+            </intent-filter>
+        </activity>
+
+        <activity android:name=".settings.BandMode"
+                  android:label="@string/band_mode_title"
+                  android:theme="@style/Theme.AppCompat.DayNight">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.DEFAULT" />
+                <category android:name="android.intent.category.VOICE_LAUNCH" />
+            </intent-filter>
+        </activity>
     </application>
 </manifest>
diff --git a/res/values/config.xml b/res/values/config.xml
index 8c36b1a..6906089 100644
--- a/res/values/config.xml
+++ b/res/values/config.xml
@@ -257,6 +257,14 @@
          audio stream which the remote party will be able to hear. -->
     <bool name="config_support_telephony_audio_device">false</bool>
 
+    <string-array translatable="false" name="config_volte_provision_error_on_publish_response">
+        <item>403 not authorized for presence</item>
+    </string-array>
+
+    <string-array translatable="false" name="config_rcs_provision_error_on_publish_response">
+        <item>404 not found</item>
+    </string-array>
+
     <!-- The country list that shortcut view can be enabled. -->
     <string-array name="config_countries_to_enable_shortcut_view" translatable="false">
     </string-array>
diff --git a/sip/src/com/android/services/telephony/sip/SipUtil.java b/sip/src/com/android/services/telephony/sip/SipUtil.java
index a88b973..828174e 100644
--- a/sip/src/com/android/services/telephony/sip/SipUtil.java
+++ b/sip/src/com/android/services/telephony/sip/SipUtil.java
@@ -29,6 +29,7 @@
 import android.telecom.PhoneAccount;
 import android.telecom.PhoneAccountHandle;
 import android.telecom.TelecomManager;
+import android.telephony.TelephonyManager;
 import android.text.TextUtils;
 import android.util.Log;
 
@@ -52,9 +53,9 @@
     public static boolean isVoipSupported(Context context) {
         return SipManager.isVoipSupported(context) &&
                 context.getResources().getBoolean(
-                        com.android.internal.R.bool.config_built_in_sip_phone) &&
-                context.getResources().getBoolean(
-                        com.android.internal.R.bool.config_voice_capable);
+                        com.android.internal.R.bool.config_built_in_sip_phone)
+                && ((TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE))
+                        .isVoiceCapable();
     }
 
     static PendingIntent createIncomingCallPendingIntent(
diff --git a/src/com/android/phone/ImsRcsController.java b/src/com/android/phone/ImsRcsController.java
index 1a28afd..9d9010a 100644
--- a/src/com/android/phone/ImsRcsController.java
+++ b/src/com/android/phone/ImsRcsController.java
@@ -37,6 +37,7 @@
 import com.android.internal.telephony.IIntegerConsumer;
 import com.android.internal.telephony.Phone;
 import com.android.internal.telephony.imsphone.ImsPhone;
+import com.android.services.telephony.rcs.TelephonyRcsService;
 
 import java.util.List;
 
@@ -50,6 +51,7 @@
     private static ImsRcsController sInstance;
 
     private PhoneGlobals mApp;
+    private TelephonyRcsService mRcsService;
 
     /**
      * Initialize the singleton ImsRcsController instance.
@@ -339,4 +341,8 @@
         }
         return rcsFeatureManager;
     }
+
+    void setRcsService(TelephonyRcsService rcsService) {
+        mRcsService = rcsService;
+    }
 }
diff --git a/src/com/android/phone/NumberVerificationManager.java b/src/com/android/phone/NumberVerificationManager.java
index 9ec16f8..2298d40 100644
--- a/src/com/android/phone/NumberVerificationManager.java
+++ b/src/com/android/phone/NumberVerificationManager.java
@@ -135,7 +135,7 @@
                 }
 
                 // make sure at least one phone is registered for voice
-                if (phone.getServiceState().getVoiceRegState() == ServiceState.STATE_IN_SERVICE) {
+                if (phone.getServiceState().getState() == ServiceState.STATE_IN_SERVICE) {
                     isAnyPhoneVoiceRegistered = true;
                 }
                 // make sure at least one phone has room for an incoming call.
diff --git a/src/com/android/phone/PhoneGlobals.java b/src/com/android/phone/PhoneGlobals.java
index a007bd9..b0e0105 100644
--- a/src/com/android/phone/PhoneGlobals.java
+++ b/src/com/android/phone/PhoneGlobals.java
@@ -71,6 +71,7 @@
 import com.android.internal.util.IndentingPrintWriter;
 import com.android.phone.settings.SettingsConstants;
 import com.android.phone.vvm.CarrierVvmPackageInstalledReceiver;
+import com.android.services.telephony.rcs.TelephonyRcsService;
 import com.android.services.telephony.sip.SipAccountRegistry;
 import com.android.services.telephony.sip.SipUtil;
 
@@ -149,6 +150,7 @@
     CallerInfoCache callerInfoCache;
     NotificationMgr notificationMgr;
     ImsResolver mImsResolver;
+    TelephonyRcsService mTelephonyRcsService;
     public PhoneInterfaceManager phoneMgr;
     public ImsRcsController imsRcsController;
     CarrierConfigLoader configLoader;
@@ -303,8 +305,8 @@
         // Cache the "voice capable" flag.
         // This flag currently comes from a resource (which is
         // overrideable on a per-product basis):
-        sVoiceCapable =
-                getResources().getBoolean(com.android.internal.R.bool.config_voice_capable);
+        sVoiceCapable = ((TelephonyManager) getSystemService(Context.TELEPHONY_SERVICE))
+                .isVoiceCapable();
         // ...but this might eventually become a PackageManager "system
         // feature" instead, in which case we'd do something like:
         // sVoiceCapable =
@@ -372,6 +374,11 @@
 
             imsRcsController = ImsRcsController.init(this);
 
+            if (getPackageManager().hasSystemFeature(PackageManager.FEATURE_TELEPHONY_IMS)) {
+                mTelephonyRcsService = new TelephonyRcsService(this);
+                imsRcsController.setRcsService(mTelephonyRcsService);
+            }
+
             configLoader = CarrierConfigLoader.init(this);
 
             // Create the CallNotifier singleton, which handles
diff --git a/src/com/android/phone/PhoneInterfaceManager.java b/src/com/android/phone/PhoneInterfaceManager.java
index 7303c7f..2f0add1 100755
--- a/src/com/android/phone/PhoneInterfaceManager.java
+++ b/src/com/android/phone/PhoneInterfaceManager.java
@@ -84,7 +84,6 @@
 import android.telephony.PhoneNumberRange;
 import android.telephony.RadioAccessFamily;
 import android.telephony.RadioAccessSpecifier;
-import com.android.telephony.Rlog;
 import android.telephony.ServiceState;
 import android.telephony.SignalStrength;
 import android.telephony.SubscriptionInfo;
@@ -174,6 +173,7 @@
 import com.android.phone.vvm.RemoteVvmTaskManager;
 import com.android.phone.vvm.VisualVoicemailSettingsUtil;
 import com.android.phone.vvm.VisualVoicemailSmsFilterConfig;
+import com.android.telephony.Rlog;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
@@ -1454,6 +1454,16 @@
         }
     }
 
+    private boolean isImsAvailableOnDevice() {
+        PackageManager pm = getDefaultPhone().getContext().getPackageManager();
+        if (pm == null) {
+            // For some reason package manger is not available.. This will fail internally anyway,
+            // so do not throw error and allow.
+            return true;
+        }
+        return pm.hasSystemFeature(PackageManager.FEATURE_TELEPHONY_IMS, 0);
+    }
+
     public void dial(String number) {
         dialForSubscriber(getPreferredVoiceSubscription(), number);
     }
@@ -3420,6 +3430,10 @@
         enforceReadPrivilegedPermission("registerImsProvisioningChangedCallback");
         final long identity = Binder.clearCallingIdentity();
         try {
+            if (!isImsAvailableOnDevice()) {
+                throw new ServiceSpecificException(ImsException.CODE_ERROR_UNSUPPORTED_OPERATION,
+                        "IMS not available on device.");
+            }
             // TODO: Refactor to remove ImsManager dependence and query through ImsPhone directly.
             ImsManager.getInstance(mApp, getSlotIndexOrException(subId))
                     .addProvisioningCallbackForSubscription(callback, subId);
@@ -5855,7 +5869,12 @@
     public boolean isRttEnabled(int subscriptionId) {
         final long identity = Binder.clearCallingIdentity();
         try {
-            return isRttSupported(subscriptionId);
+            boolean isRttSupported = isRttSupported(subscriptionId);
+            boolean isUserRttSettingOn = Settings.Secure.getInt(
+                    mApp.getContentResolver(), Settings.Secure.RTT_CALLING_MODE, 0) != 0;
+            boolean shouldIgnoreUserRttSetting = mApp.getCarrierConfigForSubId(subscriptionId)
+                    .getBoolean(CarrierConfigManager.KEY_IGNORE_RTT_MODE_SETTING_BOOL);
+            return isRttSupported && (isUserRttSettingOn || shouldIgnoreUserRttSetting);
         } finally {
             Binder.restoreCallingIdentity(identity);
         }
diff --git a/src/com/android/phone/ServiceStateProvider.java b/src/com/android/phone/ServiceStateProvider.java
new file mode 100644
index 0000000..9e6ea83
--- /dev/null
+++ b/src/com/android/phone/ServiceStateProvider.java
@@ -0,0 +1,365 @@
+/*
+ * 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.phone;
+
+import static android.provider.Telephony.ServiceStateTable;
+import static android.provider.Telephony.ServiceStateTable.CDMA_DEFAULT_ROAMING_INDICATOR;
+import static android.provider.Telephony.ServiceStateTable.CDMA_ERI_ICON_INDEX;
+import static android.provider.Telephony.ServiceStateTable.CDMA_ERI_ICON_MODE;
+import static android.provider.Telephony.ServiceStateTable.CDMA_ROAMING_INDICATOR;
+import static android.provider.Telephony.ServiceStateTable.CONTENT_URI;
+import static android.provider.Telephony.ServiceStateTable.CSS_INDICATOR;
+import static android.provider.Telephony.ServiceStateTable.DATA_OPERATOR_ALPHA_LONG;
+import static android.provider.Telephony.ServiceStateTable.DATA_OPERATOR_ALPHA_SHORT;
+import static android.provider.Telephony.ServiceStateTable.DATA_OPERATOR_NUMERIC;
+import static android.provider.Telephony.ServiceStateTable.DATA_REG_STATE;
+import static android.provider.Telephony.ServiceStateTable.DATA_ROAMING_TYPE;
+import static android.provider.Telephony.ServiceStateTable.IS_EMERGENCY_ONLY;
+import static android.provider.Telephony.ServiceStateTable.IS_MANUAL_NETWORK_SELECTION;
+import static android.provider.Telephony.ServiceStateTable.IS_USING_CARRIER_AGGREGATION;
+import static android.provider.Telephony.ServiceStateTable.NETWORK_ID;
+import static android.provider.Telephony.ServiceStateTable.OPERATOR_ALPHA_LONG_RAW;
+import static android.provider.Telephony.ServiceStateTable.OPERATOR_ALPHA_SHORT_RAW;
+import static android.provider.Telephony.ServiceStateTable.RIL_DATA_RADIO_TECHNOLOGY;
+import static android.provider.Telephony.ServiceStateTable.RIL_VOICE_RADIO_TECHNOLOGY;
+import static android.provider.Telephony.ServiceStateTable.SERVICE_STATE;
+import static android.provider.Telephony.ServiceStateTable.SYSTEM_ID;
+import static android.provider.Telephony.ServiceStateTable.VOICE_OPERATOR_ALPHA_LONG;
+import static android.provider.Telephony.ServiceStateTable.VOICE_OPERATOR_ALPHA_SHORT;
+import static android.provider.Telephony.ServiceStateTable.VOICE_OPERATOR_NUMERIC;
+import static android.provider.Telephony.ServiceStateTable.VOICE_REG_STATE;
+import static android.provider.Telephony.ServiceStateTable.VOICE_ROAMING_TYPE;
+import static android.provider.Telephony.ServiceStateTable.getUriForSubscriptionId;
+import static android.provider.Telephony.ServiceStateTable.getUriForSubscriptionIdAndField;
+
+import android.content.ContentProvider;
+import android.content.ContentValues;
+import android.content.Context;
+import android.database.Cursor;
+import android.database.MatrixCursor;
+import android.database.MatrixCursor.RowBuilder;
+import android.net.Uri;
+import android.os.Parcel;
+import android.telephony.ServiceState;
+import android.telephony.SubscriptionManager;
+import android.util.Log;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.util.HashMap;
+
+/**
+ * The class to provide base facility to access ServiceState related content,
+ * which is stored in a SQLite database.
+ */
+public class ServiceStateProvider extends ContentProvider {
+    private static final String TAG = "ServiceStateProvider";
+
+    public static final String AUTHORITY = ServiceStateTable.AUTHORITY;
+    public static final Uri AUTHORITY_URI = Uri.parse("content://" + AUTHORITY);
+
+    private final HashMap<Integer, ServiceState> mServiceStates = new HashMap<>();
+    private static final String[] sColumns = {
+        VOICE_REG_STATE,
+        DATA_REG_STATE,
+        VOICE_ROAMING_TYPE,
+        DATA_ROAMING_TYPE,
+        VOICE_OPERATOR_ALPHA_LONG,
+        VOICE_OPERATOR_ALPHA_SHORT,
+        VOICE_OPERATOR_NUMERIC,
+        DATA_OPERATOR_ALPHA_LONG,
+        DATA_OPERATOR_ALPHA_SHORT,
+        DATA_OPERATOR_NUMERIC,
+        IS_MANUAL_NETWORK_SELECTION,
+        RIL_VOICE_RADIO_TECHNOLOGY,
+        RIL_DATA_RADIO_TECHNOLOGY,
+        CSS_INDICATOR,
+        NETWORK_ID,
+        SYSTEM_ID,
+        CDMA_ROAMING_INDICATOR,
+        CDMA_DEFAULT_ROAMING_INDICATOR,
+        CDMA_ERI_ICON_INDEX,
+        CDMA_ERI_ICON_MODE,
+        IS_EMERGENCY_ONLY,
+        IS_USING_CARRIER_AGGREGATION,
+        OPERATOR_ALPHA_LONG_RAW,
+        OPERATOR_ALPHA_SHORT_RAW,
+    };
+
+    @Override
+    public boolean onCreate() {
+        return true;
+    }
+
+    /**
+     * Returns the {@link ServiceState} information on specified subscription.
+     *
+     * @param subId whose subscriber id is returned
+     * @return the {@link ServiceState} information on specified subscription.
+     */
+    @VisibleForTesting
+    public ServiceState getServiceState(int subId) {
+        return mServiceStates.get(subId);
+    }
+
+    /**
+     * Returns the system's default subscription id.
+     *
+     * @return the "system" default subscription id.
+     */
+    @VisibleForTesting
+    public int getDefaultSubId() {
+        return SubscriptionManager.getDefaultSubscriptionId();
+    }
+
+    @Override
+    public Uri insert(Uri uri, ContentValues values) {
+        if (uri.isPathPrefixMatch(CONTENT_URI)) {
+            // Parse the subId
+            int subId = 0;
+            try {
+                subId = Integer.parseInt(uri.getLastPathSegment());
+            } catch (NumberFormatException e) {
+                Log.e(TAG, "insert: no subId provided in uri");
+                throw e;
+            }
+            Log.d(TAG, "subId=" + subId);
+
+            // handle DEFAULT_SUBSCRIPTION_ID
+            if (subId == SubscriptionManager.DEFAULT_SUBSCRIPTION_ID) {
+                subId = getDefaultSubId();
+            }
+
+            final Parcel p = Parcel.obtain();
+            final byte[] rawBytes = values.getAsByteArray(SERVICE_STATE);
+            p.unmarshall(rawBytes, 0, rawBytes.length);
+            p.setDataPosition(0);
+
+            // create the new service state
+            final ServiceState newSS = ServiceState.CREATOR.createFromParcel(p);
+
+            // notify listeners
+            // if ss is null (e.g. first service state update) we will notify for all fields
+            ServiceState ss = getServiceState(subId);
+            notifyChangeForSubIdAndField(getContext(), ss, newSS, subId);
+            notifyChangeForSubId(getContext(), ss, newSS, subId);
+
+            // store the new service state
+            mServiceStates.put(subId, newSS);
+            return uri;
+        }
+        return null;
+    }
+
+    @Override
+    public int delete(Uri uri, String selection, String[] selectionArgs) {
+        throw new RuntimeException("Not supported");
+    }
+
+    @Override
+    public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
+        throw new RuntimeException("Not supported");
+    }
+
+    @Override
+    public String getType(Uri uri) {
+        throw new RuntimeException("Not supported");
+    }
+
+    @Override
+    public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
+            String sortOrder) {
+        if (!uri.isPathPrefixMatch(CONTENT_URI)) {
+            throw new IllegalArgumentException("Invalid URI: " + uri);
+        } else {
+            // Parse the subId
+            int subId = 0;
+            try {
+                subId = Integer.parseInt(uri.getLastPathSegment());
+            } catch (NumberFormatException e) {
+                Log.d(TAG, "query: no subId provided in uri, using default.");
+                subId = getDefaultSubId();
+            }
+            Log.d(TAG, "subId=" + subId);
+
+            // handle DEFAULT_SUBSCRIPTION_ID
+            if (subId == SubscriptionManager.DEFAULT_SUBSCRIPTION_ID) {
+                subId = getDefaultSubId();
+            }
+
+            // Get the service state
+            ServiceState ss = getServiceState(subId);
+            if (ss == null) {
+                Log.d(TAG, "returning null");
+                return null;
+            }
+
+            // Build the result
+            final int voice_reg_state = ss.getState();
+            final int data_reg_state = ss.getDataRegistrationState();
+            final int voice_roaming_type = ss.getVoiceRoamingType();
+            final int data_roaming_type = ss.getDataRoamingType();
+            final String voice_operator_alpha_long = ss.getOperatorAlphaLong();
+            final String voice_operator_alpha_short = ss.getOperatorAlphaShort();
+            final String voice_operator_numeric = ss.getOperatorNumeric();
+            final String data_operator_alpha_long = ss.getOperatorAlphaLong();
+            final String data_operator_alpha_short = ss.getOperatorAlphaShort();
+            final String data_operator_numeric = ss.getOperatorNumeric();
+            final int is_manual_network_selection = (ss.getIsManualSelection()) ? 1 : 0;
+            final int ril_voice_radio_technology = ss.getRilVoiceRadioTechnology();
+            final int ril_data_radio_technology = ss.getRilDataRadioTechnology();
+            final int css_indicator = ss.getCssIndicator();
+            final int network_id = ss.getCdmaNetworkId();
+            final int system_id = ss.getCdmaSystemId();
+            final int cdma_roaming_indicator = ss.getCdmaRoamingIndicator();
+            final int cdma_default_roaming_indicator = ss.getCdmaDefaultRoamingIndicator();
+            final int cdma_eri_icon_index = ss.getCdmaEriIconIndex();
+            final int cdma_eri_icon_mode = ss.getCdmaEriIconMode();
+            final int is_emergency_only = (ss.isEmergencyOnly()) ? 1 : 0;
+            final int is_using_carrier_aggregation = (ss.isUsingCarrierAggregation()) ? 1 : 0;
+            final String operator_alpha_long_raw = ss.getOperatorAlphaLongRaw();
+            final String operator_alpha_short_raw = ss.getOperatorAlphaShortRaw();
+
+            return buildSingleRowResult(projection, sColumns, new Object[] {
+                    voice_reg_state,
+                    data_reg_state,
+                    voice_roaming_type,
+                    data_roaming_type,
+                    voice_operator_alpha_long,
+                    voice_operator_alpha_short,
+                    voice_operator_numeric,
+                    data_operator_alpha_long,
+                    data_operator_alpha_short,
+                    data_operator_numeric,
+                    is_manual_network_selection,
+                    ril_voice_radio_technology,
+                    ril_data_radio_technology,
+                    css_indicator,
+                    network_id,
+                    system_id,
+                    cdma_roaming_indicator,
+                    cdma_default_roaming_indicator,
+                    cdma_eri_icon_index,
+                    cdma_eri_icon_mode,
+                    is_emergency_only,
+                    is_using_carrier_aggregation,
+                    operator_alpha_long_raw,
+                    operator_alpha_short_raw,
+            });
+        }
+    }
+
+    private static Cursor buildSingleRowResult(String[] projection, String[] availableColumns,
+            Object[] data) {
+        if (projection == null) {
+            projection = availableColumns;
+        }
+        final MatrixCursor c = new MatrixCursor(projection, 1);
+        final RowBuilder row = c.newRow();
+        for (int i = 0; i < c.getColumnCount(); i++) {
+            final String columnName = c.getColumnName(i);
+            boolean found = false;
+            for (int j = 0; j < availableColumns.length; j++) {
+                if (availableColumns[j].equals(columnName)) {
+                    row.add(data[j]);
+                    found = true;
+                    break;
+                }
+            }
+            if (!found) {
+                throw new IllegalArgumentException("Invalid column " + projection[i]);
+            }
+        }
+        return c;
+    }
+
+    /**
+     * Notify interested apps that certain fields of the ServiceState have changed.
+     *
+     * Apps which want to wake when specific fields change can use
+     * JobScheduler's TriggerContentUri.  This replaces the waking functionality of the implicit
+     * broadcast of ACTION_SERVICE_STATE_CHANGED for apps targeting version O.
+     *
+     * We will only notify for certain fields. This is an intentional change from the behavior of
+     * the broadcast. Listeners will be notified when the voice or data registration state or
+     * roaming type changes.
+     */
+    @VisibleForTesting
+    public static void notifyChangeForSubIdAndField(Context context, ServiceState oldSS,
+            ServiceState newSS, int subId) {
+        final boolean firstUpdate = (oldSS == null) ? true : false;
+
+        // for every field, if the field has changed values, notify via the provider
+        if (firstUpdate || voiceRegStateChanged(oldSS, newSS)) {
+            context.getContentResolver().notifyChange(
+                    getUriForSubscriptionIdAndField(subId, VOICE_REG_STATE),
+                    /* observer= */ null, /* syncToNetwork= */ false);
+        }
+        if (firstUpdate || dataRegStateChanged(oldSS, newSS)) {
+            context.getContentResolver().notifyChange(
+                    getUriForSubscriptionIdAndField(subId, DATA_REG_STATE), null, false);
+        }
+        if (firstUpdate || voiceRoamingTypeChanged(oldSS, newSS)) {
+            context.getContentResolver().notifyChange(
+                    getUriForSubscriptionIdAndField(subId, VOICE_ROAMING_TYPE), null, false);
+        }
+        if (firstUpdate || dataRoamingTypeChanged(oldSS, newSS)) {
+            context.getContentResolver().notifyChange(
+                    getUriForSubscriptionIdAndField(subId, DATA_ROAMING_TYPE), null, false);
+        }
+    }
+
+    private static boolean voiceRegStateChanged(ServiceState oldSS, ServiceState newSS) {
+        return oldSS.getState() != newSS.getState();
+    }
+
+    private static boolean dataRegStateChanged(ServiceState oldSS, ServiceState newSS) {
+        return oldSS.getDataRegistrationState() != newSS.getDataRegistrationState();
+    }
+
+    private static boolean voiceRoamingTypeChanged(ServiceState oldSS, ServiceState newSS) {
+        return oldSS.getVoiceRoamingType() != newSS.getVoiceRoamingType();
+    }
+
+    private static boolean dataRoamingTypeChanged(ServiceState oldSS, ServiceState newSS) {
+        return oldSS.getDataRoamingType() != newSS.getDataRoamingType();
+    }
+
+    /**
+     * Notify interested apps that the ServiceState has changed.
+     *
+     * Apps which want to wake when any field in the ServiceState has changed can use
+     * JobScheduler's TriggerContentUri.  This replaces the waking functionality of the implicit
+     * broadcast of ACTION_SERVICE_STATE_CHANGED for apps targeting version O.
+     *
+     * We will only notify for certain fields. This is an intentional change from the behavior of
+     * the broadcast. Listeners will only be notified when the voice/data registration state or
+     * roaming type changes.
+     */
+    @VisibleForTesting
+    public static void notifyChangeForSubId(Context context, ServiceState oldSS, ServiceState newSS,
+            int subId) {
+        // if the voice or data registration or roaming state field has changed values, notify via
+        // the provider.
+        // If oldSS is null and newSS is not (e.g. first update of service state) this will also
+        // notify
+        if (oldSS == null || voiceRegStateChanged(oldSS, newSS) || dataRegStateChanged(oldSS, newSS)
+                || voiceRoamingTypeChanged(oldSS, newSS) || dataRoamingTypeChanged(oldSS, newSS)) {
+            context.getContentResolver().notifyChange(getUriForSubscriptionId(subId), null, false);
+        }
+    }
+}
diff --git a/src/com/android/phone/settings/AccessibilitySettingsFragment.java b/src/com/android/phone/settings/AccessibilitySettingsFragment.java
index c9aa630..b665f4b 100644
--- a/src/com/android/phone/settings/AccessibilitySettingsFragment.java
+++ b/src/com/android/phone/settings/AccessibilitySettingsFragment.java
@@ -166,6 +166,17 @@
             int rttMode = mButtonRtt.isChecked() ? 1 : 0;
             Settings.Secure.putInt(mContext.getContentResolver(), Settings.Secure.RTT_CALLING_MODE,
                     rttMode);
+            // Update RTT config with IMS Manager if the always-on carrier config isn't set to true.
+            CarrierConfigManager configManager = (CarrierConfigManager) mContext.getSystemService(
+                            Context.CARRIER_CONFIG_SERVICE);
+            for (int subId : SubscriptionController.getInstance().getActiveSubIdList(true)) {
+                if (!configManager.getConfigForSubId(subId).getBoolean(
+                        CarrierConfigManager.KEY_IGNORE_RTT_MODE_SETTING_BOOL, false)) {
+                    int phoneId = SubscriptionController.getInstance().getPhoneId(subId);
+                    ImsManager imsManager = ImsManager.getInstance(getContext(), phoneId);
+                    imsManager.setRttEnabled(mButtonRtt.isChecked());
+                }
+            }
             return true;
         }
 
diff --git a/src/com/android/services/telephony/TelephonyConnectionService.java b/src/com/android/services/telephony/TelephonyConnectionService.java
index bac4793..fe853fe 100644
--- a/src/com/android/services/telephony/TelephonyConnectionService.java
+++ b/src/com/android/services/telephony/TelephonyConnectionService.java
@@ -896,7 +896,7 @@
             int dataNetType = phone.getServiceState().getDataNetworkType();
             if (dataNetType == TelephonyManager.NETWORK_TYPE_LTE ||
                     dataNetType == TelephonyManager.NETWORK_TYPE_LTE_CA) {
-                state = phone.getServiceState().getDataRegState();
+                state = phone.getServiceState().getDataRegistrationState();
             }
         }
 
diff --git a/src/com/android/services/telephony/rcs/PresenceHelper.java b/src/com/android/services/telephony/rcs/PresenceHelper.java
new file mode 100644
index 0000000..5f7e35f
--- /dev/null
+++ b/src/com/android/services/telephony/rcs/PresenceHelper.java
@@ -0,0 +1,166 @@
+/*
+ * 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.services.telephony.rcs;
+
+import android.annotation.Nullable;
+import android.content.Context;
+import android.telephony.SubscriptionManager;
+import android.util.Log;
+import android.util.SparseArray;
+
+import com.android.ims.RcsFeatureConnection;
+import com.android.ims.RcsFeatureManager;
+import com.android.internal.telephony.Phone;
+import com.android.internal.telephony.PhoneFactory;
+import com.android.internal.telephony.imsphone.ImsPhone;
+import com.android.internal.telephony.imsphone.ImsRcsStatusListener;
+import com.android.phone.R;
+import com.android.service.ims.presence.PresencePublication;
+import com.android.service.ims.presence.PresenceSubscriber;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Optional;
+
+class PresenceHelper {
+
+    private static final String LOG_TAG = "PresenceHelper";
+
+    private final Context mContext;
+    private final List<Phone> mPhones;
+
+    private final SparseArray<PresencePublication> mPresencePublications = new SparseArray<>();
+    private final SparseArray<PresenceSubscriber> mPresenceSubscribers = new SparseArray<>();
+
+    PresenceHelper(Context context) {
+        mContext = context;
+
+        // Get phones
+        Phone[] phoneAry = PhoneFactory.getPhones();
+        mPhones = (phoneAry != null) ? Arrays.asList(phoneAry) : new ArrayList<>();
+
+        initRcsPresencesInstance();
+        registerRcsConnectionStatus();
+
+        Log.i(LOG_TAG, "initialized: phone size=" + mPhones.size());
+    }
+
+    private void initRcsPresencesInstance() {
+        String[] volteError = mContext.getResources().getStringArray(
+                R.array.config_volte_provision_error_on_publish_response);
+        String[] rcsError = mContext.getResources().getStringArray(
+                R.array.config_rcs_provision_error_on_publish_response);
+
+        mPhones.forEach((phone) -> {
+            RcsFeatureConnection rcsConnection = getRcsFeatureConnection(phone);
+            // Initialize PresencePublication
+            mPresencePublications.put(
+                    phone.getPhoneId(),
+                    new PresencePublication(rcsConnection, mContext, volteError, rcsError));
+            // Initialize PresenceSubscriber
+            mPresenceSubscribers.put(
+                    phone.getPhoneId(),
+                    new PresenceSubscriber(rcsConnection, mContext, volteError, rcsError));
+        });
+    }
+
+    private @Nullable RcsFeatureConnection getRcsFeatureConnection(Phone phone) {
+        ImsPhone imsPhone = (ImsPhone) phone.getImsPhone();
+        if (imsPhone != null) {
+            RcsFeatureManager rcsFeatureManager = imsPhone.getRcsManager();
+            if (rcsFeatureManager != null) {
+                return rcsFeatureManager.getRcsFeatureConnection();
+            }
+        }
+        return null;
+    }
+
+    /*
+     * RcsFeatureManager in ImsPhone is not null only when RCS is connected. Register a callback to
+     * receive the RCS connection status.
+     */
+    private void registerRcsConnectionStatus() {
+        mPhones.forEach((phone) -> {
+            ImsPhone imsPhone = (ImsPhone) phone.getImsPhone();
+            if (imsPhone != null) {
+                imsPhone.setRcsStatusListener(mStatusListener);
+            }
+        });
+    }
+
+    /**
+     * The IMS RCS status listener to listen the status changed
+     */
+    private ImsRcsStatusListener mStatusListener = new ImsRcsStatusListener() {
+        @Override
+        public void onRcsConnected(int phoneId, RcsFeatureManager rcsFeatureManager) {
+            int subId = getSubscriptionId(phoneId);
+            if (subId == SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
+                Log.e(LOG_TAG, "onRcsConnected: invalid subId, phoneId=" + phoneId);
+                return;
+            }
+
+            Log.i(LOG_TAG, "onRcsConnected: phoneId=" + phoneId + ", subId=" + subId);
+            RcsFeatureConnection connection = rcsFeatureManager.getRcsFeatureConnection();
+            PresencePublication presencePublication = getPresencePublication(phoneId);
+            if (presencePublication != null) {
+                Log.i(LOG_TAG, "Update PresencePublisher because RCS is connected");
+                presencePublication.updatePresencePublisher(subId, connection);
+            }
+            PresenceSubscriber presenceSubscriber = getPresenceSubscriber(phoneId);
+            if (presenceSubscriber != null) {
+                Log.i(LOG_TAG, "Update PresenceSubscriber because RCS is connected");
+                presenceSubscriber.updatePresenceSubscriber(subId, connection);
+            }
+        }
+
+        @Override
+        public void onRcsDisconnected(int phoneId) {
+            int subId = getSubscriptionId(phoneId);
+            Log.i(LOG_TAG, "onRcsDisconnected: phoneId=" + phoneId + ", subId=" + subId);
+            PresencePublication publication = getPresencePublication(phoneId);
+            if (publication != null) {
+                Log.i(LOG_TAG, "Remove PresencePublisher because RCS is disconnected");
+                publication.removePresencePublisher(subId);
+            }
+
+            PresenceSubscriber subscriber = getPresenceSubscriber(phoneId);
+            if (subscriber != null) {
+                Log.i(LOG_TAG, "Remove PresencePublisher because RCS is disconnected");
+                subscriber.removePresenceSubscriber(subId);
+            }
+        }
+    };
+
+    private int getSubscriptionId(int phoneId) {
+        Optional<Phone> phone = mPhones.stream()
+                .filter(p -> p.getPhoneId() == phoneId).findFirst();
+        if (phone.isPresent()) {
+            return phone.get().getSubId();
+        }
+        return SubscriptionManager.INVALID_SUBSCRIPTION_ID;
+    }
+
+    public @Nullable PresencePublication getPresencePublication(int phoneId) {
+        return mPresencePublications.get(phoneId);
+    }
+
+    public @Nullable PresenceSubscriber getPresenceSubscriber(int phoneId) {
+        return mPresenceSubscribers.get(phoneId);
+    }
+}
diff --git a/src/com/android/services/telephony/rcs/TelephonyRcsService.java b/src/com/android/services/telephony/rcs/TelephonyRcsService.java
new file mode 100644
index 0000000..74765d6
--- /dev/null
+++ b/src/com/android/services/telephony/rcs/TelephonyRcsService.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.services.telephony.rcs;
+
+import android.content.Context;
+import android.util.Log;
+
+import com.android.service.ims.presence.PresencePublication;
+import com.android.service.ims.presence.PresenceSubscriber;
+
+/**
+ * Telephony RCS Service integrates PresencePublication and PresenceSubscriber into the service.
+ */
+public class TelephonyRcsService {
+
+    private static final String LOG_TAG = "TelephonyRcsService";
+
+    private final Context mContext;
+
+    // A helper class to manage the RCS Presences instances.
+    private final PresenceHelper mPresenceHelper;
+
+    public TelephonyRcsService(Context context) {
+        Log.i(LOG_TAG, "initialize");
+        mContext = context;
+        mPresenceHelper = new PresenceHelper(mContext);
+    }
+
+    private PresencePublication getPresencePublication(int phoneId) {
+        return mPresenceHelper.getPresencePublication(phoneId);
+    }
+
+    private PresenceSubscriber getPresenceSubscriber(int phoneId) {
+        return mPresenceHelper.getPresenceSubscriber(phoneId);
+    }
+}
diff --git a/tests/Android.bp b/tests/Android.bp
index 22b40b5..7ed234e 100644
--- a/tests/Android.bp
+++ b/tests/Android.bp
@@ -25,6 +25,7 @@
         "telephony-common",
         "android.test.base",
         "ims-common",
+        "android.test.mock",
     ],
     platform_apis: true,
     certificate: "platform",
diff --git a/tests/src/com/android/phone/NumberVerificationManagerTest.java b/tests/src/com/android/phone/NumberVerificationManagerTest.java
index d476ba5..f7914ab 100644
--- a/tests/src/com/android/phone/NumberVerificationManagerTest.java
+++ b/tests/src/com/android/phone/NumberVerificationManagerTest.java
@@ -57,7 +57,7 @@
     public void setUp() {
         MockitoAnnotations.initMocks(this);
         ServiceState ss = mock(ServiceState.class);
-        when(ss.getVoiceRegState()).thenReturn(ServiceState.STATE_IN_SERVICE);
+        when(ss.getState()).thenReturn(ServiceState.STATE_IN_SERVICE);
         when(mPhone1.getServiceState()).thenReturn(ss);
         when(mPhone1.getForegroundCall()).thenReturn(mForegroundCall);
         when(mPhone1.getRingingCall()).thenReturn(mRingingCall);
@@ -107,7 +107,7 @@
     @Test
     public void testNoPhoneInServiceFailure() throws Exception {
         ServiceState ss = mock(ServiceState.class);
-        when(ss.getVoiceRegState()).thenReturn(ServiceState.STATE_POWER_OFF);
+        when(ss.getState()).thenReturn(ServiceState.STATE_POWER_OFF);
         when(mPhone1.getServiceState()).thenReturn(ss);
         when(mPhone2.getServiceState()).thenReturn(ss);
         NumberVerificationManager manager =
@@ -138,7 +138,7 @@
     @Test
     public void testVerificationWorksWithOnePhoneInService() throws Exception {
         ServiceState ss = mock(ServiceState.class);
-        when(ss.getVoiceRegState()).thenReturn(ServiceState.STATE_POWER_OFF);
+        when(ss.getState()).thenReturn(ServiceState.STATE_POWER_OFF);
         when(mPhone1.getServiceState()).thenReturn(ss);
         NumberVerificationManager manager =
                 new NumberVerificationManager(() -> new Phone[]{mPhone1, mPhone2});
diff --git a/tests/src/com/android/phone/ServiceStateProviderTest.java b/tests/src/com/android/phone/ServiceStateProviderTest.java
new file mode 100644
index 0000000..574c0c9
--- /dev/null
+++ b/tests/src/com/android/phone/ServiceStateProviderTest.java
@@ -0,0 +1,307 @@
+/*
+ * 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.phone;
+
+import static android.provider.Telephony.ServiceStateTable;
+import static android.provider.Telephony.ServiceStateTable.getUriForSubscriptionId;
+
+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.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+
+import android.content.Context;
+import android.content.pm.ProviderInfo;
+import android.database.ContentObserver;
+import android.database.Cursor;
+import android.net.Uri;
+import android.telephony.ServiceState;
+import android.telephony.SubscriptionManager;
+import android.test.mock.MockContentResolver;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import org.junit.Before;
+import org.junit.Test;
+
+/**
+ * Tests for simple queries of ServiceStateProvider.
+ *
+ * Build, install and run the tests by running the commands below:
+ *     runtest --path <dir or file>
+ *     runtest --path <dir or file> --test-method <testMethodName>
+ *     e.g.)
+ *         runtest --path tests/src/com/android/phone/ServiceStateProviderTest.java \
+ *                 --test-method testGetServiceState
+ */
+public class ServiceStateProviderTest {
+    private static final String TAG = "ServiceStateProviderTest";
+
+    private Context mContext;
+    private MockContentResolver mContentResolver;
+    private ServiceState mTestServiceState;
+    private ServiceState mTestServiceStateForSubId1;
+
+    private final String[] mTestProjection =
+    {
+        ServiceStateTable.VOICE_REG_STATE,
+        ServiceStateTable.DATA_REG_STATE,
+        ServiceStateTable.VOICE_OPERATOR_ALPHA_LONG,
+        ServiceStateTable.VOICE_OPERATOR_ALPHA_SHORT,
+        ServiceStateTable.VOICE_OPERATOR_NUMERIC,
+        ServiceStateTable.DATA_OPERATOR_ALPHA_LONG,
+        ServiceStateTable.DATA_OPERATOR_ALPHA_SHORT,
+        ServiceStateTable.DATA_OPERATOR_NUMERIC,
+        ServiceStateTable.IS_MANUAL_NETWORK_SELECTION,
+        ServiceStateTable.RIL_VOICE_RADIO_TECHNOLOGY,
+        ServiceStateTable.RIL_DATA_RADIO_TECHNOLOGY,
+        ServiceStateTable.CSS_INDICATOR,
+        ServiceStateTable.NETWORK_ID,
+        ServiceStateTable.SYSTEM_ID,
+        ServiceStateTable.CDMA_ROAMING_INDICATOR,
+        ServiceStateTable.CDMA_DEFAULT_ROAMING_INDICATOR,
+        ServiceStateTable.CDMA_ERI_ICON_INDEX,
+        ServiceStateTable.CDMA_ERI_ICON_MODE,
+        ServiceStateTable.IS_EMERGENCY_ONLY,
+        ServiceStateTable.IS_USING_CARRIER_AGGREGATION,
+        ServiceStateTable.OPERATOR_ALPHA_LONG_RAW,
+        ServiceStateTable.OPERATOR_ALPHA_SHORT_RAW,
+    };
+
+    @Before
+    public void setUp() throws Exception {
+        mContext = mock(Context.class);
+        mContentResolver = new MockContentResolver() {
+            @Override
+            public void notifyChange(Uri uri, ContentObserver observer, boolean syncToNetwork) {
+                throw new RuntimeException("notifyChange!");
+            }
+        };
+        doReturn(mContentResolver).when(mContext).getContentResolver();
+
+        mTestServiceState = new ServiceState();
+        mTestServiceState.setStateOutOfService();
+        mTestServiceStateForSubId1 = new ServiceState();
+        mTestServiceStateForSubId1.setStateOff();
+
+        // Mock out the actual phone state
+        ServiceStateProvider provider = new ServiceStateProvider() {
+            @Override
+            public ServiceState getServiceState(int subId) {
+                if (subId == 1) {
+                    return mTestServiceStateForSubId1;
+                } else {
+                    return mTestServiceState;
+                }
+            }
+
+            @Override
+            public int getDefaultSubId() {
+                return 0;
+            }
+        };
+        ProviderInfo providerInfo = new ProviderInfo();
+        providerInfo.authority = "service-state";
+        provider.attachInfoForTesting(mContext, providerInfo);
+        mContentResolver.addProvider("service-state", provider);
+    }
+
+    @Test
+    @SmallTest
+    public void testQueryServiceStateWithNoSubId() {
+        // Verify that when calling query with no subId in the uri the default ServiceState is
+        // returned.
+        // In this case the subId is set to 0 and the expected service state is
+        // mTestServiceState.
+        verifyServiceStateForSubId(ServiceStateTable.CONTENT_URI, mTestServiceState);
+    }
+
+    @Test
+    @SmallTest
+    public void testGetServiceStateWithDefaultSubId() {
+        // Verify that when calling with the DEFAULT_SUBSCRIPTION_ID the correct ServiceState is
+        // returned
+        // In this case the subId is set to 0 and the expected service state is
+        // mTestServiceState.
+        verifyServiceStateForSubId(
+                getUriForSubscriptionId(SubscriptionManager.DEFAULT_SUBSCRIPTION_ID),
+                mTestServiceState);
+    }
+
+    /**
+     * Test querying the service state for a given subId
+     */
+    @Test
+    @SmallTest
+    public void testGetServiceStateForSubId() {
+        // Verify that when calling with a specific subId the correct ServiceState is returned
+        // In this case the subId is set to 1 and the expected service state is
+        // mTestServiceStateForSubId1
+        verifyServiceStateForSubId(getUriForSubscriptionId(1), mTestServiceStateForSubId1);
+    }
+
+    private void verifyServiceStateForSubId(Uri uri, ServiceState ss) {
+        Cursor cursor = mContentResolver.query(uri, mTestProjection, "",
+                null, null);
+        assertNotNull(cursor);
+        cursor.moveToFirst();
+
+        final int voiceRegState = ss.getState();
+        final int dataRegState = ss.getDataRegistrationState();
+        final String voiceOperatorAlphaLong = ss.getOperatorAlphaLong();
+        final String voiceOperatorAlphaShort = ss.getOperatorAlphaShort();
+        final String voiceOperatorNumeric = ss.getOperatorNumeric();
+        final String dataOperatorAlphaLong = ss.getOperatorAlphaLong();
+        final String dataOperatorAlphaShort = ss.getOperatorAlphaShort();
+        final String dataOperatorNumeric = ss.getOperatorNumeric();
+        final int isManualNetworkSelection = (ss.getIsManualSelection()) ? 1 : 0;
+        final int rilVoiceRadioTechnology = ss.getRilVoiceRadioTechnology();
+        final int rilDataRadioTechnology = ss.getRilDataRadioTechnology();
+        final int cssIndicator = ss.getCssIndicator();
+        final int networkId = ss.getCdmaNetworkId();
+        final int systemId = ss.getCdmaSystemId();
+        final int cdmaRoamingIndicator = ss.getCdmaRoamingIndicator();
+        final int cdmaDefaultRoamingIndicator = ss.getCdmaDefaultRoamingIndicator();
+        final int cdmaEriIconIndex = ss.getCdmaEriIconIndex();
+        final int cdmaEriIconMode = ss.getCdmaEriIconMode();
+        final int isEmergencyOnly = (ss.isEmergencyOnly()) ? 1 : 0;
+        final int isUsingCarrierAggregation = (ss.isUsingCarrierAggregation()) ? 1 : 0;
+        final String operatorAlphaLongRaw = ss.getOperatorAlphaLongRaw();
+        final String operatorAlphaShortRaw = ss.getOperatorAlphaShortRaw();
+
+        assertEquals(voiceRegState, cursor.getInt(0));
+        assertEquals(dataRegState, cursor.getInt(1));
+        assertEquals(voiceOperatorAlphaLong, cursor.getString(2));
+        assertEquals(voiceOperatorAlphaShort, cursor.getString(3));
+        assertEquals(voiceOperatorNumeric, cursor.getString(4));
+        assertEquals(dataOperatorAlphaLong, cursor.getString(5));
+        assertEquals(dataOperatorAlphaShort, cursor.getString(6));
+        assertEquals(dataOperatorNumeric, cursor.getString(7));
+        assertEquals(isManualNetworkSelection, cursor.getInt(8));
+        assertEquals(rilVoiceRadioTechnology, cursor.getInt(9));
+        assertEquals(rilDataRadioTechnology, cursor.getInt(10));
+        assertEquals(cssIndicator, cursor.getInt(11));
+        assertEquals(networkId, cursor.getInt(12));
+        assertEquals(systemId, cursor.getInt(13));
+        assertEquals(cdmaRoamingIndicator, cursor.getInt(14));
+        assertEquals(cdmaDefaultRoamingIndicator, cursor.getInt(15));
+        assertEquals(cdmaEriIconIndex, cursor.getInt(16));
+        assertEquals(cdmaEriIconMode, cursor.getInt(17));
+        assertEquals(isEmergencyOnly, cursor.getInt(18));
+        assertEquals(isUsingCarrierAggregation, cursor.getInt(19));
+        assertEquals(operatorAlphaLongRaw, cursor.getString(20));
+        assertEquals(operatorAlphaShortRaw, cursor.getString(21));
+    }
+
+    /**
+     * Test that we don't notify for certain field changes. (e.g. we don't notify when the NetworkId
+     * or SystemId change) This is an intentional behavior change from the broadcast.
+     */
+    @Test
+    @SmallTest
+    public void testNoNotify() {
+        int subId = 0;
+
+        ServiceState oldSS = new ServiceState();
+        oldSS.setStateOutOfService();
+        oldSS.setCdmaSystemAndNetworkId(1, 1);
+
+        ServiceState newSS = new ServiceState();
+        newSS.setStateOutOfService();
+        newSS.setCdmaSystemAndNetworkId(0, 0);
+
+        // Test that notifyChange is not called for these fields
+        boolean notifyChangeWasCalled = false;
+        try {
+            ServiceStateProvider.notifyChangeForSubIdAndField(mContext, oldSS, newSS, subId);
+        } catch (RuntimeException e) {
+            final String message = e.getMessage();
+            if (message != null &&  message.equals("notifyChange!")) {
+                notifyChangeWasCalled = true;
+            }
+        }
+        assertFalse(notifyChangeWasCalled);
+    }
+
+    @Test
+    @SmallTest
+    public void testNotifyChanged() {
+        int subId = 0;
+
+        ServiceState oldSS = new ServiceState();
+        oldSS.setStateOutOfService();
+        oldSS.setVoiceRegState(ServiceState.STATE_OUT_OF_SERVICE);
+
+        ServiceState copyOfOldSS = new ServiceState();
+        copyOfOldSS.setStateOutOfService();
+        copyOfOldSS.setVoiceRegState(ServiceState.STATE_OUT_OF_SERVICE);
+
+        ServiceState newSS = new ServiceState();
+        newSS.setStateOutOfService();
+        newSS.setVoiceRegState(ServiceState.STATE_POWER_OFF);
+
+        // Test that notifyChange is not called with no change in notifyChangeForSubIdAndField
+        boolean notifyChangeWasCalled = false;
+        try {
+            ServiceStateProvider.notifyChangeForSubIdAndField(mContext, oldSS, copyOfOldSS, subId);
+        } catch (RuntimeException e) {
+            final String message = e.getMessage();
+            if (message != null &&  message.equals("notifyChange!")) {
+                notifyChangeWasCalled = true;
+            }
+        }
+        assertFalse(notifyChangeWasCalled);
+
+        // Test that notifyChange is not called with no change in notifyChangeForSubId
+        notifyChangeWasCalled = false;
+        try {
+            ServiceStateProvider.notifyChangeForSubId(mContext, oldSS, copyOfOldSS, subId);
+        } catch (RuntimeException e) {
+            final String message = e.getMessage();
+            if (message != null &&  message.equals("notifyChange!")) {
+                notifyChangeWasCalled = true;
+            }
+        }
+        assertFalse(notifyChangeWasCalled);
+
+        // Test that notifyChange is called by notifyChangeForSubIdAndField when the voice_reg_state
+        // changes
+        notifyChangeWasCalled = false;
+        try {
+            ServiceStateProvider.notifyChangeForSubIdAndField(mContext, oldSS, newSS, subId);
+        } catch (RuntimeException e) {
+            final String message = e.getMessage();
+            if (message != null &&  message.equals("notifyChange!")) {
+                notifyChangeWasCalled = true;
+            }
+        }
+        assertTrue(notifyChangeWasCalled);
+
+        // Test that notifyChange is called by notifyChangeForSubId when the voice_reg_state changes
+        notifyChangeWasCalled = false;
+        try {
+            ServiceStateProvider.notifyChangeForSubId(mContext, oldSS, newSS, subId);
+        } catch (RuntimeException e) {
+            final String message = e.getMessage();
+            if (message != null &&  message.equals("notifyChange!")) {
+                notifyChangeWasCalled = true;
+            }
+        }
+        assertTrue(notifyChangeWasCalled);
+    }
+}