Merge "Internally use ImsException and rethrow as ServiceSpecificException"
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 1b2e99a..25018a3 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -1249,6 +1249,8 @@
        <br><br> - RTT calls are saved as a message transcript
        &lt;br> - RTT is not available for video calls</string>
 
+    <string name="no_rtt_when_roaming">Note: RTT is not available while roaming</string>
+
     <!-- Service option entries.  -->
     <string-array name="tty_mode_entries">
         <item>TTY Off</item>
diff --git a/src/com/android/phone/CarrierConfigLoader.java b/src/com/android/phone/CarrierConfigLoader.java
index 8b74af4..4025bbb 100644
--- a/src/com/android/phone/CarrierConfigLoader.java
+++ b/src/com/android/phone/CarrierConfigLoader.java
@@ -20,7 +20,6 @@
 import static android.service.carrier.CarrierService.ICarrierServiceWrapper.RESULT_ERROR;
 
 import android.annotation.NonNull;
-import android.app.ActivityManager;
 import android.content.BroadcastReceiver;
 import android.content.ComponentName;
 import android.content.Context;
@@ -40,7 +39,6 @@
 import android.os.RemoteException;
 import android.os.ResultReceiver;
 import android.os.ServiceManager;
-import android.os.UserHandle;
 import android.preference.PreferenceManager;
 import android.service.carrier.CarrierIdentifier;
 import android.service.carrier.CarrierService;
@@ -514,7 +512,7 @@
         pkgFilter.addAction(Intent.ACTION_PACKAGE_REMOVED);
         pkgFilter.addAction(Intent.ACTION_PACKAGE_REPLACED);
         pkgFilter.addDataScheme("package");
-        context.registerReceiverAsUser(mPackageReceiver, UserHandle.ALL, pkgFilter, null, null);
+        context.registerReceiver(mPackageReceiver, pkgFilter);
 
         int numPhones = TelephonyManager.from(context).getPhoneCount();
         mConfigFromDefaultApp = new PersistableBundle[numPhones];
@@ -592,7 +590,7 @@
         }
         intent.putExtra(CarrierConfigManager.EXTRA_SLOT_INDEX, phoneId);
         log("Broadcast CARRIER_CONFIG_CHANGED for phone " + phoneId);
-        ActivityManager.broadcastStickyIntent(intent, UserHandle.USER_ALL);
+        mContext.sendBroadcast(intent);
         mHasSentConfigChange[phoneId] = true;
     }
 
diff --git a/src/com/android/phone/PhoneGlobals.java b/src/com/android/phone/PhoneGlobals.java
index fed41b0..04850f3 100644
--- a/src/com/android/phone/PhoneGlobals.java
+++ b/src/com/android/phone/PhoneGlobals.java
@@ -527,6 +527,9 @@
                 maybeTurnCellOn(context, isAirplaneNewlyOn);
                 break;
         }
+        for (Phone phone : PhoneFactory.getPhones()) {
+            phone.getServiceStateTracker().onAirplaneModeChanged(isAirplaneNewlyOn);
+        }
     }
 
     /*
diff --git a/src/com/android/phone/PhoneInterfaceManager.java b/src/com/android/phone/PhoneInterfaceManager.java
index d546bf6..17067ec 100755
--- a/src/com/android/phone/PhoneInterfaceManager.java
+++ b/src/com/android/phone/PhoneInterfaceManager.java
@@ -1411,7 +1411,7 @@
         // from the context of the phone app.
         enforceCallPermission();
 
-        if (mAppOps.noteOp(AppOpsManager.OP_CALL_PHONE, Binder.getCallingUid(), callingPackage)
+        if (mAppOps.noteOp(AppOpsManager.OPSTR_CALL_PHONE, Binder.getCallingUid(), callingPackage)
                 != AppOpsManager.MODE_ALLOWED) {
             return;
         }
@@ -2025,14 +2025,18 @@
     /**
      * Returns the target SDK version number for a given package name.
      *
+     * This call MUST be invoked before clearing the calling UID.
+     *
      * @return target SDK if the package is found or INT_MAX.
      */
     private int getTargetSdk(String packageName) {
         try {
-            final ApplicationInfo ai = mApp.getPackageManager().getApplicationInfo(
-                            packageName, 0);
+            final ApplicationInfo ai = mApp.getPackageManager().getApplicationInfoAsUser(
+                    packageName, 0, UserHandle.getUserId(Binder.getCallingUid()));
             if (ai != null) return ai.targetSdkVersion;
         } catch (PackageManager.NameNotFoundException unexpected) {
+            loge("Failed to get package info for pkg="
+                    + packageName + ", uid=" + Binder.getCallingUid());
         }
         return Integer.MAX_VALUE;
     }
@@ -2046,7 +2050,7 @@
                     "getNeighboringCellInfo() is unavailable to callers targeting Q+ SDK levels.");
         }
 
-        if (mAppOps.noteOp(AppOpsManager.OP_NEIGHBORING_CELLS, Binder.getCallingUid(),
+        if (mAppOps.noteOp(AppOpsManager.OPSTR_NEIGHBORING_CELLS, Binder.getCallingUid(),
                 callingPackage) != AppOpsManager.MODE_ALLOWED) {
             return null;
         }
diff --git a/src/com/android/phone/settings/AccessibilitySettingsFragment.java b/src/com/android/phone/settings/AccessibilitySettingsFragment.java
index d540fba..3069091 100644
--- a/src/com/android/phone/settings/AccessibilitySettingsFragment.java
+++ b/src/com/android/phone/settings/AccessibilitySettingsFragment.java
@@ -28,11 +28,13 @@
 import android.telephony.PhoneStateListener;
 import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyManager;
+import android.text.TextUtils;
 import android.util.Log;
 
 import com.android.ims.ImsManager;
 import com.android.internal.telephony.Phone;
 import com.android.internal.telephony.PhoneFactory;
+import com.android.internal.telephony.SubscriptionController;
 import com.android.phone.PhoneGlobals;
 import com.android.phone.R;
 
@@ -109,10 +111,14 @@
             mButtonHac = null;
         }
 
-        if (PhoneGlobals.getInstance().phoneMgr
-                .isRttSupported(SubscriptionManager.getDefaultVoiceSubscriptionId())) {
+        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())) {
+                mButtonRtt.setSummary(TextUtils.concat(getText(R.string.rtt_mode_summary), "\n",
+                        getText(R.string.no_rtt_when_roaming)));
+            }
             boolean rttOn = Settings.Secure.getInt(
                     mContext.getContentResolver(), Settings.Secure.RTT_CALLING_MODE, 0) != 0;
             mButtonRtt.setChecked(rttOn);
@@ -192,6 +198,21 @@
         return false;
     }
 
+    private boolean shouldShowRttSetting() {
+        int subscriptionId = SubscriptionManager.getDefaultVoiceSubscriptionId();
+        if (subscriptionId == SubscriptionManager.INVALID_SUBSCRIPTION_ID
+                || subscriptionId == SubscriptionManager.DEFAULT_SUBSCRIPTION_ID) {
+            for (int subId : SubscriptionController.getInstance().getActiveSubIdList(true)) {
+                if (PhoneGlobals.getInstance().phoneMgr.isRttSupported(subId)) {
+                    return true;
+                }
+            }
+            return false;
+        } else {
+            return PhoneGlobals.getInstance().phoneMgr.isRttSupported(subscriptionId);
+        }
+    }
+
     /**
      * Determines if the device supports TTY per carrier config.
      * @return {@code true} if the carrier supports TTY, {@code false} otherwise.
diff --git a/src/com/android/services/telephony/ImsConference.java b/src/com/android/services/telephony/ImsConference.java
index 1f157e3..8ef0464 100644
--- a/src/com/android/services/telephony/ImsConference.java
+++ b/src/com/android/services/telephony/ImsConference.java
@@ -259,6 +259,8 @@
     private boolean mCouldManageConference;
     private FeatureFlagProxy mFeatureFlagProxy;
     private boolean mIsEmulatingSinglePartyCall = false;
+    private boolean mIsUsingSimCallManager = false;
+
     /**
      * Where {@link #mIsEmulatingSinglePartyCall} is {@code true}, contains the
      * {@link ConferenceParticipantConnection#getUserEntity()} and
@@ -616,6 +618,7 @@
     private void updateManageConference() {
         boolean couldManageConference = can(Connection.CAPABILITY_MANAGE_CONFERENCE);
         boolean canManageConference = mFeatureFlagProxy.isUsingSinglePartyCallEmulation()
+                && mIsEmulatingSinglePartyCall
                 ? mConferenceParticipantConnections.size() > 1
                 : mConferenceParticipantConnections.size() != 0;
         Log.v(this, "updateManageConference was :%s is:%s", couldManageConference ? "Y" : "N",
@@ -675,6 +678,9 @@
 
             mConferenceHostAddress = new Uri[hostAddresses.size()];
             mConferenceHostAddress = hostAddresses.toArray(mConferenceHostAddress);
+
+            mIsUsingSimCallManager = mTelecomAccountRegistry.isUsingSimCallManager(
+                    mConferenceHostPhoneAccountHandle);
         }
 
         mConferenceHost.addConnectionListener(mConferenceHostListener);
@@ -829,8 +835,18 @@
      * 1. Tell telecom we're a conference again.
      * 2. Restore {@link Connection#CAPABILITY_MANAGE_CONFERENCE} capability.
      * 3. Null out the name/address.
+     *
+     * Note: Single party call emulation is disabled if the conference is taking place via a
+     * sim call manager.  Emulating a single party call requires properties of the conference to be
+     * changed (connect time, address, conference state) which cannot be guaranteed to be relayed
+     * correctly by the sim call manager to Telecom.
      */
     private void stopEmulatingSinglePartyCall() {
+        if (mIsUsingSimCallManager) {
+            Log.i(this, "stopEmulatingSinglePartyCall: using sim call manager; skip.");
+            return;
+        }
+
         Log.i(this, "stopEmulatingSinglePartyCall: conference now has more than one"
                 + " participant; make it look conference-like again.");
         mIsEmulatingSinglePartyCall = false;
@@ -867,8 +883,18 @@
      * 2. Tell telecom we're not a conference.
      * 3. Remove {@link Connection#CAPABILITY_MANAGE_CONFERENCE} capability.
      * 4. Set the name/address to that of the single participant.
+     *
+     * Note: Single party call emulation is disabled if the conference is taking place via a
+     * sim call manager.  Emulating a single party call requires properties of the conference to be
+     * changed (connect time, address, conference state) which cannot be guaranteed to be relayed
+     * correctly by the sim call manager to Telecom.
      */
     private void startEmulatingSinglePartyCall() {
+        if (mIsUsingSimCallManager) {
+            Log.i(this, "startEmulatingSinglePartyCall: using sim call manager; skip.");
+            return;
+        }
+
         Log.i(this, "startEmulatingSinglePartyCall: conference has a single "
                 + "participant; downgrade to single party call.");
 
diff --git a/src/com/android/services/telephony/TelecomAccountRegistry.java b/src/com/android/services/telephony/TelecomAccountRegistry.java
index b12fc06..3505fe7 100644
--- a/src/com/android/services/telephony/TelecomAccountRegistry.java
+++ b/src/com/android/services/telephony/TelecomAccountRegistry.java
@@ -98,6 +98,7 @@
         private boolean mIsVideoConferencingSupported;
         private boolean mIsMergeOfWifiCallsAllowedWhenVoWifiOff;
         private boolean mIsManageImsConferenceCallSupported;
+        private boolean mIsUsingSimCallManager;
         private boolean mIsShowPreciseFailedCause;
 
         AccountEntry(Phone phone, boolean isEmergency, boolean isDummy) {
@@ -350,6 +351,7 @@
             mIsMergeOfWifiCallsAllowedWhenVoWifiOff =
                     isCarrierMergeOfWifiCallsAllowedWhenVoWifiOff();
             mIsManageImsConferenceCallSupported = isCarrierManageImsConferenceCallSupported();
+            mIsUsingSimCallManager = isCarrierUsingSimCallManager();
             mIsShowPreciseFailedCause = isCarrierShowPreciseFailedCause();
 
             if (isEmergency && mContext.getResources().getBoolean(
@@ -581,6 +583,19 @@
         }
 
         /**
+         * Determines from carrier config whether the carrier uses a sim call manager.
+         *
+         * @return {@code true} if the carrier uses a sim call manager,
+         *         {@code false} otherwise.
+         */
+        private boolean isCarrierUsingSimCallManager() {
+            PersistableBundle b =
+                    PhoneGlobals.getInstance().getCarrierConfigForSubId(mPhone.getSubId());
+            return !TextUtils.isEmpty(
+                    b.getString(CarrierConfigManager.KEY_DEFAULT_SIM_CALL_MANAGER_STRING));
+        }
+
+        /**
          * Determines from carrier config whether showing percise call diconnect cause to user
          * is supported.
          *
@@ -731,6 +746,15 @@
         }
 
         /**
+         * Indicates whether this account uses a sim call manger.
+         * @return {@code true} if the account uses a sim call manager,
+         *         {@code false} otherwise.
+         */
+        public boolean isUsingSimCallManager() {
+            return mIsUsingSimCallManager;
+        }
+
+        /**
          * Indicates whether this account supports showing the precise call disconnect cause
          * to user (i.e. conferencing).
          * @return {@code true} if the account supports showing the precise call disconnect cause,
@@ -1003,7 +1027,7 @@
      * @param handle The phone account handle to find the subscription address for.
      * @return The address.
      */
-    Uri getAddress(PhoneAccountHandle handle) {
+    public Uri getAddress(PhoneAccountHandle handle) {
         synchronized (mAccountsLock) {
             for (AccountEntry entry : mAccounts) {
                 if (entry.getPhoneAccountHandle().equals(handle)) {
@@ -1015,6 +1039,24 @@
     }
 
     /**
+     * Returns whethere a the subscription associated with a {@link PhoneAccountHandle} is using a
+     * sim call manager.
+     *
+     * @param handle The phone account handle to find the subscription address for.
+     * @return {@code true} if a sim call manager is in use, {@code false} otherwise.
+     */
+    public boolean isUsingSimCallManager(PhoneAccountHandle handle) {
+        synchronized (mAccountsLock) {
+            for (AccountEntry entry : mAccounts) {
+                if (entry.getPhoneAccountHandle().equals(handle)) {
+                    return entry.isUsingSimCallManager();
+                }
+            }
+        }
+        return false;
+    }
+
+    /**
      * Sets up all the phone accounts for SIMs on first boot.
      */
     public void setupOnBoot() {
diff --git a/src/com/android/services/telephony/TelephonyConnection.java b/src/com/android/services/telephony/TelephonyConnection.java
index 632e9ac..7bb6b7d 100644
--- a/src/com/android/services/telephony/TelephonyConnection.java
+++ b/src/com/android/services/telephony/TelephonyConnection.java
@@ -2203,7 +2203,6 @@
         boolean isIms = phone.getPhoneType() == PhoneConstants.PHONE_TYPE_IMS;
         boolean isVoWifiEnabled = false;
         if (isIms) {
-            ImsPhone imsPhone = (ImsPhone) phone;
             isVoWifiEnabled = ImsUtil.isWfcEnabled(phone.getContext(), phone.getPhoneId());
         }
         PhoneAccountHandle phoneAccountHandle = isIms ? PhoneUtils
diff --git a/tests/src/com/android/services/telephony/ImsConferenceTest.java b/tests/src/com/android/services/telephony/ImsConferenceTest.java
index 56a6240..909ab65 100644
--- a/tests/src/com/android/services/telephony/ImsConferenceTest.java
+++ b/tests/src/com/android/services/telephony/ImsConferenceTest.java
@@ -32,6 +32,7 @@
 import android.os.Looper;
 import android.telecom.ConferenceParticipant;
 import android.telecom.Connection;
+import android.telecom.PhoneAccountHandle;
 import android.test.suitebuilder.annotation.SmallTest;
 
 import com.android.internal.telephony.PhoneConstants;
@@ -53,9 +54,6 @@
     @Mock
     private TelecomAccountRegistry mMockTelecomAccountRegistry;
 
-    @Mock
-    private com.android.internal.telephony.Connection mOriginalConnection;
-
     private TestTelephonyConnection mConferenceHost;
 
     @Before
@@ -66,11 +64,16 @@
         }
         mConferenceHost = new TestTelephonyConnection();
         mConferenceHost.setManageImsConferenceCallSupported(true);
+        when(mMockTelecomAccountRegistry.getAddress(any(PhoneAccountHandle.class)))
+                .thenReturn(null);
     }
 
     @Test
     @SmallTest
     public void testSinglePartyEmulation() {
+        when(mMockTelecomAccountRegistry.isUsingSimCallManager(any(PhoneAccountHandle.class)))
+                .thenReturn(false);
+
         ImsConference imsConference = new ImsConference(mMockTelecomAccountRegistry,
                 mMockTelephonyConnectionServiceProxy, mConferenceHost,
                 null /* phoneAccountHandle */, () -> true /* featureFlagProxy */);
@@ -100,9 +103,51 @@
         assertEquals(2, imsConference.getNumberOfParticipants());
     }
 
+    /**
+     * Verify that we do not use single party emulation when a sim call manager is in use.
+     */
+    @Test
+    @SmallTest
+    public void testNoSinglePartyEmulationWithSimCallManager() {
+        // Make it look like there is a sim call manager in use.
+        when(mMockTelecomAccountRegistry.isUsingSimCallManager(
+                any(PhoneAccountHandle.class))).thenReturn(true);
+
+        ImsConference imsConference = new ImsConference(mMockTelecomAccountRegistry,
+                mMockTelephonyConnectionServiceProxy, mConferenceHost,
+                null /* phoneAccountHandle */, () -> true /* featureFlagProxy */);
+
+        ConferenceParticipant participant1 = new ConferenceParticipant(
+                Uri.parse("tel:6505551212"),
+                "A",
+                Uri.parse("sip:6505551212@testims.com"),
+                Connection.STATE_ACTIVE);
+        ConferenceParticipant participant2 = new ConferenceParticipant(
+                Uri.parse("tel:6505551213"),
+                "A",
+                Uri.parse("sip:6505551213@testims.com"),
+                Connection.STATE_ACTIVE);
+        imsConference.handleConferenceParticipantsUpdate(mConferenceHost,
+                Arrays.asList(participant1, participant2));
+        assertEquals(2, imsConference.getNumberOfParticipants());
+
+        // Because we're not using single party emulation, should still be one participant.
+        imsConference.handleConferenceParticipantsUpdate(mConferenceHost,
+                Arrays.asList(participant1));
+        assertEquals(1, imsConference.getNumberOfParticipants());
+
+        // Back to 2!
+        imsConference.handleConferenceParticipantsUpdate(mConferenceHost,
+                Arrays.asList(participant1, participant2));
+        assertEquals(2, imsConference.getNumberOfParticipants());
+    }
+
     @Test
     @SmallTest
     public void testNormalConference() {
+        when(mMockTelecomAccountRegistry.isUsingSimCallManager(any(PhoneAccountHandle.class)))
+                .thenReturn(false);
+
         ImsConference imsConference = new ImsConference(mMockTelecomAccountRegistry,
                 mMockTelephonyConnectionServiceProxy, mConferenceHost,
                 null /* phoneAccountHandle */, () -> false /* featureFlagProxy */);
diff --git a/tests/src/com/android/services/telephony/TestTelephonyConnection.java b/tests/src/com/android/services/telephony/TestTelephonyConnection.java
index c064ef6..b6e4bf3 100644
--- a/tests/src/com/android/services/telephony/TestTelephonyConnection.java
+++ b/tests/src/com/android/services/telephony/TestTelephonyConnection.java
@@ -82,9 +82,11 @@
                 any(Connection.PostDialListener.class));
         when(mMockPhone.getRingingCall()).thenReturn(mMockCall);
         when(mMockPhone.getContext()).thenReturn(mMockContext);
+        when(mMockPhone.getCurrentSubscriberUris()).thenReturn(null);
         when(mMockContext.getResources()).thenReturn(mMockResources);
         when(mMockResources.getBoolean(anyInt())).thenReturn(false);
         when(mMockPhone.getDefaultPhone()).thenReturn(mMockPhone);
+        when(mMockPhone.getPhoneType()).thenReturn(PhoneConstants.PHONE_TYPE_IMS);
         when(mMockCall.getState()).thenReturn(Call.State.ACTIVE);
         when(mMockCall.getPhone()).thenReturn(mMockPhone);
     }