Merge "[RCS UCE] The creation of UceController by TelephonyRcsService."
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index a222705..2b2df11 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -24,7 +24,6 @@
 
     <original-package android:name="com.android.phone" />
 
-    <protected-broadcast android:name="android.telecom.action.TTY_PREFERRED_MODE_CHANGED" />
     <protected-broadcast android:name="android.telecom.action.CURRENT_TTY_MODE_CHANGED" />
     <protected-broadcast android:name="android.intent.action.SERVICE_STATE" />
     <protected-broadcast android:name="android.intent.action.RADIO_TECHNOLOGY" />
diff --git a/src/com/android/phone/PhoneInterfaceManager.java b/src/com/android/phone/PhoneInterfaceManager.java
index a08893b..4ee4141 100755
--- a/src/com/android/phone/PhoneInterfaceManager.java
+++ b/src/com/android/phone/PhoneInterfaceManager.java
@@ -289,6 +289,10 @@
     private static final int EVENT_GET_CALL_WAITING_DONE = 88;
     private static final int CMD_SET_CALL_WAITING = 89;
     private static final int EVENT_SET_CALL_WAITING_DONE = 90;
+    private static final int CMD_ENABLE_NR_DUAL_CONNECTIVITY = 91;
+    private static final int EVENT_ENABLE_NR_DUAL_CONNECTIVITY_DONE = 92;
+    private static final int CMD_IS_NR_DUAL_CONNECTIVITY_ENABLED = 93;
+    private static final int EVENT_IS_NR_DUAL_CONNECTIVITY_ENABLED_DONE = 94;
 
     // Parameters of select command.
     private static final int SELECT_COMMAND = 0xA4;
@@ -751,6 +755,90 @@
                     handleNullReturnEvent(msg, "resetModemConfig");
                     break;
 
+                case CMD_IS_NR_DUAL_CONNECTIVITY_ENABLED: {
+                    request = (MainThreadRequest) msg.obj;
+                    onCompleted = obtainMessage(EVENT_IS_NR_DUAL_CONNECTIVITY_ENABLED_DONE,
+                            request);
+                    Phone phone = getPhoneFromRequest(request);
+                    if (phone != null) {
+                        phone.isNrDualConnectivityEnabled(onCompleted, request.workSource);
+                    } else {
+                        loge("isNRDualConnectivityEnabled: No phone object");
+                        request.result = false;
+                        notifyRequester(request);
+                    }
+                    break;
+                }
+
+                case EVENT_IS_NR_DUAL_CONNECTIVITY_ENABLED_DONE:
+                    ar = (AsyncResult) msg.obj;
+                    request = (MainThreadRequest) ar.userObj;
+                    if (ar.exception == null && ar.result != null) {
+                        request.result = ar.result;
+                    } else {
+                        // request.result must be set to something non-null
+                        // for the calling thread to unblock
+                        if (request.result != null) {
+                            request.result = ar.result;
+                        } else {
+                            request.result = false;
+                        }
+                        if (ar.result == null) {
+                            loge("isNRDualConnectivityEnabled: Empty response");
+                        } else if (ar.exception instanceof CommandException) {
+                            loge("isNRDualConnectivityEnabled: CommandException: "
+                                    + ar.exception);
+                        } else {
+                            loge("isNRDualConnectivityEnabled: Unknown exception");
+                        }
+                    }
+                    notifyRequester(request);
+                    break;
+
+                case CMD_ENABLE_NR_DUAL_CONNECTIVITY: {
+                    request = (MainThreadRequest) msg.obj;
+                    onCompleted = obtainMessage(EVENT_ENABLE_NR_DUAL_CONNECTIVITY_DONE, request);
+                    Phone phone = getPhoneFromRequest(request);
+                    if (phone != null) {
+                        phone.setNrDualConnectivityState((int) request.argument, onCompleted,
+                                request.workSource);
+                    } else {
+                        loge("enableNrDualConnectivity: No phone object");
+                        request.result =
+                                TelephonyManager.ENABLE_NR_DUAL_CONNECTIVITY_RADIO_NOT_AVAILABLE;
+                        notifyRequester(request);
+                    }
+                    break;
+                }
+
+                case EVENT_ENABLE_NR_DUAL_CONNECTIVITY_DONE: {
+                    ar = (AsyncResult) msg.obj;
+                    request = (MainThreadRequest) ar.userObj;
+                    if (ar.exception == null) {
+                        request.result =
+                                TelephonyManager.ENABLE_NR_DUAL_CONNECTIVITY_SUCCESS;
+                    } else {
+                        request.result =
+                                TelephonyManager
+                                        .ENABLE_NR_DUAL_CONNECTIVITY_RADIO_ERROR;
+                        if (ar.exception instanceof CommandException) {
+                            CommandException.Error error =
+                                    ((CommandException) (ar.exception)).getCommandError();
+                            if (error == CommandException.Error.RADIO_NOT_AVAILABLE) {
+                                request.result =
+                                        TelephonyManager
+                                                .ENABLE_NR_DUAL_CONNECTIVITY_RADIO_NOT_AVAILABLE;
+                            }
+                            loge("enableNrDualConnectivity" + ": CommandException: "
+                                    + ar.exception);
+                        } else {
+                            loge("enableNrDualConnectivity" + ": Unknown exception");
+                        }
+                    }
+                    notifyRequester(request);
+                    break;
+                }
+
                 case CMD_GET_PREFERRED_NETWORK_TYPE:
                     request = (MainThreadRequest) msg.obj;
                     onCompleted = obtainMessage(EVENT_GET_PREFERRED_NETWORK_TYPE_DONE, request);
@@ -5733,6 +5821,58 @@
     }
 
     /**
+     * Enable/Disable E-UTRA-NR Dual Connectivity
+     * @param subId subscription id of the sim card
+     * @param nrDualConnectivityState expected NR dual connectivity state
+     * This can be passed following states
+     * <ol>
+     * <li>Enable NR dual connectivity {@link TelephonyManager#NR_DUAL_CONNECTIVITY_ENABLE}
+     * <li>Disable NR dual connectivity {@link TelephonyManager#NR_DUAL_CONNECTIVITY_DISABLE}
+     * <li>Disable NR dual connectivity and force secondary cell to be released
+     * {@link TelephonyManager#NR_DUAL_CONNECTIVITY_DISABLE_IMMEDIATE}
+     * </ol>
+     * @return operation result.
+     */
+    @Override
+    public int setNrDualConnectivityState(int subId,
+            @TelephonyManager.NrDualConnectivityState int nrDualConnectivityState) {
+        TelephonyPermissions.enforceCallingOrSelfModifyPermissionOrCarrierPrivilege(
+                mApp, subId, "enableNRDualConnectivity");
+        WorkSource workSource = getWorkSource(Binder.getCallingUid());
+        final long identity = Binder.clearCallingIdentity();
+        try {
+            int result = (int) sendRequest(CMD_ENABLE_NR_DUAL_CONNECTIVITY,
+                    nrDualConnectivityState, subId,
+                    workSource);
+            if (DBG) log("enableNRDualConnectivity result: " + result);
+            return result;
+        } finally {
+            Binder.restoreCallingIdentity(identity);
+        }
+    }
+
+    /**
+     * Is E-UTRA-NR Dual Connectivity enabled
+     * @return true if dual connectivity is enabled else false
+     */
+    @Override
+    public boolean isNrDualConnectivityEnabled(int subId) {
+        TelephonyPermissions
+                .enforeceCallingOrSelfReadPrivilegedPhoneStatePermissionOrCarrierPrivilege(
+                        mApp, subId, "isNRDualConnectivityEnabled");
+        WorkSource workSource = getWorkSource(Binder.getCallingUid());
+        final long identity = Binder.clearCallingIdentity();
+        try {
+            boolean isEnabled = (boolean) sendRequest(CMD_IS_NR_DUAL_CONNECTIVITY_ENABLED,
+                    null, subId, workSource);
+            if (DBG) log("isNRDualConnectivityEnabled: " + isEnabled);
+            return isEnabled;
+        } finally {
+            Binder.restoreCallingIdentity(identity);
+        }
+    }
+
+    /**
      * Get the effective allowed network types on the device.
      * This API will return an intersection of allowed network types for all reasons,
      * including the configuration done through setAllowedNetworkTypes
diff --git a/src/com/android/services/telephony/ImsConference.java b/src/com/android/services/telephony/ImsConference.java
index 4826ecb..c9f762b 100644
--- a/src/com/android/services/telephony/ImsConference.java
+++ b/src/com/android/services/telephony/ImsConference.java
@@ -16,6 +16,7 @@
 
 package com.android.services.telephony;
 
+import android.annotation.NonNull;
 import android.content.Context;
 import android.graphics.drawable.Icon;
 import android.net.Uri;
@@ -28,6 +29,7 @@
 import android.telecom.TelecomManager;
 import android.telecom.VideoProfile;
 import android.telephony.PhoneNumberUtils;
+import android.text.TextUtils;
 import android.util.Pair;
 
 import com.android.ims.internal.ConferenceParticipant;
@@ -1269,58 +1271,79 @@
     }
 
     /**
+     * Extracts a phone number from a {@link Uri}.
+     * <p>
+     * Phone numbers can be represented either as a TEL URI or a SIP URI.
+     * For conference event packages, RFC3261 specifies how participants can be identified using a
+     * SIP URI.
+     * A valid SIP uri has the format: sip:user:password@host:port;uri-parameters?headers
+     * Per RFC3261, the "user" can be a telephone number.
+     * For example: sip:1650555121;phone-context=blah.com@host.com
+     * In this case, the phone number is in the user field of the URI, and the parameters can be
+     * ignored.
+     *
+     * A SIP URI can also specify a phone number in a format similar to:
+     * sip:+1-212-555-1212@something.com;user=phone
+     * In this case, the phone number is again in user field and the parameters can be ignored.
+     * We can get the user field in these instances by splitting the string on the @, ;, or :
+     * and looking at the first found item.
+     * @param handle The URI containing a SIP or TEL formatted phone number.
+     * @return extracted phone number.
+     */
+    private static @NonNull String extractPhoneNumber(@NonNull Uri handle) {
+        // Number is always in the scheme specific part, regardless of whether this is a TEL or SIP
+        // URI.
+        String number = handle.getSchemeSpecificPart();
+        // Get anything before the @ for the SIP case.
+        String[] numberParts = number.split("[@;:]");
+
+        if (numberParts.length == 0) {
+            Log.v(LOG_TAG, "extractPhoneNumber(N) : no number in handle");
+            return "";
+        }
+        return numberParts[0];
+    }
+
+    /**
      * Determines if the passed in participant handle is the same as the conference host's handle.
      * Starts with a simple equality check.  However, the handles from a conference event package
      * will be a SIP uri, so we need to pull that apart to look for the participant's phone number.
      *
-     * @param hostHandles The handle(s) of the connection hosting the conference.
+     * @param hostHandles The handle(s) of the connection hosting the conference, typically obtained
+     *                    from P-Associated-Uri entries.
      * @param handle The handle of the conference participant.
      * @return {@code true} if the host's handle matches the participant's handle, {@code false}
      *      otherwise.
      */
-    private boolean isParticipantHost(Uri[] hostHandles, Uri handle) {
+    @VisibleForTesting
+    public static boolean isParticipantHost(Uri[] hostHandles, Uri handle) {
         // If there is no host handle or no participant handle, bail early.
         if (hostHandles == null || hostHandles.length == 0 || handle == null) {
-            Log.v(this, "isParticipantHost(N) : host or participant uri null");
+            Log.v(LOG_TAG, "isParticipantHost(N) : host or participant uri null");
             return false;
         }
 
-        // Conference event package participants are identified using SIP URIs (see RFC3261).
-        // A valid SIP uri has the format: sip:user:password@host:port;uri-parameters?headers
-        // Per RFC3261, the "user" can be a telephone number.
-        // For example: sip:1650555121;phone-context=blah.com@host.com
-        // In this case, the phone number is in the user field of the URI, and the parameters can be
-        // ignored.
-        //
-        // A SIP URI can also specify a phone number in a format similar to:
-        // sip:+1-212-555-1212@something.com;user=phone
-        // In this case, the phone number is again in user field and the parameters can be ignored.
-        // We can get the user field in these instances by splitting the string on the @, ;, or :
-        // and looking at the first found item.
-
-        String number = handle.getSchemeSpecificPart();
-        String numberParts[] = number.split("[@;:]");
-
-        if (numberParts.length == 0) {
-            Log.v(this, "isParticipantHost(N) : no number in participant handle");
+        String number = extractPhoneNumber(handle);
+        // If we couldn't extract the participant's number, then we can't determine if it is the
+        // host or not.
+        if (TextUtils.isEmpty(number)) {
             return false;
         }
-        number = numberParts[0];
 
         for (Uri hostHandle : hostHandles) {
             if (hostHandle == null) {
                 continue;
             }
-            // The host number will be a tel: uri.  Per RFC3966, the part after tel: is the phone
-            // number.
-            String hostNumber = hostHandle.getSchemeSpecificPart();
+            // Similar to the CEP participant data, the host identity in the P-Associated-Uri could
+            // be a SIP URI or a TEL URI.
+            String hostNumber = extractPhoneNumber(hostHandle);
 
             // Use a loose comparison of the phone numbers.  This ensures that numbers that differ
             // by special characters are counted as equal.
             // E.g. +16505551212 would be the same as 16505551212
             boolean isHost = PhoneNumberUtils.compare(hostNumber, number);
 
-            Log.v(this, "isParticipantHost(%s) : host: %s, participant %s", (isHost ? "Y" : "N"),
+            Log.v(LOG_TAG, "isParticipantHost(%s) : host: %s, participant %s", (isHost ? "Y" : "N"),
                     Rlog.pii(LOG_TAG, hostNumber), Rlog.pii(LOG_TAG, number));
 
             if (isHost) {
diff --git a/tests/src/com/android/services/telephony/ImsConferenceTest.java b/tests/src/com/android/services/telephony/ImsConferenceTest.java
index f13d709..7e6488d 100644
--- a/tests/src/com/android/services/telephony/ImsConferenceTest.java
+++ b/tests/src/com/android/services/telephony/ImsConferenceTest.java
@@ -16,6 +16,8 @@
 
 package com.android.services.telephony;
 
+import static junit.framework.Assert.assertTrue;
+
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
@@ -573,4 +575,40 @@
         assertEquals(0, imsConference.getNumberOfParticipants());
         verify(mConferenceHost.mMockCall).hangup();
     }
+
+    /**
+     * Verifies that an ImsConference can handle SIP and TEL URIs for both the P-Associated-Uri and
+     * conference event package identities.
+     */
+    @Test
+    public void testIsParticipantHost() {
+        // Simplest case, assume P-Associated-Uri is a tel URI and that the CEP participant is also
+        // a tel URI.
+        assertTrue(ImsConference.isParticipantHost(new Uri[] {
+                        Uri.parse("tel:+8616505551234")},
+                Uri.parse("tel:+8616505551234")));
+
+        // Assume P-Associated-Uri is a tel URI and the CEP participant is a sip URI.
+        assertTrue(ImsConference.isParticipantHost(new Uri[] {
+                        Uri.parse("tel:+8616505551234")},
+                Uri.parse("sip:+8616505551234@bj.ims.mnc011.mcc460.3gppnetwork.org")));
+
+        // Assume P-Associated-Uri is a sip URI and the CEP participant is a tel URI.
+        assertTrue(ImsConference.isParticipantHost(new Uri[] {
+                        Uri.parse("sip:+8616505551234@bj.ims.mnc011.mcc460.3gppnetwork.org")},
+                Uri.parse("tel:+8616505551234")));
+
+        // Assume both P-Associated-Uri and the CEP participant are SIP URIs.
+        assertTrue(ImsConference.isParticipantHost(new Uri[] {
+                        Uri.parse("sip:+8616505551234@bj.ims.mnc011.mcc460.3gppnetwork.org")},
+                Uri.parse("sip:+8616505551234@bj.ims.mnc011.mcc460.3gppnetwork.org")));
+
+        // Corner cases
+        assertFalse(ImsConference.isParticipantHost(new Uri[] {
+                        Uri.parse("tel:+8616505551234")}, Uri.fromParts("", "", "")));
+        assertFalse(ImsConference.isParticipantHost(new Uri[] {
+                        Uri.parse("tel:+8616505551234")}, null));
+        assertFalse(ImsConference.isParticipantHost(null, null));
+        assertFalse(ImsConference.isParticipantHost(new Uri[0], null));
+    }
 }