Merge "Cache SIM PIN for verification after unattended reboot."
diff --git a/Android.bp b/Android.bp
index e7ca068..4357f12 100644
--- a/Android.bp
+++ b/Android.bp
@@ -15,6 +15,23 @@
 // Build the Phone app which includes the emergency dialer. See Contacts
 // for the 'other' dialer.
 
+package {
+    default_applicable_licenses: ["packages_services_Telephony_license"],
+}
+
+// Added automatically by a large-scale-change
+// See: http://go/android-license-faq
+license {
+    name: "packages_services_Telephony_license",
+    visibility: [":__subpackages__"],
+    license_kinds: [
+        "SPDX-license-identifier-Apache-2.0",
+    ],
+    license_text: [
+        "NOTICE",
+    ],
+}
+
 android_app {
     name: "TeleService",
 
diff --git a/OWNERS b/OWNERS
index 3059d4d..e095b89 100644
--- a/OWNERS
+++ b/OWNERS
@@ -13,3 +13,4 @@
 dbright@google.com
 xiaotonj@google.com
 
+per-file *SimPhonebookProvider* = file:platform/packages/apps/Contacts:/OWNERS
diff --git a/apex/Android.bp b/apex/Android.bp
index 25a4909..a0e5713 100644
--- a/apex/Android.bp
+++ b/apex/Android.bp
@@ -1,3 +1,12 @@
+package {
+    // See: http://go/android-license-faq
+    // A large-scale-change added 'default_applicable_licenses' to import
+    // all of the 'license_kinds' from "packages_services_Telephony_license"
+    // to get the below license kinds:
+    //   SPDX-license-identifier-Apache-2.0
+    default_applicable_licenses: ["packages_services_Telephony_license"],
+}
+
 apex_defaults {
     name: "com.android.telephony-defaults",
 
diff --git a/apex/testing/Android.bp b/apex/testing/Android.bp
index 10455a4..1138b5e 100644
--- a/apex/testing/Android.bp
+++ b/apex/testing/Android.bp
@@ -12,6 +12,15 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+package {
+    // See: http://go/android-license-faq
+    // A large-scale-change added 'default_applicable_licenses' to import
+    // all of the 'license_kinds' from "packages_services_Telephony_license"
+    // to get the below license kinds:
+    //   SPDX-license-identifier-Apache-2.0
+    default_applicable_licenses: ["packages_services_Telephony_license"],
+}
+
 apex {
     name: "test_com.android.telephony",
     visibility: [
@@ -22,4 +31,4 @@
     file_contexts: ":com.android.telephony-file_contexts",
     // Test APEX, should never be installed
     installable: false,
-}
\ No newline at end of file
+}
diff --git a/ecc/conversion_toolset_v1/proto/Android.bp b/ecc/conversion_toolset_v1/proto/Android.bp
index e1e0643..632ab40 100644
--- a/ecc/conversion_toolset_v1/proto/Android.bp
+++ b/ecc/conversion_toolset_v1/proto/Android.bp
@@ -12,6 +12,15 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+package {
+    // See: http://go/android-license-faq
+    // A large-scale-change added 'default_applicable_licenses' to import
+    // all of the 'license_kinds' from "packages_services_Telephony_license"
+    // to get the below license kinds:
+    //   SPDX-license-identifier-Apache-2.0
+    default_applicable_licenses: ["packages_services_Telephony_license"],
+}
+
 java_library_static {
     name: "ecc-protos-lite",
     proto: {
diff --git a/src/com/android/phone/PhoneInterfaceManager.java b/src/com/android/phone/PhoneInterfaceManager.java
index 933a764..9510cae 100755
--- a/src/com/android/phone/PhoneInterfaceManager.java
+++ b/src/com/android/phone/PhoneInterfaceManager.java
@@ -51,6 +51,7 @@
 import android.os.RemoteException;
 import android.os.ResultReceiver;
 import android.os.ServiceSpecificException;
+import android.os.SystemClock;
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.os.WorkSource;
@@ -372,6 +373,8 @@
     public static final String RESET_NETWORK_ERASE_MODEM_CONFIG_ENABLED =
             "reset_network_erase_modem_config_enabled";
 
+    private static final int SET_NETWORK_SELECTION_MODE_AUTOMATIC_TIMEOUT_MS = 2000; // 2 seconds
+
     /**
      * A request object to use for transmitting data to an ICC.
      */
@@ -1916,8 +1919,8 @@
      * @see #sendRequestAsync
      */
     private Object sendRequest(int command, Object argument) {
-        return sendRequest(
-                command, argument, SubscriptionManager.INVALID_SUBSCRIPTION_ID, null, null);
+        return sendRequest(command, argument, SubscriptionManager.INVALID_SUBSCRIPTION_ID, null,
+                null, -1 /*timeoutInMs*/);
     }
 
     /**
@@ -1927,7 +1930,7 @@
      */
     private Object sendRequest(int command, Object argument, WorkSource workSource) {
         return sendRequest(command, argument,  SubscriptionManager.INVALID_SUBSCRIPTION_ID,
-                null, workSource);
+                null, workSource, -1 /*timeoutInMs*/);
     }
 
     /**
@@ -1936,7 +1939,18 @@
      * @see #sendRequestAsync
      */
     private Object sendRequest(int command, Object argument, Integer subId) {
-        return sendRequest(command, argument, subId, null, null);
+        return sendRequest(command, argument, subId, null, null, -1 /*timeoutInMs*/);
+    }
+
+    /**
+     * Posts the specified command to be executed on the main thread,
+     * waits for the request to complete for at most {@code timeoutInMs}, and returns the result
+     * if not timeout or null otherwise.
+     * @see #sendRequestAsync
+     */
+    private @Nullable Object sendRequest(int command, Object argument, Integer subId,
+            long timeoutInMs) {
+        return sendRequest(command, argument, subId, null, null, timeoutInMs);
     }
 
     /**
@@ -1945,7 +1959,7 @@
      * @see #sendRequestAsync
      */
     private Object sendRequest(int command, Object argument, int subId, WorkSource workSource) {
-        return sendRequest(command, argument, subId, null, workSource);
+        return sendRequest(command, argument, subId, null, workSource, -1 /*timeoutInMs*/);
     }
 
     /**
@@ -1954,17 +1968,18 @@
      * @see #sendRequestAsync
      */
     private Object sendRequest(int command, Object argument, Phone phone, WorkSource workSource) {
-        return sendRequest(
-                command, argument, SubscriptionManager.INVALID_SUBSCRIPTION_ID, phone, workSource);
+        return sendRequest(command, argument, SubscriptionManager.INVALID_SUBSCRIPTION_ID, phone,
+                workSource, -1 /*timeoutInMs*/);
     }
 
     /**
-     * Posts the specified command to be executed on the main thread,
-     * waits for the request to complete, and returns the result.
+     * Posts the specified command to be executed on the main thread. If {@code timeoutInMs} is
+     * negative, waits for the request to complete, and returns the result. Otherwise, wait for
+     * maximum of {@code timeoutInMs} milliseconds, interrupt and return null.
      * @see #sendRequestAsync
      */
-    private Object sendRequest(
-            int command, Object argument, Integer subId, Phone phone, WorkSource workSource) {
+    private @Nullable Object sendRequest(int command, Object argument, Integer subId, Phone phone,
+            WorkSource workSource, long timeoutInMs) {
         if (Looper.myLooper() == mMainThreadHandler.getLooper()) {
             throw new RuntimeException("This method will deadlock if called from the main thread.");
         }
@@ -1981,16 +1996,36 @@
         Message msg = mMainThreadHandler.obtainMessage(command, request);
         msg.sendToTarget();
 
-        // Wait for the request to complete
+
         synchronized (request) {
-            while (request.result == null) {
-                try {
-                    request.wait();
-                } catch (InterruptedException e) {
-                    // Do nothing, go back and wait until the request is complete
+            if (timeoutInMs >= 0) {
+                // Wait for at least timeoutInMs before returning null request result
+                long now = SystemClock.elapsedRealtime();
+                long deadline = now + timeoutInMs;
+                while (request == null && now < deadline) {
+                    try {
+                        request.wait(deadline - now);
+                    } catch (InterruptedException e) {
+                        // Do nothing, go back and check if request is completed or timeout
+                    } finally {
+                        now = SystemClock.elapsedRealtime();
+                    }
+                }
+            } else {
+                // Wait for the request to complete
+                while (request.result == null) {
+                    try {
+                        request.wait();
+                    } catch (InterruptedException e) {
+                        // Do nothing, go back and wait until the request is complete
+                    }
                 }
             }
         }
+        if (request.result == null) {
+            Log.wtf(LOG_TAG,
+                    "sendRequest: Blocking command timed out. Something has gone terribly wrong.");
+        }
         return request.result;
     }
 
@@ -5631,7 +5666,8 @@
                 return;
             }
             if (DBG) log("setNetworkSelectionModeAutomatic: subId " + subId);
-            sendRequest(CMD_SET_NETWORK_SELECTION_MODE_AUTOMATIC, null, subId);
+            sendRequest(CMD_SET_NETWORK_SELECTION_MODE_AUTOMATIC, null, subId,
+                    SET_NETWORK_SELECTION_MODE_AUTOMATIC_TIMEOUT_MS);
         } finally {
             Binder.restoreCallingIdentity(identity);
         }
diff --git a/src/com/android/phone/SimPhonebookProvider.java b/src/com/android/phone/SimPhonebookProvider.java
index 7a1e93c..6a27130 100644
--- a/src/com/android/phone/SimPhonebookProvider.java
+++ b/src/com/android/phone/SimPhonebookProvider.java
@@ -59,6 +59,7 @@
 import java.util.Arrays;
 import java.util.LinkedHashSet;
 import java.util.List;
+import java.util.Objects;
 import java.util.Set;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.locks.Lock;
@@ -295,6 +296,9 @@
 
     private Cursor queryElementaryFilesItem(PhonebookArgs args, String[] projection) {
         validateProjection(ELEMENTARY_FILES_COLUMNS_SET, projection);
+        if (projection == null) {
+            projection = ELEMENTARY_FILES_ALL_COLUMNS;
+        }
 
         MatrixCursor result = new MatrixCursor(projection);
         try {
@@ -641,8 +645,8 @@
     }
 
     private boolean hasPermissionsForFdnWrite(PhonebookArgs args) {
-        TelephonyManager telephonyManager = getContext().getSystemService(
-                TelephonyManager.class);
+        TelephonyManager telephonyManager = Objects.requireNonNull(
+                getContext().getSystemService(TelephonyManager.class));
         String callingPackage = getCallingPackage();
         int granted = PackageManager.PERMISSION_DENIED;
         if (callingPackage != null) {
@@ -701,7 +705,12 @@
 
         String name = values.getAsString(SimRecords.NAME);
         int length = getEncodedNameLength(name);
-        int maxLength = AdnRecord.getMaxAlphaTagBytes(getRecordSize(getRecordsSizeForEf(args)));
+        int[] recordsSize = getRecordsSizeForEf(args);
+        if (recordsSize == null) {
+            throw new IllegalStateException(
+                    "Failed to get " + ElementaryFiles.NAME_MAX_LENGTH + " from SIM");
+        }
+        int maxLength = AdnRecord.getMaxAlphaTagBytes(getRecordSize(recordsSize));
 
         if (length > maxLength) {
             throw new IllegalArgumentException(SimRecords.NAME + " is too long.");
@@ -740,7 +749,7 @@
 
     private AdnRecord loadRecord(PhonebookArgs args) {
         List<AdnRecord> records = loadRecordsForEf(args);
-        if (args.recordNumber > records.size()) {
+        if (records == null || args.recordNumber > records.size()) {
             return null;
         }
         AdnRecord result = records.get(args.recordNumber - 1);
diff --git a/src/com/android/services/telephony/CallQualityManager.java b/src/com/android/services/telephony/CallQualityManager.java
index 01b5bae..0e32ddc 100644
--- a/src/com/android/services/telephony/CallQualityManager.java
+++ b/src/com/android/services/telephony/CallQualityManager.java
@@ -99,7 +99,7 @@
                 .setContentTitle(title)
                 .setContentText(details)
                 .setStyle(new Notification.BigTextStyle().bigText(details))
-                .setOngoing(true)
+                .setAutoCancel(true)
                 .setChannelId(CALL_QUALITY_CHANNEL_ID)
                 .setOnlyAlertOnce(true)
                 .build();
diff --git a/testapps/EmbmsServiceTestApp/Android.bp b/testapps/EmbmsServiceTestApp/Android.bp
index e4a54cb..584e5bd 100644
--- a/testapps/EmbmsServiceTestApp/Android.bp
+++ b/testapps/EmbmsServiceTestApp/Android.bp
@@ -1,4 +1,13 @@
 // Build the Sample Embms Services
+package {
+    // See: http://go/android-license-faq
+    // A large-scale-change added 'default_applicable_licenses' to import
+    // all of the 'license_kinds' from "packages_services_Telephony_license"
+    // to get the below license kinds:
+    //   SPDX-license-identifier-Apache-2.0
+    default_applicable_licenses: ["packages_services_Telephony_license"],
+}
+
 android_app {
     name: "EmbmsTestService",
     srcs: ["src/**/*.java"],
diff --git a/testapps/EmbmsTestDownloadApp/Android.bp b/testapps/EmbmsTestDownloadApp/Android.bp
index 63f4e83..c1b9425 100644
--- a/testapps/EmbmsTestDownloadApp/Android.bp
+++ b/testapps/EmbmsTestDownloadApp/Android.bp
@@ -1,3 +1,12 @@
+package {
+    // See: http://go/android-license-faq
+    // A large-scale-change added 'default_applicable_licenses' to import
+    // all of the 'license_kinds' from "packages_services_Telephony_license"
+    // to get the below license kinds:
+    //   SPDX-license-identifier-Apache-2.0
+    default_applicable_licenses: ["packages_services_Telephony_license"],
+}
+
 src_dirs = ["src"]
 res_dirs = ["res"]
 android_test {
diff --git a/testapps/EmbmsTestStreamingApp/Android.bp b/testapps/EmbmsTestStreamingApp/Android.bp
index 814c5ca..9f082ee 100644
--- a/testapps/EmbmsTestStreamingApp/Android.bp
+++ b/testapps/EmbmsTestStreamingApp/Android.bp
@@ -1,3 +1,12 @@
+package {
+    // See: http://go/android-license-faq
+    // A large-scale-change added 'default_applicable_licenses' to import
+    // all of the 'license_kinds' from "packages_services_Telephony_license"
+    // to get the below license kinds:
+    //   SPDX-license-identifier-Apache-2.0
+    default_applicable_licenses: ["packages_services_Telephony_license"],
+}
+
 android_test {
     name: "EmbmsTestStreamingApp",
     srcs: ["src/**/*.java"],
diff --git a/testapps/GbaTestApp/Android.bp b/testapps/GbaTestApp/Android.bp
index cb6df4e..b3c45dd 100644
--- a/testapps/GbaTestApp/Android.bp
+++ b/testapps/GbaTestApp/Android.bp
@@ -12,6 +12,15 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+package {
+    // See: http://go/android-license-faq
+    // A large-scale-change added 'default_applicable_licenses' to import
+    // all of the 'license_kinds' from "packages_services_Telephony_license"
+    // to get the below license kinds:
+    //   SPDX-license-identifier-Apache-2.0
+    default_applicable_licenses: ["packages_services_Telephony_license"],
+}
+
 android_test {
     name: "GbaTestApp",
     static_libs: [
diff --git a/testapps/ImsTestService/Android.bp b/testapps/ImsTestService/Android.bp
index a0b4edb..7073749 100644
--- a/testapps/ImsTestService/Android.bp
+++ b/testapps/ImsTestService/Android.bp
@@ -1,3 +1,12 @@
+package {
+    // See: http://go/android-license-faq
+    // A large-scale-change added 'default_applicable_licenses' to import
+    // all of the 'license_kinds' from "packages_services_Telephony_license"
+    // to get the below license kinds:
+    //   SPDX-license-identifier-Apache-2.0
+    default_applicable_licenses: ["packages_services_Telephony_license"],
+}
+
 android_app {
     name: "ImsTestApp",
     static_libs: [
diff --git a/testapps/SmsManagerTestApp/Android.bp b/testapps/SmsManagerTestApp/Android.bp
index 5333eab..e451b2d 100644
--- a/testapps/SmsManagerTestApp/Android.bp
+++ b/testapps/SmsManagerTestApp/Android.bp
@@ -1,3 +1,12 @@
+package {
+    // See: http://go/android-license-faq
+    // A large-scale-change added 'default_applicable_licenses' to import
+    // all of the 'license_kinds' from "packages_services_Telephony_license"
+    // to get the below license kinds:
+    //   SPDX-license-identifier-Apache-2.0
+    default_applicable_licenses: ["packages_services_Telephony_license"],
+}
+
 android_app {
     name: "SmsManagerTestApp",
     srcs: ["src/**/*.java"],
diff --git a/testapps/TelephonyManagerTestApp/Android.bp b/testapps/TelephonyManagerTestApp/Android.bp
index 8a37c99..e95d62f 100644
--- a/testapps/TelephonyManagerTestApp/Android.bp
+++ b/testapps/TelephonyManagerTestApp/Android.bp
@@ -1,3 +1,12 @@
+package {
+    // See: http://go/android-license-faq
+    // A large-scale-change added 'default_applicable_licenses' to import
+    // all of the 'license_kinds' from "packages_services_Telephony_license"
+    // to get the below license kinds:
+    //   SPDX-license-identifier-Apache-2.0
+    default_applicable_licenses: ["packages_services_Telephony_license"],
+}
+
 android_test {
     name: "TelephonyManagerTestApp",
     srcs: ["src/**/*.java"],
diff --git a/testapps/TelephonyRegistryTestApp/Android.bp b/testapps/TelephonyRegistryTestApp/Android.bp
index fec5286..2439461 100644
--- a/testapps/TelephonyRegistryTestApp/Android.bp
+++ b/testapps/TelephonyRegistryTestApp/Android.bp
@@ -1,3 +1,12 @@
+package {
+    // See: http://go/android-license-faq
+    // A large-scale-change added 'default_applicable_licenses' to import
+    // all of the 'license_kinds' from "packages_services_Telephony_license"
+    // to get the below license kinds:
+    //   SPDX-license-identifier-Apache-2.0
+    default_applicable_licenses: ["packages_services_Telephony_license"],
+}
+
 android_test {
     name: "TelephonyRegistryTestApp",
     srcs: ["src/**/*.java"],
diff --git a/testapps/TestRcsApp/TestApp/Android.bp b/testapps/TestRcsApp/TestApp/Android.bp
index dfa1f2e..e63715b 100644
--- a/testapps/TestRcsApp/TestApp/Android.bp
+++ b/testapps/TestRcsApp/TestApp/Android.bp
@@ -1,3 +1,12 @@
+package {
+    // See: http://go/android-license-faq
+    // A large-scale-change added 'default_applicable_licenses' to import
+    // all of the 'license_kinds' from "packages_services_Telephony_license"
+    // to get the below license kinds:
+    //   SPDX-license-identifier-Apache-2.0
+    default_applicable_licenses: ["packages_services_Telephony_license"],
+}
+
 android_app {
     name: "TestRcsApp",
 
@@ -9,11 +18,10 @@
         "androidx-constraintlayout_constraintlayout",
         "aosp_test_rcs_client_base",
         "androidx.appcompat_appcompat",
+        "libphonenumber-platform"
     ],
     certificate: "platform",
 
     sdk_version: "system_current",
     min_sdk_version: "30",
 }
-
-
diff --git a/testapps/TestRcsApp/TestApp/AndroidManifest.xml b/testapps/TestRcsApp/TestApp/AndroidManifest.xml
index 6e52949..8f2d6bd 100644
--- a/testapps/TestRcsApp/TestApp/AndroidManifest.xml
+++ b/testapps/TestRcsApp/TestApp/AndroidManifest.xml
@@ -19,8 +19,8 @@
 -->
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
     package="com.google.android.sample.rcsclient"
-    android:versionCode="3"
-    android:versionName="1.0.2_UP1.0">
+    android:versionCode="5"
+    android:versionName="1.0.4">
 
     <uses-sdk
         android:minSdkVersion="30"
diff --git a/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/util/NumberUtils.java b/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/util/NumberUtils.java
index 72cbf3f..14d3b9c 100644
--- a/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/util/NumberUtils.java
+++ b/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/util/NumberUtils.java
@@ -17,8 +17,12 @@
 package com.google.android.sample.rcsclient.util;
 
 import android.content.Context;
-import android.telephony.PhoneNumberUtils;
 import android.telephony.TelephonyManager;
+import android.util.Log;
+
+import com.android.i18n.phonenumbers.NumberParseException;
+import com.android.i18n.phonenumbers.PhoneNumberUtil;
+import com.android.i18n.phonenumbers.Phonenumber;
 
 public class NumberUtils {
 
@@ -30,6 +34,13 @@
     public static String formatNumber(Context context, String number) {
         TelephonyManager manager = context.getSystemService(TelephonyManager.class);
         String simCountryIso = manager.getSimCountryIso().toUpperCase();
-        return PhoneNumberUtils.formatNumberToE164(number, simCountryIso);
+        PhoneNumberUtil util = PhoneNumberUtil.getInstance();
+        try {
+            Phonenumber.PhoneNumber phoneNumber = util.parse(number, simCountryIso);
+            return util.format(phoneNumber, PhoneNumberUtil.PhoneNumberFormat.E164);
+        } catch (NumberParseException e) {
+            Log.w("NumberUtils", "formatNumber: could not format " + number + ", error: " + e);
+        }
+        return null;
     }
 }
diff --git a/testapps/TestRcsApp/aosp_test_rcsclient/Android.bp b/testapps/TestRcsApp/aosp_test_rcsclient/Android.bp
index eef34c8..413b5e8 100644
--- a/testapps/TestRcsApp/aosp_test_rcsclient/Android.bp
+++ b/testapps/TestRcsApp/aosp_test_rcsclient/Android.bp
@@ -1,5 +1,24 @@
 
 
+package {
+    default_applicable_licenses: [
+        "packages_services_Telephony_testapps_TestRcsApp_aosp_test_rcsclient_license",
+    ],
+}
+
+// Added automatically by a large-scale-change
+// See: http://go/android-license-faq
+license {
+    name: "packages_services_Telephony_testapps_TestRcsApp_aosp_test_rcsclient_license",
+    visibility: [":__subpackages__"],
+    license_kinds: [
+        "SPDX-license-identifier-Apache-2.0",
+    ],
+    license_text: [
+        "LICENSE",
+    ],
+}
+
 android_library {
     name: "aosp_test_rcs_client_base",
 
@@ -23,5 +42,3 @@
     sdk_version: "system_current",
     min_sdk_version: "30",
 }
-
-
diff --git a/testapps/TestRcsApp/aosp_test_rcsclient/javatests/com/android/libraries/rcs/simpleclient/protocol/cpim/SimpleCpimMessageTest.java b/testapps/TestRcsApp/aosp_test_rcsclient/javatests/com/android/libraries/rcs/simpleclient/protocol/cpim/SimpleCpimMessageTest.java
new file mode 100644
index 0000000..2dda33f
--- /dev/null
+++ b/testapps/TestRcsApp/aosp_test_rcsclient/javatests/com/android/libraries/rcs/simpleclient/protocol/cpim/SimpleCpimMessageTest.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.libraries.rcs.simpleclient.protocol.cpim;
+
+import static com.google.common.truth.Truth.assertThat;
+import static java.nio.charset.StandardCharsets.UTF_8;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public class SimpleCpimMessageTest {
+    private static final String SAMPLE_CPIM =
+            "From: MR SANDERS <im:piglet@100akerwood.com>\r\n"
+                    + "To: Depressed Donkey <im:eeyore@100akerwood.com>\r\n"
+                    + "DateTime: 2000-12-13T13:40:00-08:00\r\n"
+                    + "Subject: the weather will be fine today\r\n"
+                    + "Subject:;lang=fr beau temps prevu pour aujourd'hui\r\n"
+                    + "NS: MyFeatures <mid:MessageFeatures@id.foo.com>\r\n"
+                    + "Require: MyFeatures.VitalMessageOption\r\n"
+                    + "MyFeatures.VitalMessageOption: Confirmation-requested\r\n"
+                    + "MyFeatures.WackyMessageOption: Use-silly-font\r\n"
+                    + "\r\n"
+                    + "Content-type: text/plain; charset=utf-8\r\n"
+                    + "Content-ID: <1234567890@foo.com>\r\n"
+                    + "\r\n"
+                    + "body";
+
+    @Test
+    public void parse_successful() throws Exception {
+        SimpleCpimMessage cpim = SimpleCpimMessage.parse(SAMPLE_CPIM.getBytes(UTF_8));
+
+        assertThat(cpim.namespaces()).containsEntry("MyFeatures", "mid:MessageFeatures@id.foo.com");
+        assertThat(cpim.headers()).containsEntry("Require", "MyFeatures.VitalMessageOption");
+        assertThat(cpim.contentType()).isEqualTo("text/plain; charset=utf-8");
+        assertThat(cpim.content()).isEqualTo("body");
+    }
+}
diff --git a/testapps/TestRcsApp/aosp_test_rcsclient/javatests/com/android/libraries/rcs/simpleclient/service/chat/SimpleChatSessionTest.java b/testapps/TestRcsApp/aosp_test_rcsclient/javatests/com/android/libraries/rcs/simpleclient/service/chat/SimpleChatSessionTest.java
index 5c2e995..2723940 100644
--- a/testapps/TestRcsApp/aosp_test_rcsclient/javatests/com/android/libraries/rcs/simpleclient/service/chat/SimpleChatSessionTest.java
+++ b/testapps/TestRcsApp/aosp_test_rcsclient/javatests/com/android/libraries/rcs/simpleclient/service/chat/SimpleChatSessionTest.java
@@ -35,14 +35,14 @@
 import gov.nist.javax.sip.message.SIPRequest;
 import gov.nist.javax.sip.message.SIPResponse;
 
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
 import java.util.List;
 
 import javax.sip.message.Message;
 import javax.sip.message.Request;
 
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
 @RunWith(AndroidJUnit4.class)
 public class SimpleChatSessionTest {
     private static final String LOCAL_URI = "tel:+1234567890";
@@ -126,6 +126,16 @@
                 public String getPlaniHeader() {
                     return "IEEE-802.11;i-wlan-node-id=PLANI01EB5B0";
                 }
+
+                @Override
+                public String getUserAgentHeader() {
+                    return "Test-Client";
+                }
+
+                @Override
+                public int getMaxPayloadSizeOnUdp() {
+                    return 0;
+                }
             };
     private final SimpleRcsClientContext context =
             new SimpleRcsClientContext(
diff --git a/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/protocol/cpim/CpimUtils.java b/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/protocol/cpim/CpimUtils.java
index b621257..6bb8eec 100644
--- a/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/protocol/cpim/CpimUtils.java
+++ b/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/protocol/cpim/CpimUtils.java
@@ -16,8 +16,9 @@
 
 package com.android.libraries.rcs.simpleclient.protocol.cpim;
 
-import java.time.LocalDate;
 import java.time.ZoneId;
+import java.time.ZonedDateTime;
+import java.time.format.DateTimeFormatter;
 import java.util.Random;
 
 /** Collections of utility functions for CPIM */
@@ -28,6 +29,7 @@
     private CpimUtils() {
     }
 
+    @SuppressWarnings("AndroidJdkLibsChecker")
     public static SimpleCpimMessage createForText(String text) {
         return SimpleCpimMessage.newBuilder()
                 .addNamespace("imdn", "urn:ietf:params:imdn")
@@ -35,7 +37,8 @@
                 .addHeader("imdn.Disposition-Notification", "positive-delivery, display")
                 .addHeader("To", ANONYMOUS_URI)
                 .addHeader("From", ANONYMOUS_URI)
-                .addHeader("DateTime", LocalDate.now(ZoneId.systemDefault()).toString())
+                .addHeader("DateTime", ZonedDateTime.now(ZoneId.systemDefault()).format(
+                        DateTimeFormatter.ISO_INSTANT))
                 .setContentType("text/plain")
                 .setContent(text)
                 .build();
@@ -43,6 +46,6 @@
 
     private static String generateImdnMessageId() {
         Random random = new Random();
-        return "Test_" + random.nextLong();
+        return "Test_" + random.nextInt(1000000);
     }
 }
diff --git a/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/protocol/cpim/SimpleCpimMessage.java b/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/protocol/cpim/SimpleCpimMessage.java
index aeb6b11..e2efafe 100644
--- a/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/protocol/cpim/SimpleCpimMessage.java
+++ b/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/protocol/cpim/SimpleCpimMessage.java
@@ -16,11 +16,19 @@
 
 package com.android.libraries.rcs.simpleclient.protocol.cpim;
 
+import android.text.TextUtils;
 import com.google.auto.value.AutoValue;
+import com.google.common.base.Ascii;
 import com.google.common.base.Utf8;
 import com.google.common.collect.ImmutableMap;
-
+import com.google.common.io.CharStreams;
+import java.io.BufferedReader;
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStreamReader;
 import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
 
 /**
  * The CPIM implementation as per RFC 3862. This class supports minimal fields that is required to
@@ -30,10 +38,9 @@
 public abstract class SimpleCpimMessage {
     private static final String CRLF = "\r\n";
     private static final String COLSP = ": ";
-
-    public static SimpleCpimMessage.Builder newBuilder() {
-        return new AutoValue_SimpleCpimMessage.Builder();
-    }
+    private static final Pattern NAMESPACE_HEADER_PATTERN =
+            Pattern.compile("NS:\\s+(\\S+)\\s+<(.+)>");
+    private static final Pattern HEADER_PATTERN = Pattern.compile("([^\\s:]+):\\s+(.+)");
 
     public abstract ImmutableMap<String, String> namespaces();
 
@@ -61,13 +68,50 @@
 
         builder.append(CRLF);
         builder.append("Content-Type").append(COLSP).append(contentType());
-        builder.append("Content-Length").append(COLSP).append(Utf8.encodedLength(content()));
         builder.append(CRLF);
+        builder.append("Content-Length").append(COLSP).append(Utf8.encodedLength(content()));
+        builder.append(CRLF).append(CRLF);
         builder.append(content());
 
         return builder.toString();
     }
 
+    public static SimpleCpimMessage parse(byte[] content) throws IOException {
+        BufferedReader reader =
+                new BufferedReader(new InputStreamReader(new ByteArrayInputStream(content)));
+        Builder builder = newBuilder();
+
+        String line = reader.readLine();
+        while (!TextUtils.isEmpty(line)) {
+            Matcher namespaceMatcher = NAMESPACE_HEADER_PATTERN.matcher(line);
+            Matcher headerMatcher = HEADER_PATTERN.matcher(line);
+            if (namespaceMatcher.matches()) {
+                builder.addNamespace(namespaceMatcher.group(1), namespaceMatcher.group(2));
+            } else if (headerMatcher.matches()) {
+                builder.addHeader(headerMatcher.group(1), headerMatcher.group(2));
+            }
+
+            line = reader.readLine();
+        }
+
+        line = reader.readLine();
+        while (!TextUtils.isEmpty(line)) {
+            Matcher headerMatcher = HEADER_PATTERN.matcher(line);
+            if (headerMatcher.matches()) {
+                if (Ascii.equalsIgnoreCase("content-type", headerMatcher.group(1))) {
+                    builder.setContentType(headerMatcher.group(2));
+                }
+            }
+
+            line = reader.readLine();
+        }
+
+        String body = CharStreams.toString(reader);
+        builder.setContent(body);
+
+        return builder.build();
+    }
+
     @AutoValue.Builder
     public abstract static class Builder {
         public abstract ImmutableMap.Builder<String, String> namespacesBuilder();
@@ -90,4 +134,8 @@
             return this;
         }
     }
+
+    public static SimpleCpimMessage.Builder newBuilder() {
+        return new AutoValue_SimpleCpimMessage.Builder();
+    }
 }
diff --git a/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/protocol/msrp/ImsPdnNetworkFetcher.java b/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/protocol/msrp/ImsPdnNetworkFetcher.java
index 0011011..57bb75a 100644
--- a/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/protocol/msrp/ImsPdnNetworkFetcher.java
+++ b/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/protocol/msrp/ImsPdnNetworkFetcher.java
@@ -82,7 +82,7 @@
         return result;
     }
 
-    private ConnectivityManager getConnectivityManager() {
+    ConnectivityManager getConnectivityManager() {
         return context.getSystemService(ConnectivityManager.class);
     }
 }
diff --git a/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/protocol/msrp/MsrpConstants.java b/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/protocol/msrp/MsrpConstants.java
index ad1b98e..ba424c6 100644
--- a/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/protocol/msrp/MsrpConstants.java
+++ b/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/protocol/msrp/MsrpConstants.java
@@ -44,8 +44,12 @@
     public static final String HEADER_TO_PATH = "To-Path";
     public static final String HEADER_FROM_PATH = "From-Path";
     public static final String HEADER_FAILURE_REPORT = "Failure-Report";
+    public static final String HEADER_SUCCESS_REPORT = "Success-Report";
+    public static final String REPORT_VALUE_YES = "yes";
+    public static final String REPORT_VALUE_NO = "no";
+
     public static final int RESPONSE_CODE_OK = 200;
 
     private MsrpConstants() {
     }
-}
\ No newline at end of file
+}
diff --git a/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/protocol/msrp/MsrpManager.java b/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/protocol/msrp/MsrpManager.java
index 47326bd..81abe89 100644
--- a/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/protocol/msrp/MsrpManager.java
+++ b/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/protocol/msrp/MsrpManager.java
@@ -17,6 +17,7 @@
 package com.android.libraries.rcs.simpleclient.protocol.msrp;
 
 import android.content.Context;
+import android.net.ConnectivityManager;
 import android.net.Network;
 
 import com.google.common.util.concurrent.Futures;
@@ -24,6 +25,7 @@
 import com.google.common.util.concurrent.MoreExecutors;
 
 import java.io.IOException;
+import java.net.InetAddress;
 import java.net.Socket;
 
 /** Provides creating and managing {@link MsrpSession} */
@@ -34,24 +36,27 @@
         imsPdnNetworkFetcher = new ImsPdnNetworkFetcher(context);
     }
 
-    private static MsrpSession createMsrpSession(
-            Network network, String host, int port, MsrpSessionListener listener)
-            throws IOException {
-        Socket socket = network.getSocketFactory().createSocket(host, port);
-        MsrpSession msrpSession = new MsrpSession(socket, listener);
+    private static MsrpSession createMsrpSession(ConnectivityManager manager,
+            Network network, String host, int port, String localIp, int localPort,
+            MsrpSessionListener listener) throws IOException {
+        Socket socket = network.getSocketFactory().createSocket(host, port,
+                InetAddress.getByName(localIp), localPort);
+        MsrpSession msrpSession = new MsrpSession(manager,
+                network, socket, listener);
         Thread thread = new Thread(msrpSession::run);
         thread.start();
         return msrpSession;
     }
 
     public ListenableFuture<MsrpSession> createMsrpSession(
-            String host, int port, MsrpSessionListener listener) {
+            String host, int port, String localIp, int localPort, MsrpSessionListener listener) {
         return Futures.transformAsync(
                 imsPdnNetworkFetcher.getImsPdnNetwork(),
                 network -> {
                     if (network != null) {
                         return Futures.immediateFuture(
-                                createMsrpSession(network, host, port, listener));
+                                createMsrpSession(imsPdnNetworkFetcher.getConnectivityManager(),
+                                        network, host, port, localIp, localPort, listener));
                     } else {
                         return Futures.immediateFailedFuture(
                                 new IllegalStateException("Network is null"));
diff --git a/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/protocol/msrp/MsrpSession.java b/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/protocol/msrp/MsrpSession.java
index 96ca19c..3f8b986 100644
--- a/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/protocol/msrp/MsrpSession.java
+++ b/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/protocol/msrp/MsrpSession.java
@@ -19,6 +19,16 @@
 import static com.android.libraries.rcs.simpleclient.protocol.msrp.MsrpChunk.Method.SEND;
 import static com.android.libraries.rcs.simpleclient.protocol.msrp.MsrpChunk.Method.UNKNOWN;
 
+import android.net.ConnectivityManager;
+import android.net.Network;
+import android.net.QosCallback;
+import android.net.QosCallbackException;
+import android.net.QosSession;
+import android.net.QosSessionAttributes;
+import android.net.QosSocketInfo;
+import android.util.Log;
+
+import androidx.annotation.NonNull;
 import androidx.concurrent.futures.CallbackToFutureAdapter;
 import androidx.concurrent.futures.CallbackToFutureAdapter.Completer;
 
@@ -26,6 +36,7 @@
 
 import com.google.common.util.concurrent.Futures;
 import com.google.common.util.concurrent.ListenableFuture;
+import com.google.common.util.concurrent.MoreExecutors;
 
 import java.io.IOException;
 import java.io.InputStream;
@@ -38,6 +49,7 @@
  * Provides MSRP sending and receiving messages ability.
  */
 public class MsrpSession {
+    private final Network network;
     private final Socket socket;
     private final InputStream input;
     private final OutputStream output;
@@ -45,13 +57,51 @@
     private final ConcurrentHashMap<String, MsrpTransaction> transactions =
             new ConcurrentHashMap<>();
     private final MsrpSessionListener listener;
+    private final ConnectivityManager connectivityManager;
+    private final String LOG_TAG = MsrpSession.class.getSimpleName();
 
     /** Creates a new MSRP session on the given listener and the provided streams. */
-    MsrpSession(Socket socket, MsrpSessionListener listener) throws IOException {
+    MsrpSession(ConnectivityManager connectivityManager, Network network, Socket socket,
+            MsrpSessionListener listener) throws IOException {
+        this.connectivityManager = connectivityManager;
+        this.network = network;
         this.socket = socket;
         this.input = socket.getInputStream();
         this.output = socket.getOutputStream();
         this.listener = listener;
+
+        listenForBearer();
+    }
+
+    private final QosCallback qosCallback = new QosCallback() {
+        @Override
+        public void onError(@NonNull QosCallbackException exception) {
+            Log.e(LOG_TAG, "onError: " + exception.toString());
+            super.onError(exception);
+        }
+
+        @Override
+        public void onQosSessionAvailable(@NonNull QosSession session,
+                @NonNull QosSessionAttributes sessionAttributes) {
+            Log.d(LOG_TAG, "onQosSessionAvailable: " + session.toString() + ", "
+                    + sessionAttributes.toString());
+            super.onQosSessionAvailable(session, sessionAttributes);
+        }
+
+        @Override
+        public void onQosSessionLost(@NonNull QosSession session) {
+            Log.e(LOG_TAG, "onQosSessionLost: " + session.toString());
+            super.onQosSessionLost(session);
+        }
+    };
+
+    private void listenForBearer() {
+        try {
+            connectivityManager.registerQosCallback(new QosSocketInfo(network, socket),
+                    qosCallback, MoreExecutors.directExecutor());
+        } catch (IOException e) {
+            throw new RuntimeException(e);
+        }
     }
 
     /**
@@ -110,6 +160,7 @@
         if (isOpen.getAndSet(false)) {
             output.flush();
         }
+        connectivityManager.unregisterQosCallback(qosCallback);
         socket.close();
     }
 
diff --git a/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/protocol/sip/SipUtils.java b/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/protocol/sip/SipUtils.java
index 2f95bef..7605fb5 100644
--- a/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/protocol/sip/SipUtils.java
+++ b/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/protocol/sip/SipUtils.java
@@ -22,7 +22,8 @@
 import android.text.TextUtils;
 import android.util.Log;
 
-import com.android.libraries.rcs.simpleclient.protocol.sdp.SdpUtils;
+import androidx.annotation.Nullable;
+
 import com.android.libraries.rcs.simpleclient.protocol.sdp.SimpleSdpMessage;
 
 import com.google.common.base.Ascii;
@@ -259,7 +260,7 @@
 
         request.setCallId(invite.getCallId());
 
-        Via via = (Via) request.getTopmostVia().clone();
+        Via via = (Via) invite.getTopmostVia().clone();
         via.removeParameter("branch");
         request.addHeader(via);
         request.addHeader(
@@ -280,12 +281,13 @@
      * @param code          The status code of the response.
      */
     public static SIPResponse buildInviteResponse(
-            SipSessionConfiguration configuration, SIPRequest invite, int code)
+            SipSessionConfiguration configuration,
+            SIPRequest invite,
+            int code,
+            @Nullable SimpleSdpMessage sdp)
             throws ParseException {
         SIPResponse response = invite.createResponse(code);
         if (code == Response.OK) {
-            SimpleSdpMessage sdp = SdpUtils.createSdpForMsrp(configuration.getLocalIpAddress(),
-                    false);
             response.setMessageContent(SDP_CONTENT_TYPE, SDP_CONTENT_SUB_TYPE, sdp.encode());
         }
 
diff --git a/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/service/chat/MinimalCpmChatService.java b/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/service/chat/MinimalCpmChatService.java
index 01a1061..b204de6 100644
--- a/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/service/chat/MinimalCpmChatService.java
+++ b/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/service/chat/MinimalCpmChatService.java
@@ -21,6 +21,8 @@
 import android.text.TextUtils;
 import android.util.Log;
 
+import androidx.annotation.Nullable;
+
 import com.android.libraries.rcs.simpleclient.SimpleRcsClientContext;
 import com.android.libraries.rcs.simpleclient.protocol.msrp.MsrpManager;
 import com.android.libraries.rcs.simpleclient.protocol.sip.SipSession;
@@ -45,8 +47,6 @@
 import javax.sip.message.Request;
 import javax.sip.message.Response;
 
-import androidx.annotation.Nullable;
-
 /**
  * Minimal CPM chat session service that provides the interface creating a {@link SimpleChatSession}
  * instance using {@link SipDelegateConnection}.
@@ -162,7 +162,8 @@
                         SipUtils.buildInviteResponse(
                                 mContext.getSipSession().getSessionConfiguration(),
                                 request,
-                                Response.METHOD_NOT_ALLOWED);
+                                Response.METHOD_NOT_ALLOWED,
+                                null);
                 sendSipResponse(response, /* session= */ null)
                         .addListener(() -> {
                         }, MoreExecutors.directExecutor());
diff --git a/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/service/chat/SimpleChatSession.java b/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/service/chat/SimpleChatSession.java
index 74472d7..ebccbde 100644
--- a/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/service/chat/SimpleChatSession.java
+++ b/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/service/chat/SimpleChatSession.java
@@ -31,6 +31,7 @@
 import com.android.libraries.rcs.simpleclient.protocol.cpim.SimpleCpimMessage;
 import com.android.libraries.rcs.simpleclient.protocol.msrp.MsrpChunk;
 import com.android.libraries.rcs.simpleclient.protocol.msrp.MsrpChunk.Continuation;
+import com.android.libraries.rcs.simpleclient.protocol.msrp.MsrpChunkHeader;
 import com.android.libraries.rcs.simpleclient.protocol.msrp.MsrpConstants;
 import com.android.libraries.rcs.simpleclient.protocol.msrp.MsrpManager;
 import com.android.libraries.rcs.simpleclient.protocol.msrp.MsrpSession;
@@ -109,6 +110,8 @@
 
         // Build a new CPIM message and send it out through the MSRP session.
         SimpleCpimMessage cpim = CpimUtils.createForText(msg);
+        Log.i(TAG, "Encoded CPIM:" + cpim.encode());
+
         byte[] content = cpim.encode().getBytes(UTF_8);
         MsrpChunk msrpChunk =
                 MsrpChunk.newBuilder()
@@ -118,12 +121,18 @@
                         .continuation(Continuation.COMPLETE)
                         .addHeader(MsrpConstants.HEADER_TO_PATH, mRemoteSdp.getPath().get())
                         .addHeader(MsrpConstants.HEADER_FROM_PATH, mLocalSdp.getPath().get())
+                        .addHeader(MsrpConstants.HEADER_FAILURE_REPORT,
+                                MsrpConstants.REPORT_VALUE_YES)
+                        .addHeader(MsrpConstants.HEADER_SUCCESS_REPORT,
+                                MsrpConstants.REPORT_VALUE_NO)
                         .addHeader(
                                 MsrpConstants.HEADER_BYTE_RANGE,
                                 String.format("1-%d/%d", content.length, content.length))
                         .addHeader(MsrpConstants.HEADER_MESSAGE_ID, MsrpUtils.generateRandomId())
                         .addHeader(MsrpConstants.HEADER_CONTENT_TYPE, CPIM_CONTENT_TYPE)
                         .build();
+
+        Log.i(TAG, "Send a MSRP chunk: " + msrpChunk);
         Futures.addCallback(
                 session.send(msrpChunk),
                 new FutureCallback<MsrpChunk>() {
@@ -213,11 +222,13 @@
 
         updateRemoteUri(mInviteRequest);
 
+        SipSessionConfiguration configuration = mContext.getSipSession().getSessionConfiguration();
+        SimpleSdpMessage sdp = SdpUtils.createSdpForMsrp(configuration.getLocalIpAddress(), false);
+
         // Automatically reply back to the invite by building a pre-canned response.
         try {
-            SIPResponse response =
-                    SipUtils.buildInviteResponse(
-                            mContext.getSipSession().getSessionConfiguration(), invite, statusCode);
+            SIPResponse response = SipUtils.buildInviteResponse(configuration, invite, statusCode,
+                    sdp);
             return Futures.transform(
                     mService.sendSipResponse(response, this), result -> null,
                     MoreExecutors.directExecutor());
@@ -339,64 +350,78 @@
             return;
         }
 
+        SimpleSdpMessage sdp;
         try {
-            SimpleSdpMessage sdp =
-                    SimpleSdpMessage.parse(new ByteArrayInputStream(response.getRawContent()));
-            startMsrpSession(sdp);
+            sdp = SimpleSdpMessage.parse(new ByteArrayInputStream(response.getRawContent()));
         } catch (ParseException | IOException e) {
             notifyFailure("Invalid SDP in INVITE", CODE_ERROR_UNSPECIFIED);
+            return;
         }
 
-        if (mInviteRequest != null) {
-            SIPRequest ack = mInviteRequest.createAckRequest((To) response.getToHeader());
-            Futures.addCallback(
-                    mService.sendSipRequest(ack, this),
-                    new FutureCallback<Boolean>() {
-                        @Override
-                        public void onSuccess(Boolean result) {
-                            if (result) {
-                                mStartFuture.set(null);
-                                mStartFuture = null;
-                            } else {
-                                notifyFailure("Failed to send ACK", CODE_ERROR_UNSPECIFIED);
-                            }
-                        }
+        if (mInviteRequest == null) {
+            notifyFailure("No INVITE request sent out", CODE_ERROR_UNSPECIFIED);
+            return;
+        }
 
-                        @Override
-                        public void onFailure(Throwable t) {
+        SIPRequest ack = mInviteRequest.createAckRequest((To) response.getToHeader());
+        Futures.addCallback(
+                mService.sendSipRequest(ack, this),
+                new FutureCallback<Boolean>() {
+                    @Override
+                    public void onSuccess(Boolean result) {
+                        if (result) {
+                            startMsrpSession(sdp);
+                            mRemoteSdp = sdp;
+                        } else {
                             notifyFailure("Failed to send ACK", CODE_ERROR_UNSPECIFIED);
                         }
-                    },
-                    MoreExecutors.directExecutor());
-        }
+                    }
+
+                    @Override
+                    public void onFailure(Throwable t) {
+                        notifyFailure("Failed to send ACK", CODE_ERROR_UNSPECIFIED);
+                    }
+                },
+                MoreExecutors.directExecutor());
     }
 
     private void notifyFailure(String message, @ErrorCode int code) {
-        mStartFuture.setException(new ChatServiceException(message, code));
-        mStartFuture = null;
+        if (mStartFuture != null) {
+            mStartFuture.setException(new ChatServiceException(message, code));
+            mStartFuture = null;
+        }
+    }
+
+    private void notifySuccess() {
+        if (mStartFuture != null) {
+            mStartFuture.set(null);
+            mStartFuture = null;
+        }
     }
 
     private void startMsrpSession(SimpleSdpMessage remoteSdp) {
         Log.d(TAG, "Start MSRP session: " + remoteSdp);
         if (remoteSdp.getAddress().isPresent() && remoteSdp.getPort().isPresent()) {
+            String localIp = getLocalIp();
             Futures.addCallback(
                     mMsrpManager.createMsrpSession(
-                            remoteSdp.getAddress().get(), remoteSdp.getPort().getAsInt(),
-                            this::receiveMsrpChunk),
+                            remoteSdp.getAddress().get(), remoteSdp.getPort().getAsInt(), localIp,
+                            0 /* localPort */, this::receiveMsrpChunk),
                     new FutureCallback<MsrpSession>() {
                         @Override
                         public void onSuccess(MsrpSession result) {
                             mMsrpSession = result;
+                            notifySuccess();
                         }
 
                         @Override
                         public void onFailure(Throwable t) {
                             Log.e(TAG, "Failed to create msrp session", t);
+                            notifyFailure("Failed to establish msrp session",
+                                    CODE_ERROR_UNSPECIFIED);
                             terminate()
                                     .addListener(
-                                            () -> {
-                                                Log.d(TAG, "Session terminated");
-                                            },
+                                            () -> Log.d(TAG, "Session terminated"),
                                             MoreExecutors.directExecutor());
                         }
                     },
@@ -406,13 +431,36 @@
         }
     }
 
+    private String getLocalIp() {
+        SipSessionConfiguration configuration = mContext.getSipSession().getSessionConfiguration();
+        return configuration.getLocalIpAddress();
+    }
+
     private void receiveMsrpChunk(MsrpChunk chunk) {
         Log.d(TAG, "Received msrp= " + chunk + " conversation=" + mConversationId);
-        if (mListener != null) {
-            // TODO(b/173186571): Parse CPIM and invoke onMessageReceived()
+
+        MsrpChunkHeader contentTypeHeader = chunk.header("Content-Type");
+        if (chunk.content().length == 0 || contentTypeHeader == null) {
+            Log.i(TAG, "No content or Content-Type header, drop it");
+            return;
+        }
+
+        String contentType = contentTypeHeader.value();
+        if ("message/cpim".equals(contentType)) {
+            try {
+                SimpleCpimMessage cpim = SimpleCpimMessage.parse(chunk.content());
+                if (mListener != null) {
+                    mListener.onMessageReceived(cpim);
+                }
+            } catch (Exception e) {
+                Log.e(TAG, "Error while parsing cpim message.", e);
+            }
+        } else {
+            Log.w(TAG, contentType + " is not supported.");
         }
     }
 
+
     /** Set new listener for this session. */
     public void setListener(@Nullable ChatSessionListener listener) {
         mListener = listener;
diff --git a/tests/Android.bp b/tests/Android.bp
index 4eacf6d..c180476 100644
--- a/tests/Android.bp
+++ b/tests/Android.bp
@@ -14,6 +14,15 @@
 // limitations under the License.
 //
 
+package {
+    // See: http://go/android-license-faq
+    // A large-scale-change added 'default_applicable_licenses' to import
+    // all of the 'license_kinds' from "packages_services_Telephony_license"
+    // to get the below license kinds:
+    //   SPDX-license-identifier-Apache-2.0
+    default_applicable_licenses: ["packages_services_Telephony_license"],
+}
+
 android_test {
     name: "TeleServiceTests",
 
@@ -50,4 +59,3 @@
     ],
 
 }
-
diff --git a/tests/src/com/android/phone/SimPhonebookProviderTest.java b/tests/src/com/android/phone/SimPhonebookProviderTest.java
index 8778529..4ab92a7 100644
--- a/tests/src/com/android/phone/SimPhonebookProviderTest.java
+++ b/tests/src/com/android/phone/SimPhonebookProviderTest.java
@@ -118,6 +118,14 @@
 
     @Test
     public void query_entityFiles_returnsCursorWithCorrectProjection() {
+        // Null projection
+        try (Cursor cursor = mResolver.query(ElementaryFiles.CONTENT_URI, null, null,
+                null)) {
+            assertThat(Objects.requireNonNull(cursor).getColumnNames()).asList()
+                    .containsExactlyElementsIn(
+                            SimPhonebookProvider.ELEMENTARY_FILES_ALL_COLUMNS);
+        }
+
         // Empty projection
         try (Cursor cursor = mResolver.query(ElementaryFiles.CONTENT_URI, new String[0], null,
                 null)) {
@@ -211,18 +219,38 @@
     }
 
     @Test
+    public void query_entityFilesItem_nullProjection_returnsCursorWithCorrectProjection() {
+        setupSimsWithSubscriptionIds(1);
+        mIccPhoneBook.makeAllEfsSupported(1);
+
+        // Null projection
+        try (Cursor cursor = mResolver.query(ElementaryFiles.getItemUri(1, EF_ADN), null, null,
+                null)) {
+            assertThat(Objects.requireNonNull(cursor).getColumnNames()).asList()
+                    .containsExactlyElementsIn(
+                            SimPhonebookProvider.ELEMENTARY_FILES_ALL_COLUMNS);
+        }
+    }
+
+    @Test
     public void query_adnRecords_returnsCursorWithMatchingProjection() {
         setupSimsWithSubscriptionIds(1);
         mIccPhoneBook.makeAllEfsSupported(1);
         Uri contentAdn = SimRecords.getContentUri(1, EF_ADN);
 
+        // Null projection
+        try (Cursor cursor = mResolver.query(contentAdn, null, null, null)) {
+            assertThat(Objects.requireNonNull(cursor).getColumnNames()).asList()
+                    .containsExactlyElementsIn(SimPhonebookProvider.SIM_RECORDS_ALL_COLUMNS);
+        }
+
         // Empty projection
         try (Cursor cursor = mResolver.query(contentAdn, new String[0], null, null)) {
             assertThat(cursor).hasColumnNames();
         }
 
         // Single column
-        try (Cursor cursor = mResolver.query(contentAdn, new String[] {
+        try (Cursor cursor = mResolver.query(contentAdn, new String[]{
                 SimRecords.PHONE_NUMBER
         }, null, null)) {
             assertThat(cursor).hasColumnNames(SimRecords.PHONE_NUMBER);
@@ -531,6 +559,20 @@
     }
 
     @Test
+    public void query_itemUriNullProjection_returnsCursorWithAllColumns() {
+        setupSimsWithSubscriptionIds(1);
+        mIccPhoneBook.makeAllEfsSupported(1);
+
+        try (Cursor cursor = mResolver.query(SimRecords.getItemUri(1, ElementaryFiles.EF_ADN, 1),
+                null, null, null)
+        ) {
+            assertThat(Objects.requireNonNull(
+                    cursor).getColumnNames()).asList().containsExactlyElementsIn(
+                    SimPhonebookProvider.SIM_RECORDS_ALL_COLUMNS);
+        }
+    }
+
+    @Test
     public void query_itemUriEmptyRecord_returnsEmptyCursor() {
         setupSimsWithSubscriptionIds(1);
         mIccPhoneBook.setRecordsSize(1, IccConstants.EF_ADN, 1, 30);