Merge "Fix issue where verstat is not propagated to Telecom."
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 16ad93a..a7c2073 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -322,7 +322,7 @@
         <activity android:name="GsmUmtsCallBarringOptions"
                 android:label="@string/labelCallBarring"
                 android:configChanges="orientation|screenSize|keyboardHidden"
-                android:theme="@style/CallSettingsWithoutDividerTheme">
+                android:theme="@style/DialerSettingsLight">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
             </intent-filter>
diff --git a/res/values/config.xml b/res/values/config.xml
index d4e4c79..2f0fcc6 100644
--- a/res/values/config.xml
+++ b/res/values/config.xml
@@ -257,6 +257,16 @@
          audio stream which the remote party will be able to hear. -->
     <bool name="config_support_telephony_audio_device">false</bool>
 
+    <!-- Whether the device supports dialing emergency RTT calls when there's no SIM card installed
+    -->
+    <bool name="config_support_simless_emergency_rtt">false</bool>
+
+    <!-- Array of countries that support sim-less emergency RTT calls. Values should be
+         ISO3166 country codes in lowercase. -->
+    <string-array name="config_simless_emergency_rtt_supported_countries" translatable="false">
+        <item>us</item>
+    </string-array>
+
     <string-array translatable="false" name="config_volte_provision_error_on_publish_response">
         <item>403 not authorized for presence</item>
     </string-array>
diff --git a/src/com/android/phone/CallFeaturesSetting.java b/src/com/android/phone/CallFeaturesSetting.java
index 9faffa1..ef83ead 100644
--- a/src/com/android/phone/CallFeaturesSetting.java
+++ b/src/com/android/phone/CallFeaturesSetting.java
@@ -97,6 +97,7 @@
             "phone_account_settings_preference_screen";
 
     private static final String ENABLE_VIDEO_CALLING_KEY = "button_enable_video_calling";
+    private static final String BUTTON_VP_KEY = "button_voice_privacy_key";
 
     private Phone mPhone;
     private ImsManager mImsMgr;
@@ -397,6 +398,9 @@
                 if (phoneType == PhoneConstants.PHONE_TYPE_CDMA) {
                     prefSet.removePreference(fdnButton);
                     addPreferencesFromResource(R.xml.cdma_call_privacy);
+                    CdmaVoicePrivacySwitchPreference buttonVoicePrivacy =
+                            (CdmaVoicePrivacySwitchPreference) findPreference(BUTTON_VP_KEY);
+                    buttonVoicePrivacy.setPhone(mPhone);
                 } else if (phoneType == PhoneConstants.PHONE_TYPE_GSM) {
                     if (mPhone.getIccCard() == null || !mPhone.getIccCard().getIccFdnAvailable()) {
                         prefSet.removePreference(fdnButton);
diff --git a/src/com/android/phone/CarrierConfigLoader.java b/src/com/android/phone/CarrierConfigLoader.java
index a816c89..b969ce4 100644
--- a/src/com/android/phone/CarrierConfigLoader.java
+++ b/src/com/android/phone/CarrierConfigLoader.java
@@ -1,4 +1,4 @@
-/**
+/*
  * Copyright (c) 2015, The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
@@ -21,6 +21,7 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.app.AppOpsManager;
 import android.content.BroadcastReceiver;
 import android.content.ComponentName;
 import android.content.Context;
@@ -37,6 +38,7 @@
 import android.os.IBinder;
 import android.os.Message;
 import android.os.PersistableBundle;
+import android.os.Process;
 import android.os.RemoteException;
 import android.os.ResultReceiver;
 import android.os.ServiceManager;
@@ -47,6 +49,8 @@
 import android.telephony.CarrierConfigManager;
 import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyManager;
+import android.text.TextUtils;
+import android.util.ArraySet;
 import android.util.LocalLog;
 import android.util.Log;
 
@@ -71,6 +75,7 @@
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.List;
+import java.util.Set;
 
 /**
  * CarrierConfigLoader binds to privileged carrier apps to fetch carrier config overlays.
@@ -153,6 +158,12 @@
     // SharedPreferences key for last known build fingerprint.
     private static final String KEY_FINGERPRINT = "build_fingerprint";
 
+    // Argument for #dump that indicates we should also call the default and specified carrier
+    // service's #dump method. In multi-SIM devices, it's possible that carrier A is on SIM 1 and
+    // carrier B is on SIM 2, in which case we should not dump carrier B's service when carrier A
+    // requested the dump.
+    private static final String DUMP_ARG_REQUESTING_PACKAGE = "--requesting-package";
+
     // Handler to process various events.
     //
     // For each phoneId, the event sequence should be:
@@ -174,42 +185,39 @@
         @Override
         public void handleMessage(Message msg) {
             final int phoneId = msg.arg1;
-            logWithLocalLog("mHandler: " + msg.what + " phoneId: " + phoneId);
+            logdWithLocalLog("mHandler: " + msg.what + " phoneId: " + phoneId);
             if (!SubscriptionManager.isValidPhoneId(phoneId)
                     && msg.what != EVENT_MULTI_SIM_CONFIG_CHANGED) {
                 return;
             }
             switch (msg.what) {
-                case EVENT_CLEAR_CONFIG:
-                {
+                case EVENT_CLEAR_CONFIG: {
                     clearConfigForPhone(phoneId, true);
                     break;
                 }
 
-                case EVENT_SYSTEM_UNLOCKED:
-                {
+                case EVENT_SYSTEM_UNLOCKED: {
                     for (int i = 0; i < TelephonyManager.from(mContext).getActiveModemCount();
                             ++i) {
                         // When user unlock device, we should only try to send broadcast again if we
                         // have sent it before unlock. This will avoid we try to load carrier config
                         // when SIM is still loading when unlock happens.
                         if (mHasSentConfigChange[i]) {
-                            logWithLocalLog("System unlocked");
+                            logdWithLocalLog("System unlocked");
                             updateConfigForPhoneId(i);
                         }
                     }
                     break;
                 }
 
-                case EVENT_PACKAGE_CHANGED:
-                {
+                case EVENT_PACKAGE_CHANGED: {
                     final String carrierPackageName = (String) msg.obj;
                     // Only update if there are cached config removed to avoid updating config for
                     // unrelated packages.
                     if (clearCachedConfigForPackage(carrierPackageName)) {
                         int numPhones = TelephonyManager.from(mContext).getActiveModemCount();
                         for (int i = 0; i < numPhones; ++i) {
-                            logWithLocalLog("Package changed: " + carrierPackageName
+                            logdWithLocalLog("Package changed: " + carrierPackageName
                                     + ", phone=" + i);
                             updateConfigForPhoneId(i);
                         }
@@ -217,13 +225,12 @@
                     break;
                 }
 
-                case EVENT_DO_FETCH_DEFAULT:
-                {
+                case EVENT_DO_FETCH_DEFAULT: {
                     // Restore persistent override values.
                     PersistableBundle config = restoreConfigFromXml(
                             mPlatformCarrierConfigPackage, OVERRIDE_PACKAGE_ADDITION, phoneId);
                     if (config != null) {
-                        log("Loaded persistent override config from XML. package="
+                        logd("Loaded persistent override config from XML. package="
                                 + mPlatformCarrierConfigPackage
                                 + " phoneId=" + phoneId);
                         mPersistentOverrideConfigs[phoneId] = config;
@@ -231,7 +238,7 @@
 
                     config = restoreConfigFromXml(mPlatformCarrierConfigPackage, "", phoneId);
                     if (config != null) {
-                        log(
+                        logd(
                                 "Loaded config from XML. package="
                                         + mPlatformCarrierConfigPackage
                                         + " phoneId="
@@ -261,8 +268,7 @@
                     break;
                 }
 
-                case EVENT_CONNECTED_TO_DEFAULT:
-                {
+                case EVENT_CONNECTED_TO_DEFAULT: {
                     removeMessages(EVENT_BIND_DEFAULT_TIMEOUT);
                     final CarrierServiceConnection conn = (CarrierServiceConnection) msg.obj;
                     // If new service connection has been created, unbind.
@@ -292,7 +298,7 @@
                                     PersistableBundle config =
                                             resultData.getParcelable(KEY_CONFIG_BUNDLE);
                                     saveConfigToXml(mPlatformCarrierConfigPackage, "", phoneId,
-                                        carrierId, config);
+                                            carrierId, config);
                                     mConfigFromDefaultApp[phoneId] = config;
                                     sendMessage(
                                             obtainMessage(
@@ -304,7 +310,7 @@
                         ICarrierService carrierService =
                                 ICarrierService.Stub.asInterface(conn.service);
                         carrierService.getCarrierConfig(carrierId, resultReceiver);
-                        logWithLocalLog("fetch config for default app: "
+                        logdWithLocalLog("Fetch config for default app: "
                                 + mPlatformCarrierConfigPackage
                                 + " carrierid: " + carrierId.toString());
                     } catch (RemoteException e) {
@@ -320,9 +326,8 @@
                 }
 
                 case EVENT_BIND_DEFAULT_TIMEOUT:
-                case EVENT_FETCH_DEFAULT_TIMEOUT:
-                {
-                    loge("bind/fetch time out from " + mPlatformCarrierConfigPackage);
+                case EVENT_FETCH_DEFAULT_TIMEOUT: {
+                    loge("Bind/fetch time out from " + mPlatformCarrierConfigPackage);
                     removeMessages(EVENT_FETCH_DEFAULT_TIMEOUT);
                     // If we attempted to bind to the app, but the service connection is null due to
                     // the race condition that clear config event happens before bind/fetch complete
@@ -337,8 +342,7 @@
                     break;
                 }
 
-                case EVENT_FETCH_DEFAULT_DONE:
-                {
+                case EVENT_FETCH_DEFAULT_DONE: {
                     // If we attempted to bind to the app, but the service connection is null, then
                     // config was cleared while we were waiting and we should not continue.
                     if (!msg.getData().getBoolean("loaded_from_xml", false)
@@ -347,7 +351,7 @@
                     }
                     final String carrierPackageName = getCarrierPackageForPhoneId(phoneId);
                     if (carrierPackageName != null) {
-                        log("Found carrier config app: " + carrierPackageName);
+                        logd("Found carrier config app: " + carrierPackageName);
                         sendMessage(obtainMessage(EVENT_DO_FETCH_CARRIER, phoneId, -1));
                     } else {
                         notifySubscriptionInfoUpdater(phoneId);
@@ -355,13 +359,12 @@
                     break;
                 }
 
-                case EVENT_DO_FETCH_CARRIER:
-                {
+                case EVENT_DO_FETCH_CARRIER: {
                     final String carrierPackageName = getCarrierPackageForPhoneId(phoneId);
                     final PersistableBundle config =
                             restoreConfigFromXml(carrierPackageName, "", phoneId);
                     if (config != null) {
-                        log(
+                        logd(
                                 "Loaded config from XML. package="
                                         + carrierPackageName
                                         + " phoneId="
@@ -372,26 +375,22 @@
                         sendMessage(newMsg);
                     } else {
                         // No cached config, so fetch it from a carrier app.
-                        if (carrierPackageName != null
-                                && bindToConfigPackage(
-                                        carrierPackageName,
-                                        phoneId,
-                                        EVENT_CONNECTED_TO_CARRIER)) {
+                        if (carrierPackageName != null && bindToConfigPackage(carrierPackageName,
+                                phoneId, EVENT_CONNECTED_TO_CARRIER)) {
                             sendMessageDelayed(
                                     obtainMessage(EVENT_BIND_CARRIER_TIMEOUT, phoneId, -1),
                                     BIND_TIMEOUT_MILLIS);
                         } else {
                             // Send broadcast if bind fails.
                             broadcastConfigChangedIntent(phoneId);
-                            loge("bind to carrier app: " + carrierPackageName + " fails");
+                            loge("Bind to carrier app: " + carrierPackageName + " fails");
                             notifySubscriptionInfoUpdater(phoneId);
                         }
                     }
                     break;
                 }
 
-                case EVENT_CONNECTED_TO_CARRIER:
-                {
+                case EVENT_CONNECTED_TO_CARRIER: {
                     removeMessages(EVENT_BIND_CARRIER_TIMEOUT);
                     final CarrierServiceConnection conn = (CarrierServiceConnection) msg.obj;
                     // If new service connection has been created, unbind.
@@ -435,7 +434,7 @@
                         ICarrierService carrierService =
                                 ICarrierService.Stub.asInterface(conn.service);
                         carrierService.getCarrierConfig(carrierId, resultReceiver);
-                        logWithLocalLog("fetch config for carrier app: "
+                        logdWithLocalLog("Fetch config for carrier app: "
                                 + getCarrierPackageForPhoneId(phoneId)
                                 + " carrierid: " + carrierId.toString());
                     } catch (RemoteException e) {
@@ -450,9 +449,8 @@
                 }
 
                 case EVENT_BIND_CARRIER_TIMEOUT:
-                case EVENT_FETCH_CARRIER_TIMEOUT:
-                {
-                    loge("bind/fetch from carrier app timeout");
+                case EVENT_FETCH_CARRIER_TIMEOUT: {
+                    loge("Bind/fetch from carrier app timeout");
                     removeMessages(EVENT_FETCH_CARRIER_TIMEOUT);
                     // If we attempted to bind to the app, but the service connection is null due to
                     // the race condition that clear config event happens before bind/fetch complete
@@ -466,8 +464,7 @@
                     notifySubscriptionInfoUpdater(phoneId);
                     break;
                 }
-                case EVENT_FETCH_CARRIER_DONE:
-                {
+                case EVENT_FETCH_CARRIER_DONE: {
                     // If we attempted to bind to the app, but the service connection is null, then
                     // config was cleared while we were waiting and we should not continue.
                     if (!msg.getData().getBoolean("loaded_from_xml", false)
@@ -478,13 +475,12 @@
                     break;
                 }
 
-                case EVENT_CHECK_SYSTEM_UPDATE:
-                {
+                case EVENT_CHECK_SYSTEM_UPDATE: {
                     SharedPreferences sharedPrefs =
                             PreferenceManager.getDefaultSharedPreferences(mContext);
                     final String lastFingerprint = sharedPrefs.getString(KEY_FINGERPRINT, null);
                     if (!Build.FINGERPRINT.equals(lastFingerprint)) {
-                        log(
+                        logd(
                                 "Build fingerprint changed. old: "
                                         + lastFingerprint
                                         + " new: "
@@ -543,7 +539,7 @@
         mHasSentConfigChange = new boolean[numPhones];
         // Make this service available through ServiceManager.
         ServiceManager.addService(Context.CARRIER_CONFIG_SERVICE, this);
-        log("CarrierConfigLoader has started");
+        logd("CarrierConfigLoader has started");
         mSubscriptionInfoUpdater = PhoneFactory.getSubscriptionInfoUpdater();
         mHandler.sendEmptyMessage(EVENT_CHECK_SYSTEM_UPDATE);
     }
@@ -638,17 +634,17 @@
             }
         }
         intent.putExtra(CarrierConfigManager.EXTRA_SLOT_INDEX, phoneId);
-        log("Broadcast CARRIER_CONFIG_CHANGED for phone " + phoneId);
+        logd("Broadcast CARRIER_CONFIG_CHANGED for phone " + phoneId);
         mContext.sendBroadcast(intent);
         mHasSentConfigChange[phoneId] = true;
     }
 
     /** Binds to the default or carrier config app. */
     private boolean bindToConfigPackage(String pkgName, int phoneId, int eventId) {
-        logWithLocalLog("Binding to " + pkgName + " for phone " + phoneId);
+        logdWithLocalLog("Binding to " + pkgName + " for phone " + phoneId);
         Intent carrierService = new Intent(CarrierService.CARRIER_SERVICE_INTERFACE);
         carrierService.setPackage(pkgName);
-        mServiceConnection[phoneId] = new CarrierServiceConnection(phoneId, eventId);
+        mServiceConnection[phoneId] = new CarrierServiceConnection(phoneId, pkgName, eventId);
         try {
             if (mContext.bindService(carrierService, mServiceConnection[phoneId],
                     Context.BIND_AUTO_CREATE)) {
@@ -756,9 +752,9 @@
      *
      * @param packageName the name of the package from which we fetched this bundle.
      * @param extraString An extra string to be used in the XML file name.
-     * @param phoneId the phone ID.
-     * @param carrierId contains all carrier-identifying information.
-     * @param config the bundle to be written. Null will be treated as an empty bundle.
+     * @param phoneId     the phone ID.
+     * @param carrierId   contains all carrier-identifying information.
+     * @param config      the bundle to be written. Null will be treated as an empty bundle.
      */
     private void saveConfigToXml(String packageName, @NonNull String extraString, int phoneId,
             CarrierIdentifier carrierId, PersistableBundle config) {
@@ -789,7 +785,8 @@
             return;
         }
 
-        logWithLocalLog("save config to xml, packagename: " + packageName + " phoneId: " + phoneId);
+        logdWithLocalLog(
+                "Save config to xml, packagename: " + packageName + " phoneId: " + phoneId);
 
         FileOutputStream outFile = null;
         try {
@@ -800,8 +797,7 @@
             config.writeToStream(outFile);
             outFile.flush();
             outFile.close();
-        }
-        catch (IOException e) {
+        } catch (IOException e) {
             loge(e.toString());
         }
     }
@@ -817,9 +813,9 @@
      *
      * @param packageName the name of the package from which we fetched this bundle.
      * @param extraString An extra string to be used in the XML file name.
-     * @param phoneId the phone ID.
+     * @param phoneId     the phone ID.
      * @return the bundle from the XML file. Returns null if there is no saved config, the saved
-     *         version does not match, or reading config fails.
+     * version does not match, or reading config fails.
      */
     private PersistableBundle restoreConfigFromXml(String packageName, @NonNull String extraString,
             int phoneId) {
@@ -859,13 +855,11 @@
             }
 
             inFile.close();
-        }
-        catch (FileNotFoundException e) {
+        } catch (FileNotFoundException e) {
             // Missing file is normal occurrence that might occur with a new sim or when restoring
             // an override file during boot and should not be treated as an error.
-            if (file != null) log("File not found: " + file.getPath());
-        }
-        catch (IOException e) {
+            if (file != null) logd("File not found: " + file.getPath());
+        } catch (IOException e) {
             loge(e.toString());
         }
 
@@ -894,7 +888,7 @@
         });
         if (packageFiles == null || packageFiles.length < 1) return false;
         for (File f : packageFiles) {
-            log("deleting " + f.getName());
+            logd("Deleting " + f.getName());
             f.delete();
         }
         return true;
@@ -902,7 +896,7 @@
 
     /** Builds a canonical file name for a config file. */
     private String getFilenameForConfig(@NonNull String packageName, @NonNull String extraString,
-                                        @NonNull String iccid, int cid) {
+            @NonNull String iccid, int cid) {
         // the same carrier should have a single copy of XML file named after carrier id.
         // However, it's still possible that platform doesn't recognize the current sim carrier,
         // we will use iccid + carrierid as the canonical file name. carrierid can also handle the
@@ -945,12 +939,14 @@
     }
 
     @Override
-    public @NonNull PersistableBundle getConfigForSubId(int subId, String callingPackage) {
+    @NonNull
+    public PersistableBundle getConfigForSubId(int subId, String callingPackage) {
         return getConfigForSubIdWithFeature(subId, callingPackage, null);
     }
 
     @Override
-    public @NonNull PersistableBundle getConfigForSubIdWithFeature(int subId, String callingPackage,
+    @NonNull
+    public PersistableBundle getConfigForSubIdWithFeature(int subId, String callingPackage,
             String callingFeatureId) {
         if (!TelephonyPermissions.checkCallingOrSelfReadPhoneState(mContext, subId, callingPackage,
                 callingFeatureId, "getCarrierConfig")) {
@@ -994,7 +990,7 @@
         //TODO: Also check for SHELL UID to restrict this method to testing only (b/131326259)
         int phoneId = SubscriptionManager.getPhoneId(subscriptionId);
         if (!SubscriptionManager.isValidPhoneId(phoneId)) {
-            log("Ignore invalid phoneId: " + phoneId + " for subId: " + subscriptionId);
+            logd("Ignore invalid phoneId: " + phoneId + " for subId: " + subscriptionId);
             return;
         }
         overrideConfig(mOverrideConfigs, phoneId, overrides);
@@ -1033,7 +1029,7 @@
     public void notifyConfigChangedForSubId(int subId) {
         int phoneId = SubscriptionManager.getPhoneId(subId);
         if (!SubscriptionManager.isValidPhoneId(phoneId)) {
-            log("Ignore invalid phoneId: " + phoneId + " for subId: " + subId);
+            logd("Ignore invalid phoneId: " + phoneId + " for subId: " + subId);
             return;
         }
 
@@ -1055,7 +1051,7 @@
     public void updateConfigForPhoneId(int phoneId, String simState) {
         mContext.enforceCallingOrSelfPermission(
                 android.Manifest.permission.MODIFY_PHONE_STATE, null);
-        logWithLocalLog("update config for phoneId: " + phoneId + " simState: " + simState);
+        logdWithLocalLog("Update config for phoneId: " + phoneId + " simState: " + simState);
         if (!SubscriptionManager.isValidPhoneId(phoneId)) {
             return;
         }
@@ -1083,44 +1079,78 @@
         return mPlatformCarrierConfigPackage;
     }
 
+    private void unbindIfBound(Context context, CarrierServiceConnection conn,
+            int phoneId) {
+        if (mServiceBound[phoneId]) {
+            mServiceBound[phoneId] = false;
+            context.unbindService(conn);
+        }
+    }
+
+    /**
+     * If {@code args} contains {@link #DUMP_ARG_REQUESTING_PACKAGE} and a following package name,
+     * we'll also call {@link IBinder#dump} on the default carrier service (if bound) and the
+     * specified carrier service (if bound). Typically, this is done for connectivity bug reports
+     * where we don't call {@code dumpsys activity service all-non-platform} because that contains
+     * too much info, but we still want to let carrier apps include their diagnostics.
+     */
     @Override
     public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+        IndentingPrintWriter indentPW = new IndentingPrintWriter(pw, "    ");
         if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DUMP)
                 != PackageManager.PERMISSION_GRANTED) {
-            pw.println("Permission Denial: can't dump carrierconfig from from pid="
+            indentPW.println("Permission Denial: can't dump carrierconfig from from pid="
                     + Binder.getCallingPid() + ", uid=" + Binder.getCallingUid());
             return;
         }
-        pw.println("CarrierConfigLoader: " + this);
-        for (int i = 0; i < TelephonyManager.getDefault().getPhoneCount(); i++) {
-            pw.println("Phone Id = " + i);
-            // display default values in CarrierConfigManager
-            printConfig(CarrierConfigManager.getDefaultConfig(), pw,
-                    "Default Values from CarrierConfigManager");
-            pw.println("");
-            // display ConfigFromDefaultApp
-            printConfig(mConfigFromDefaultApp[i], pw, "mConfigFromDefaultApp");
-            pw.println("");
-            // display ConfigFromCarrierApp
-            printConfig(mConfigFromCarrierApp[i], pw, "mConfigFromCarrierApp");
-            pw.println("");
-            printConfig(mPersistentOverrideConfigs[i], pw, "mPersistentOverrideConfigs");
-            pw.println("");
-            printConfig(mOverrideConfigs[i], pw, "mOverrideConfigs");
+        String requestingPackage = null;
+        int requestingPackageIndex = ArrayUtils.indexOf(args, DUMP_ARG_REQUESTING_PACKAGE);
+        if (requestingPackageIndex >= 0 && requestingPackageIndex < args.length - 1
+                && !TextUtils.isEmpty(args[requestingPackageIndex + 1])) {
+            requestingPackage = args[requestingPackageIndex + 1];
+            // Throws a SecurityException if the caller is impersonating another app in an effort to
+            // dump extra info (which may contain PII the caller doesn't have a right to).
+            enforceCallerIsSystemOrRequestingPackage(requestingPackage);
         }
 
-        pw.println("CarrierConfigLoadingLog=");
-        mCarrierConfigLoadingLog.dump(fd, pw, args);
+        indentPW.println("CarrierConfigLoader: " + this);
+        for (int i = 0; i < TelephonyManager.from(mContext).getActiveModemCount(); i++) {
+            indentPW.println("Phone Id = " + i);
+            // display default values in CarrierConfigManager
+            printConfig(CarrierConfigManager.getDefaultConfig(), indentPW,
+                    "Default Values from CarrierConfigManager");
+            // display ConfigFromDefaultApp
+            printConfig(mConfigFromDefaultApp[i], indentPW, "mConfigFromDefaultApp");
+            // display ConfigFromCarrierApp
+            printConfig(mConfigFromCarrierApp[i], indentPW, "mConfigFromCarrierApp");
+            printConfig(mPersistentOverrideConfigs[i], indentPW, "mPersistentOverrideConfigs");
+            printConfig(mOverrideConfigs[i], indentPW, "mOverrideConfigs");
+        }
+
+        indentPW.println("CarrierConfigLoadingLog=");
+        mCarrierConfigLoadingLog.dump(fd, indentPW, args);
+
+        if (requestingPackage != null) {
+            logd("Including default and requesting package " + requestingPackage
+                    + " carrier services in dump");
+            indentPW.println("");
+            indentPW.println("Connected services");
+            dumpCarrierServiceIfBound(fd, indentPW, "Default config package",
+                    mPlatformCarrierConfigPackage, false /* considerCarrierPrivileges */);
+            dumpCarrierServiceIfBound(fd, indentPW, "Requesting package", requestingPackage,
+                    true /* considerCarrierPrivileges */);
+        }
     }
 
-    private void printConfig(PersistableBundle configApp, PrintWriter pw, String name) {
-        IndentingPrintWriter indentPW = new IndentingPrintWriter(pw, "    ");
+    private void printConfig(PersistableBundle configApp, IndentingPrintWriter indentPW,
+            String name) {
+        indentPW.increaseIndent();
         if (configApp == null) {
-            indentPW.increaseIndent();
             indentPW.println(name + " : null ");
+            indentPW.decreaseIndent();
+            indentPW.println("");
             return;
         }
-        indentPW.increaseIndent();
         indentPW.println(name + " : ");
         List<String> sortedKeys = new ArrayList<String>(configApp.keySet());
         Collections.sort(sortedKeys);
@@ -1136,48 +1166,162 @@
                 indentPW.println(key + " = " + configApp.get(key));
             }
         }
+        indentPW.decreaseIndent();
+        indentPW.decreaseIndent();
+        indentPW.decreaseIndent();
+        indentPW.println("");
     }
 
-    private void unbindIfBound(Context context, CarrierServiceConnection conn,
-            int phoneId) {
-        if (mServiceBound[phoneId]) {
-            mServiceBound[phoneId] = false;
-            context.unbindService(conn);
+    /**
+     * Passes without problem when one of these conditions is true:
+     * - The caller is a privileged UID (e.g. for dumpstate.cpp generating a bug report, where the
+     * system knows the true caller plumbed in through the {@link android.os.BugreportManager} API).
+     * - The caller's UID matches the supplied package.
+     *
+     * @throws SecurityException if none of the above conditions are met.
+     */
+    private void enforceCallerIsSystemOrRequestingPackage(String requestingPackage)
+            throws SecurityException {
+        final int callingUid = Binder.getCallingUid();
+        if (callingUid == Process.ROOT_UID || callingUid == Process.SYSTEM_UID
+                || callingUid == Process.SHELL_UID || callingUid == Process.PHONE_UID) {
+            // Bug reports (dumpstate.cpp) run as SHELL, and let some other privileged UIDs through
+            // as well.
+            return;
         }
+        // An app is trying to dump extra detail, block it if they aren't who they claim to be.
+        AppOpsManager appOps = mContext.getSystemService(AppOpsManager.class);
+        if (appOps == null) {
+            throw new SecurityException("No AppOps");
+        }
+        // Will throw a SecurityException if the UID and package don't match.
+        appOps.checkPackage(callingUid, requestingPackage);
+    }
+
+    /**
+     * Searches for one or more appropriate {@link CarrierService} instances to dump based on the
+     * current connections.
+     *
+     * @param targetPkgName             the target package name to dump carrier services for
+     * @param considerCarrierPrivileges if true, allow a carrier service to be dumped if it shares
+     *                                  carrier privileges with {@code targetPkgName};
+     *                                  otherwise, only dump a carrier service if it is {@code
+     *                                  targetPkgName}
+     */
+    private void dumpCarrierServiceIfBound(FileDescriptor fd, IndentingPrintWriter indentPW,
+            String prefix, String targetPkgName, boolean considerCarrierPrivileges) {
+        // Null package is possible if it's early in the boot process, there was a recent crash, we
+        // loaded the config from XML most recently, or a SIM slot is empty. Carrier apps with
+        // long-lived bindings should typically get dumped here regardless. Even if an app is being
+        // used for multiple phoneIds, we assume that it's smart enough to handle that on its own,
+        // and that in most cases we'd just be dumping duplicate information and bloating a report.
+        indentPW.increaseIndent();
+        indentPW.println(prefix + " : " + targetPkgName);
+        Set<String> dumpedPkgNames = new ArraySet<>(mServiceConnection.length);
+        for (CarrierServiceConnection connection : mServiceConnection) {
+            if (connection == null || !SubscriptionManager.isValidPhoneId(connection.phoneId)
+                    || TextUtils.isEmpty(connection.pkgName)) {
+                continue;
+            }
+            final String servicePkgName = connection.pkgName;
+            // Note: we intentionally ignore system components here because we should NOT match the
+            // shell caller that's typically used for bug reports via non-BugreportManager triggers.
+            final boolean exactPackageMatch = TextUtils.equals(targetPkgName, servicePkgName);
+            final boolean carrierPrivilegesMatch =
+                    considerCarrierPrivileges && hasCarrierPrivileges(targetPkgName,
+                            connection.phoneId);
+            if (!exactPackageMatch && !carrierPrivilegesMatch) continue;
+            // Make sure this service is actually alive before trying to dump it. We don't pay
+            // attention to mServiceBound[connection.phoneId] because typically carrier apps will
+            // request long-lived bindings, and even if we unbind the app, it may still be alive due
+            // to CarrierServiceBindHelper. Pull it out as a reference so even if it gets set to
+            // null within the ServiceConnection during unbinding we can avoid an NPE.
+            final IBinder service = connection.service;
+            if (service == null || !service.isBinderAlive() || !service.pingBinder()) continue;
+            // We've got a live service. Last check is just to make sure we don't dump a package
+            // multiple times.
+            if (!dumpedPkgNames.add(servicePkgName)) continue;
+            if (!exactPackageMatch) {
+                logd(targetPkgName + " has carrier privileges on phoneId " + connection.phoneId
+                        + ", service provided by " + servicePkgName);
+                indentPW.increaseIndent();
+                indentPW.println("Proxy : " + servicePkgName);
+                indentPW.decreaseIndent();
+            }
+            // Flush before we let the app output anything to ensure correct ordering of output.
+            // Internally, Binder#dump calls flush on its printer after finishing so we don't
+            // need to do anything after.
+            indentPW.flush();
+            try {
+                logd("Dumping " + servicePkgName);
+                // We don't need to give the carrier service any args.
+                connection.service.dump(fd, null /* args */);
+                logd("Done with " + servicePkgName);
+            } catch (RemoteException e) {
+                logd("RemoteException from " + servicePkgName, e);
+                indentPW.increaseIndent();
+                indentPW.println("RemoteException");
+                indentPW.increaseIndent();
+                e.printStackTrace(indentPW);
+                indentPW.decreaseIndent();
+                indentPW.decreaseIndent();
+                // We won't retry this package again because now it's in dumpedPkgNames.
+            }
+            indentPW.println("");
+        }
+        if (dumpedPkgNames.isEmpty()) {
+            indentPW.increaseIndent();
+            indentPW.println("Not bound");
+            indentPW.decreaseIndent();
+            indentPW.println("");
+        }
+        indentPW.decreaseIndent();
+    }
+
+    private boolean hasCarrierPrivileges(String pkgName, int phoneId) {
+        int[] subIds = SubscriptionManager.getSubId(phoneId);
+        if (ArrayUtils.isEmpty(subIds)) {
+            return false;
+        }
+        return TelephonyManager.from(mContext).createForSubscriptionId(
+                subIds[0]).checkCarrierPrivilegesForPackage(pkgName)
+                == TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS;
     }
 
     private class CarrierServiceConnection implements ServiceConnection {
-        int phoneId;
-        int eventId;
+        final int phoneId;
+        final String pkgName;
+        final int eventId;
         IBinder service;
 
-        public CarrierServiceConnection(int phoneId, int eventId) {
+        CarrierServiceConnection(int phoneId, String pkgName, int eventId) {
             this.phoneId = phoneId;
+            this.pkgName = pkgName;
             this.eventId = eventId;
         }
 
         @Override
         public void onServiceConnected(ComponentName name, IBinder service) {
-            log("Connected to config app: " + name.flattenToString());
+            logd("Connected to config app: " + name.flattenToShortString());
             this.service = service;
             mHandler.sendMessage(mHandler.obtainMessage(eventId, phoneId, -1, this));
         }
 
         @Override
         public void onServiceDisconnected(ComponentName name) {
-            log("Disconnected from config app: " + name.flattenToString());
+            logd("Disconnected from config app: " + name.flattenToShortString());
             this.service = null;
         }
 
         @Override
         public void onBindingDied(ComponentName name) {
-            log("Binding died from config app: " + name.flattenToString());
+            logd("Binding died from config app: " + name.flattenToShortString());
             this.service = null;
         }
 
         @Override
         public void onNullBinding(ComponentName name) {
-            log("Null binding from config app: " + name.flattenToString());
+            logd("Null binding from config app: " + name.flattenToShortString());
             this.service = null;
         }
     }
@@ -1188,8 +1332,9 @@
             String action = intent.getAction();
             boolean replace = intent.getBooleanExtra(Intent.EXTRA_REPLACING, false);
             // If replace is true, only care ACTION_PACKAGE_REPLACED.
-            if (replace && !Intent.ACTION_PACKAGE_REPLACED.equals(action))
+            if (replace && !Intent.ACTION_PACKAGE_REPLACED.equals(action)) {
                 return;
+            }
 
             switch (action) {
                 case Intent.ACTION_BOOT_COMPLETED:
@@ -1211,11 +1356,15 @@
         }
     }
 
-    private void log(String msg) {
+    private void logd(String msg) {
         Log.d(LOG_TAG, msg);
     }
 
-    private void logWithLocalLog(String msg) {
+    private void logd(String msg, Throwable tr) {
+        Log.d(LOG_TAG, msg, tr);
+    }
+
+    private void logdWithLocalLog(String msg) {
         Log.d(LOG_TAG, msg);
         mCarrierConfigLoadingLog.log(msg);
     }
diff --git a/src/com/android/phone/CdmaCallOptions.java b/src/com/android/phone/CdmaCallOptions.java
index 8f4fa67..2e310aa 100644
--- a/src/com/android/phone/CdmaCallOptions.java
+++ b/src/com/android/phone/CdmaCallOptions.java
@@ -19,9 +19,7 @@
 import android.os.Bundle;
 import android.os.PersistableBundle;
 import android.preference.Preference;
-import android.preference.PreferenceActivity;
 import android.preference.PreferenceScreen;
-import android.preference.SwitchPreference;
 import android.telephony.CarrierConfigManager;
 import android.view.MenuItem;
 
@@ -34,7 +32,7 @@
     private static final String BUTTON_VP_KEY = "button_voice_privacy_key";
     private static final String CALL_FORWARDING_KEY = "call_forwarding_key";
     private static final String CALL_WAITING_KEY = "call_waiting_key";
-    private SwitchPreference mButtonVoicePrivacy;
+    private CdmaVoicePrivacySwitchPreference mButtonVoicePrivacy;
 
     @Override
     protected void onCreate(Bundle icicle) {
@@ -46,7 +44,8 @@
         subInfoHelper.setActionBarTitle(
                 getActionBar(), getResources(), R.string.labelCdmaMore_with_label);
 
-        mButtonVoicePrivacy = (SwitchPreference) findPreference(BUTTON_VP_KEY);
+        mButtonVoicePrivacy = (CdmaVoicePrivacySwitchPreference) findPreference(BUTTON_VP_KEY);
+        mButtonVoicePrivacy.setPhone(subInfoHelper.getPhone());
         PersistableBundle carrierConfig;
         if (subInfoHelper.hasSubId()) {
             carrierConfig = PhoneGlobals.getInstance().getCarrierConfigForSubId(
diff --git a/src/com/android/phone/CdmaVoicePrivacySwitchPreference.java b/src/com/android/phone/CdmaVoicePrivacySwitchPreference.java
index b79a3f0..0192c2a 100644
--- a/src/com/android/phone/CdmaVoicePrivacySwitchPreference.java
+++ b/src/com/android/phone/CdmaVoicePrivacySwitchPreference.java
@@ -102,4 +102,8 @@
             phone.getEnhancedVoicePrivacy(obtainMessage(MESSAGE_GET_VP));
         }
     }
+
+    public void setPhone(Phone phone) {
+        this.phone = phone;
+    }
 }
diff --git a/src/com/android/phone/EmergencyCallbackModeExitDialog.java b/src/com/android/phone/EmergencyCallbackModeExitDialog.java
index 6edc155..1210627 100644
--- a/src/com/android/phone/EmergencyCallbackModeExitDialog.java
+++ b/src/com/android/phone/EmergencyCallbackModeExitDialog.java
@@ -35,6 +35,7 @@
 import android.os.IBinder;
 import android.os.Looper;
 import android.os.Message;
+import android.telephony.TelephonyManager;
 import android.util.Log;
 
 import com.android.internal.telephony.Phone;
@@ -330,7 +331,8 @@
             // Received exit Emergency Callback Mode notification close all dialogs
             if (intent.getAction().equals(
                     TelephonyIntents.ACTION_EMERGENCY_CALLBACK_MODE_CHANGED)) {
-                if (intent.getBooleanExtra("phoneinECMState", false) == false) {
+                // Cancel if the sticky broadcast extra for whether or not we are in ECM is false.
+                if (!intent.getBooleanExtra(TelephonyManager.EXTRA_PHONE_IN_ECM_STATE, false)) {
                     if (mAlertDialog != null)
                         mAlertDialog.dismiss();
                     if (mProgressDialog != null)
diff --git a/src/com/android/phone/EmergencyDialer.java b/src/com/android/phone/EmergencyDialer.java
index 334442f..183742e 100644
--- a/src/com/android/phone/EmergencyDialer.java
+++ b/src/com/android/phone/EmergencyDialer.java
@@ -40,7 +40,6 @@
 import android.os.Bundle;
 import android.os.PersistableBundle;
 import android.provider.Settings;
-import android.telecom.ParcelableCallAnalytics;
 import android.telecom.PhoneAccount;
 import android.telecom.TelecomManager;
 import android.telephony.CarrierConfigManager;
@@ -490,7 +489,7 @@
         if (!TextUtils.isEmpty(phoneNumber)) {
             if (DBG) Log.d(LOG_TAG, "dial emergency number: " + Rlog.pii(LOG_TAG, phoneNumber));
 
-            placeCall(phoneNumber, ParcelableCallAnalytics.CALL_SOURCE_EMERGENCY_SHORTCUT,
+            placeCall(phoneNumber, TelecomManager.CALL_SOURCE_EMERGENCY_SHORTCUT,
                     mShortcutViewConfig.getPhoneInfo());
         } else {
             Log.d(LOG_TAG, "emergency number is empty");
@@ -738,7 +737,7 @@
                 return;
             }
 
-            placeCall(mLastNumber, ParcelableCallAnalytics.CALL_SOURCE_EMERGENCY_DIALPAD,
+            placeCall(mLastNumber, TelecomManager.CALL_SOURCE_EMERGENCY_DIALPAD,
                     phoneToMakeCall);
         } else {
             if (DBG) Log.d(LOG_TAG, "rejecting bad requested number " + mLastNumber);
@@ -1176,9 +1175,9 @@
 
     private String callSourceToString(int callSource) {
         switch (callSource) {
-            case ParcelableCallAnalytics.CALL_SOURCE_EMERGENCY_DIALPAD:
+            case TelecomManager.CALL_SOURCE_EMERGENCY_DIALPAD:
                 return "DialPad";
-            case ParcelableCallAnalytics.CALL_SOURCE_EMERGENCY_SHORTCUT:
+            case TelecomManager.CALL_SOURCE_EMERGENCY_SHORTCUT:
                 return "Shortcut";
             default:
                 return "Unknown-" + callSource;
diff --git a/src/com/android/phone/ImsRcsController.java b/src/com/android/phone/ImsRcsController.java
index 3ea8df2..6a059e4 100644
--- a/src/com/android/phone/ImsRcsController.java
+++ b/src/com/android/phone/ImsRcsController.java
@@ -36,6 +36,7 @@
 import com.android.ims.ImsManager;
 import com.android.internal.telephony.IIntegerConsumer;
 import com.android.internal.telephony.Phone;
+import com.android.internal.telephony.TelephonyPermissions;
 import com.android.internal.telephony.imsphone.ImsPhone;
 import com.android.services.telephony.rcs.RcsFeatureController;
 import com.android.services.telephony.rcs.TelephonyRcsService;
@@ -245,9 +246,13 @@
     }
 
     @Override
-    public void requestCapabilities(int subId, List<Uri> contactNumbers,
-            IRcsUceControllerCallback c) {
+    public void requestCapabilities(int subId, String callingPackage, String callingFeatureId,
+            List<Uri> contactNumbers, IRcsUceControllerCallback c) {
         enforceReadPrivilegedPermission("requestCapabilities");
+        if (!isUceSettingEnabled(subId, callingPackage, callingFeatureId)) {
+            throw new ServiceSpecificException(ImsException.CODE_ERROR_UNSUPPORTED_OPERATION,
+                    "The user has not enabled UCE for this subscription.");
+        }
         final long token = Binder.clearCallingIdentity();
         try {
             UserCapabilityExchangeImpl uce = getRcsFeatureController(subId).getFeature(
@@ -280,17 +285,32 @@
     }
 
     @Override
-    public boolean isUceSettingEnabled(int subId) {
-        enforceReadPrivilegedPermission("isUceSettingEnabled");
-        return SubscriptionManager.getBooleanSubscriptionProperty(subId,
-                SubscriptionManager.IMS_RCS_UCE_ENABLED, false /*defaultValue*/, mApp);
+    public boolean isUceSettingEnabled(int subId, String callingPackage, String callingFeatureId) {
+        if (!TelephonyPermissions.checkCallingOrSelfReadPhoneState(
+                mApp, subId, callingPackage, callingFeatureId, "isUceSettingEnabled")) {
+            Log.w(TAG, "isUceSettingEnabled: READ_PHONE_STATE app op disabled when accessing "
+                    + "isUceSettingEnabled");
+            return false;
+        }
+        final long token = Binder.clearCallingIdentity();
+        try {
+            return SubscriptionManager.getBooleanSubscriptionProperty(subId,
+                    SubscriptionManager.IMS_RCS_UCE_ENABLED, false /*defaultValue*/, mApp);
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
     }
 
     @Override
     public void setUceSettingEnabled(int subId, boolean isEnabled) {
         enforceModifyPermission();
-        SubscriptionManager.setSubscriptionProperty(subId, SubscriptionManager.IMS_RCS_UCE_ENABLED,
-                (isEnabled ? "1" : "0"));
+        final long token = Binder.clearCallingIdentity();
+        try {
+            SubscriptionManager.setSubscriptionProperty(subId,
+                    SubscriptionManager.IMS_RCS_UCE_ENABLED, (isEnabled ? "1" : "0"));
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
     }
 
     /**
diff --git a/src/com/android/phone/PhoneInterfaceManager.java b/src/com/android/phone/PhoneInterfaceManager.java
index 92b063a..3901ac4 100755
--- a/src/com/android/phone/PhoneInterfaceManager.java
+++ b/src/com/android/phone/PhoneInterfaceManager.java
@@ -3044,14 +3044,15 @@
     }
 
     @Override
-    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) {
         mAppOps.checkPackage(Binder.getCallingUid(), callingPackage);
         enforceVisualVoicemailPackage(callingPackage, subId);
         enforceSendSmsPermission();
         SmsController smsController = PhoneFactory.getSmsController();
-        smsController.sendVisualVoicemailSmsForSubscriber(callingPackage, subId, number, port, text,
-                sentIntent);
+        smsController.sendVisualVoicemailSmsForSubscriber(callingPackage, callingAttributionTag,
+                subId, number, port, text, sentIntent);
     }
 
     /**
@@ -3435,6 +3436,9 @@
             Phone phone = getPhone(subId);
             if (phone == null) return false;
             return phone.isImsCapabilityAvailable(capability, regTech);
+        } catch (com.android.ims.ImsException e) {
+            Log.w(LOG_TAG, "IMS isAvailable - service unavailable: " + e.getMessage());
+            return false;
         } finally {
             Binder.restoreCallingIdentity(token);
         }
@@ -8199,13 +8203,15 @@
     }
 
     @Override
-    public void enqueueSmsPickResult(String callingPackage, IIntegerConsumer pendingSubIdResult) {
+    public void enqueueSmsPickResult(String callingPackage, String callingAttributionTag,
+            IIntegerConsumer pendingSubIdResult) {
         if (callingPackage == null) {
             callingPackage = getCurrentPackageName();
         }
         SmsPermissions permissions = new SmsPermissions(getDefaultPhone(), mApp,
                 (AppOpsManager) mApp.getSystemService(Context.APP_OPS_SERVICE));
-        if (!permissions.checkCallingCanSendSms(callingPackage, "Sending message")) {
+        if (!permissions.checkCallingCanSendSms(callingPackage, callingAttributionTag,
+                "Sending message")) {
             throw new SecurityException("Requires SEND_SMS permission to perform this operation");
         }
         PickSmsSubscriptionActivity.addPendingResult(pendingSubIdResult);
diff --git a/src/com/android/phone/settings/AccessibilitySettingsFragment.java b/src/com/android/phone/settings/AccessibilitySettingsFragment.java
index e31de64..37212cf 100644
--- a/src/com/android/phone/settings/AccessibilitySettingsFragment.java
+++ b/src/com/android/phone/settings/AccessibilitySettingsFragment.java
@@ -25,6 +25,7 @@
 import android.preference.PreferenceScreen;
 import android.preference.SwitchPreference;
 import android.provider.Settings;
+import android.telephony.AccessNetworkConstants;
 import android.telephony.CarrierConfigManager;
 import android.telephony.PhoneStateListener;
 import android.telephony.SubscriptionManager;
@@ -39,6 +40,11 @@
 import com.android.phone.PhoneGlobals;
 import com.android.phone.R;
 
+import java.util.concurrent.Executor;
+import java.util.concurrent.Executors;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.TimeUnit;
+
 public class AccessibilitySettingsFragment extends PreferenceFragment {
     private static final String LOG_TAG = AccessibilitySettingsFragment.class.getSimpleName();
     private static final boolean DBG = (PhoneGlobals.DBG_LEVEL >= 2);
@@ -48,6 +54,8 @@
     private static final String BUTTON_RTT_KEY = "button_rtt_key";
     private static final String RTT_INFO_PREF = "button_rtt_more_information_key";
 
+    private static final int WFC_QUERY_TIMEOUT_MILLIS = 20;
+
     private final PhoneStateListener mPhoneStateListener = new PhoneStateListener() {
         /**
          * Disable the TTY setting when in/out of a call (and if carrier doesn't
@@ -110,10 +118,13 @@
         }
 
         if (shouldShowRttSetting()) {
-            // TODO: this is going to be a on/off switch for now. Ask UX about how to integrate
-            // this settings with TTY
-            if (TelephonyManager.getDefault().isNetworkRoaming(
-                    SubscriptionManager.getDefaultVoiceSubscriptionId())) {
+            TelephonyManager tm =
+                    (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE);
+            boolean isRoaming = tm.isNetworkRoaming(
+                    SubscriptionManager.getDefaultVoiceSubscriptionId());
+
+            boolean shouldDisableBecauseRoamingOffWfc = isRoaming && !isOnWfc();
+            if (shouldDisableBecauseRoamingOffWfc) {
                 mButtonRtt.setSummary(TextUtils.concat(getText(R.string.rtt_mode_summary), "\n",
                         getText(R.string.no_rtt_when_roaming)));
             }
@@ -230,6 +241,21 @@
         return false;
     }
 
+    private boolean isOnWfc() {
+        LinkedBlockingQueue<Integer> result = new LinkedBlockingQueue<>(1);
+        Executor executor = Executors.newSingleThreadExecutor();
+        mContext.getSystemService(android.telephony.ims.ImsManager.class)
+                .getImsMmTelManager(SubscriptionManager.getDefaultSubscriptionId())
+                .getRegistrationTransportType(executor, result::offer);
+        try {
+            Integer transportType = result.poll(WFC_QUERY_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);
+            return transportType != null
+                    && transportType == AccessNetworkConstants.TRANSPORT_TYPE_WLAN;
+        } catch (InterruptedException e) {
+            return false;
+        }
+    }
+
     private boolean shouldShowRttSetting() {
         // Go through all the subs -- if we want to display the RTT setting for any of them, do
         // display it.
diff --git a/src/com/android/services/telephony/RadioOnStateListener.java b/src/com/android/services/telephony/RadioOnStateListener.java
index 1bc6969..93e1e3c 100644
--- a/src/com/android/services/telephony/RadioOnStateListener.java
+++ b/src/com/android/services/telephony/RadioOnStateListener.java
@@ -172,6 +172,7 @@
      * call with {@link Callback#isOkToCall}
      */
     private void onServiceStateChanged(ServiceState state) {
+        if (mPhone == null) return;
         Log.d(this, "onServiceStateChanged(), new state = %s, Phone = %s", state,
                 mPhone.getPhoneId());
 
@@ -195,6 +196,7 @@
     }
 
     private void onRadioOn() {
+        if (mPhone == null) return;
         ServiceState state =  mPhone.getServiceState();
         Log.d(this, "onRadioOn, state = %s, Phone = %s", state,
                 mPhone.getPhoneId());
@@ -216,6 +218,7 @@
      * Handles the retry timer expiring.
      */
     private void onRetryTimeout() {
+        if (mPhone == null) return;
         int serviceState = mPhone.getServiceState().getState();
         Log.d(this, "onRetryTimeout():  phone state = %s, service state = %d, retries = %d.",
                 mPhone.getState(), serviceState, mNumRetriesSoFar);
diff --git a/src/com/android/services/telephony/TelecomAccountRegistry.java b/src/com/android/services/telephony/TelecomAccountRegistry.java
index 1013927..9e880a5 100644
--- a/src/com/android/services/telephony/TelecomAccountRegistry.java
+++ b/src/com/android/services/telephony/TelecomAccountRegistry.java
@@ -36,6 +36,7 @@
 import android.os.PersistableBundle;
 import android.os.UserHandle;
 import android.provider.Settings;
+import android.provider.Telephony;
 import android.telecom.PhoneAccount;
 import android.telecom.PhoneAccountHandle;
 import android.telecom.TelecomManager;
@@ -48,6 +49,7 @@
 import android.telephony.TelephonyManager;
 import android.telephony.ims.ImsException;
 import android.telephony.ims.ImsMmTelManager;
+import android.telephony.ims.ImsRcsManager;
 import android.telephony.ims.ImsReasonInfo;
 import android.telephony.ims.RegistrationManager;
 import android.telephony.ims.feature.MmTelFeature;
@@ -55,6 +57,7 @@
 import android.text.TextUtils;
 
 import com.android.ims.ImsManager;
+import com.android.internal.telephony.LocaleTracker;
 import com.android.internal.telephony.Phone;
 import com.android.internal.telephony.PhoneFactory;
 import com.android.internal.telephony.SubscriptionController;
@@ -67,6 +70,7 @@
 import java.util.LinkedList;
 import java.util.List;
 import java.util.Optional;
+import java.util.function.Predicate;
 
 /**
  * Owns all data we have registered with Telecom including handling dynamic addition and
@@ -540,16 +544,31 @@
         }
 
         /**
-         * Determines from carrier configuration whether RCS presence indication for video calls is
-         * supported.
+         * Determines from carrier configuration and user setting whether RCS presence indication
+         * for video calls is supported.
          *
          * @return {@code true} if RCS presence indication for video calls is supported.
          */
         private boolean isCarrierVideoPresenceSupported() {
             PersistableBundle b =
                     PhoneGlobals.getInstance().getCarrierConfigForSubId(mPhone.getSubId());
-            return b != null &&
-                    b.getBoolean(CarrierConfigManager.KEY_USE_RCS_PRESENCE_BOOL);
+            boolean carrierConfigEnabled = b != null
+                    && b.getBoolean(CarrierConfigManager.KEY_USE_RCS_PRESENCE_BOOL);
+            return carrierConfigEnabled && isUserContactDiscoverySettingEnabled();
+        }
+
+        /**
+         * @return true if the user has enabled contact discovery for the subscription associated
+         * with this account entry, false otherwise.
+         */
+        private boolean isUserContactDiscoverySettingEnabled() {
+            try {
+                ImsRcsManager manager = mImsManager.getImsRcsManager(mPhone.getSubId());
+                return manager.getUceAdapter().isUceSettingEnabled();
+            } catch (Exception e) {
+                Log.w(LOG_TAG, "isUserContactDiscoverySettingEnabled caught exception: " + e);
+                return false;
+            }
         }
 
         /**
@@ -751,6 +770,25 @@
             }
         }
 
+        public void updateVideoPresenceCapability() {
+            synchronized (mAccountsLock) {
+                if (!mAccounts.contains(this)) {
+                    // Account has already been torn down, don't try to register it again.
+                    // This handles the case where teardown has already happened, and we got a Ims
+                    // registration update that lost the race for the mAccountsLock.  In such a
+                    // scenario by the time we get here, the original phone account could have been
+                    // torn down.
+                    return;
+                }
+                boolean isVideoPresenceSupported = isCarrierVideoPresenceSupported();
+                if (mIsVideoPresenceSupported != isVideoPresenceSupported) {
+                    Log.i(this, "updateVideoPresenceCapability for subId=" + mPhone.getSubId()
+                            + ", new value= " + isVideoPresenceSupported);
+                    mAccount = registerPstnPhoneAccount(mIsEmergency, mIsDummy);
+                }
+            }
+        }
+
         public void updateRttCapability() {
             boolean isRttEnabled = isRttCurrentlySupported();
             if (isRttEnabled != mIsRttCapable) {
@@ -773,14 +811,51 @@
          * device.
          */
         private boolean isRttCurrentlySupported() {
+            // First check the emergency case -- if it's supported and turned on,
+            // we want to present RTT as available on the emergency-only phone account
+            if (mIsEmergency) {
+                // First check whether the device supports it
+                boolean devicesSupportsRtt =
+                        mContext.getResources().getBoolean(R.bool.config_support_rtt);
+                boolean deviceSupportsEmergencyRtt = mContext.getResources().getBoolean(
+                        R.bool.config_support_simless_emergency_rtt);
+                if (!(deviceSupportsEmergencyRtt && devicesSupportsRtt)) {
+                    Log.i(this, "isRttCurrentlySupported -- emergency acct and no device support");
+                    return false;
+                }
+                // Next check whether we're in or near a country that supports it
+                String country =
+                        mPhone.getServiceStateTracker().getLocaleTracker()
+                                .getCurrentCountry().toLowerCase();
+                String[] supportedCountries = mContext.getResources().getStringArray(
+                        R.array.config_simless_emergency_rtt_supported_countries);
+                if (supportedCountries == null || Arrays.stream(supportedCountries).noneMatch(
+                        Predicate.isEqual(country))) {
+                    Log.i(this, "isRttCurrentlySupported -- emergency acct and"
+                            + " not supported in this country: " + country);
+                    return false;
+                }
+                
+                return true;
+            }
+
             boolean hasVoiceAvailability = isImsVoiceAvailable();
 
             boolean isRttSupported = PhoneGlobals.getInstance().phoneMgr
                     .isRttEnabled(mPhone.getSubId());
 
             boolean isRoaming = mTelephonyManager.isNetworkRoaming(mPhone.getSubId());
+            boolean isOnWfc = mPhone.getImsRegistrationTech()
+                    == ImsRegistrationImplBase.REGISTRATION_TECH_IWLAN;
 
-            return hasVoiceAvailability && isRttSupported && !isRoaming;
+            boolean shouldDisableBecauseRoamingOffWfc = isRoaming && !isOnWfc;
+            Log.i(this, "isRttCurrentlySupported -- regular acct,"
+                    + " hasVoiceAvailability: " + hasVoiceAvailability + "\n"
+                    + " isRttSupported: " + isRttSupported + "\n"
+                    + " isRoaming: " + isRoaming + "\n"
+                    + " isOnWfc: " + isOnWfc + "\n");
+
+            return hasVoiceAvailability && isRttSupported && !shouldDisableBecauseRoamingOffWfc;
         }
 
         /**
@@ -947,6 +1022,7 @@
     private static TelecomAccountRegistry sInstance;
     private final Context mContext;
     private final TelecomManager mTelecomManager;
+    private final android.telephony.ims.ImsManager mImsManager;
     private final TelephonyManager mTelephonyManager;
     private final SubscriptionManager mSubscriptionManager;
     private List<AccountEntry> mAccounts = new LinkedList<AccountEntry>();
@@ -962,6 +1038,7 @@
     TelecomAccountRegistry(Context context) {
         mContext = context;
         mTelecomManager = context.getSystemService(TelecomManager.class);
+        mImsManager = context.getSystemService(android.telephony.ims.ImsManager.class);
         mTelephonyManager = TelephonyManager.from(context);
         mSubscriptionManager = SubscriptionManager.from(context);
     }
@@ -1196,9 +1273,14 @@
 
         //We also need to listen for locale changes
         //(e.g. system language changed -> SIM card name changed)
-        mContext.registerReceiver(mLocaleChangeReceiver,
-                new IntentFilter(Intent.ACTION_LOCALE_CHANGED));
+        IntentFilter localeChangeFilter = new IntentFilter(Intent.ACTION_LOCALE_CHANGED);
+        localeChangeFilter.addAction(TelephonyManager.ACTION_NETWORK_COUNTRY_CHANGED);
+        mContext.registerReceiver(mLocaleChangeReceiver, localeChangeFilter);
 
+        registerContentObservers();
+    }
+
+    private void registerContentObservers() {
         // Listen to the RTT system setting so that we update it when the user flips it.
         ContentObserver rttUiSettingObserver = new ContentObserver(
                 new Handler(Looper.getMainLooper())) {
@@ -1215,6 +1297,23 @@
         Uri rttSettingUri = Settings.Secure.getUriFor(Settings.Secure.RTT_CALLING_MODE);
         mContext.getContentResolver().registerContentObserver(
                 rttSettingUri, false, rttUiSettingObserver);
+
+        // Listen to the changes to the user's Contacts Discovery Setting.
+        ContentObserver contactDiscoveryObserver = new ContentObserver(
+                new Handler(Looper.getMainLooper())) {
+            @Override
+            public void onChange(boolean selfChange) {
+                synchronized (mAccountsLock) {
+                    for (AccountEntry account : mAccounts) {
+                        account.updateVideoPresenceCapability();
+                    }
+                }
+            }
+        };
+        Uri contactDiscUri = Uri.withAppendedPath(Telephony.SimInfo.CONTENT_URI,
+                Telephony.SimInfo.COLUMN_IMS_RCS_UCE_ENABLED);
+        mContext.getContentResolver().registerContentObserver(
+                contactDiscUri, true /*notifyForDescendants*/, contactDiscoveryObserver);
     }
 
     /**
diff --git a/src/com/android/services/telephony/TelephonyConnectionService.java b/src/com/android/services/telephony/TelephonyConnectionService.java
index 665bf22..eb6482f 100644
--- a/src/com/android/services/telephony/TelephonyConnectionService.java
+++ b/src/com/android/services/telephony/TelephonyConnectionService.java
@@ -2176,30 +2176,24 @@
                             return 1;
                         }
                         // sort by number of RadioAccessFamily Capabilities.
-                        int compare = Integer.bitCount(o1.capabilities) -
-                                Integer.bitCount(o2.capabilities);
+                        int compare = RadioAccessFamily.compare(o1.capabilities, o2.capabilities);
                         if (compare == 0) {
-                            // Sort by highest RAF Capability if the number is the same.
-                            compare = RadioAccessFamily.getHighestRafCapability(o1.capabilities) -
-                                    RadioAccessFamily.getHighestRafCapability(o2.capabilities);
-                            if (compare == 0) {
-                                if (firstOccupiedSlot != null) {
-                                    // If the RAF capability is the same, choose based on whether or
-                                    // not any of the slots are occupied with a SIM card (if both
-                                    // are, always choose the first).
-                                    if (o1.slotId == firstOccupiedSlot.getPhoneId()) {
-                                        return 1;
-                                    } else if (o2.slotId == firstOccupiedSlot.getPhoneId()) {
-                                        return -1;
-                                    }
-                                } else {
-                                    // No slots have SIMs detected in them, so weight the default
-                                    // Phone Id greater than the others.
-                                    if (o1.slotId == defaultPhoneId) {
-                                        return 1;
-                                    } else if (o2.slotId == defaultPhoneId) {
-                                        return -1;
-                                    }
+                            if (firstOccupiedSlot != null) {
+                                // If the RAF capability is the same, choose based on whether or
+                                // not any of the slots are occupied with a SIM card (if both
+                                // are, always choose the first).
+                                if (o1.slotId == firstOccupiedSlot.getPhoneId()) {
+                                    return 1;
+                                } else if (o2.slotId == firstOccupiedSlot.getPhoneId()) {
+                                    return -1;
+                                }
+                            } else {
+                                // No slots have SIMs detected in them, so weight the default
+                                // Phone Id greater than the others.
+                                if (o1.slotId == defaultPhoneId) {
+                                    return 1;
+                                } else if (o2.slotId == defaultPhoneId) {
+                                    return -1;
                                 }
                             }
                         }
diff --git a/tests/src/com/android/services/telephony/RadioOnStateListenerTest.java b/tests/src/com/android/services/telephony/RadioOnStateListenerTest.java
index dffbbf3..d55a2fa 100644
--- a/tests/src/com/android/services/telephony/RadioOnStateListenerTest.java
+++ b/tests/src/com/android/services/telephony/RadioOnStateListenerTest.java
@@ -69,9 +69,11 @@
     @Override
     @After
     public void tearDown() throws Exception {
+        mListener.setTimeBetweenRetriesMillis(5000);
+        mListener.setMaxNumRetries(5);
+        mListener.getHandler().removeCallbacksAndMessages(null);
         // Wait for the queue to clear...
         waitForHandlerAction(mListener.getHandler(), TIMEOUT_MS /*ms timeout*/);
-        mListener.getHandler().removeCallbacksAndMessages(null);
         mListener = null;
         super.tearDown();
     }