Merge "Remove module source code from test-api-stubs-docs"
diff --git a/api/test-current.txt b/api/test-current.txt
index 9acacdb..912f33c 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -3189,6 +3189,7 @@
     field public static final String DYNAMIC_POWER_SAVINGS_DISABLE_THRESHOLD = "dynamic_power_savings_disable_threshold";
     field public static final String DYNAMIC_POWER_SAVINGS_ENABLED = "dynamic_power_savings_enabled";
     field public static final String HIDDEN_API_BLACKLIST_EXEMPTIONS = "hidden_api_blacklist_exemptions";
+    field public static final String HIDDEN_API_POLICY = "hidden_api_policy";
     field public static final String HIDE_ERROR_DIALOGS = "hide_error_dialogs";
     field public static final String LOCATION_GLOBAL_KILL_SWITCH = "location_global_kill_switch";
     field public static final String LOCATION_IGNORE_SETTINGS_PACKAGE_WHITELIST = "location_ignore_settings_package_whitelist";
diff --git a/api/test-lint-baseline.txt b/api/test-lint-baseline.txt
index 0287a28..f5ab40a 100644
--- a/api/test-lint-baseline.txt
+++ b/api/test-lint-baseline.txt
@@ -2499,6 +2499,8 @@
     
 NoSettingsProvider: android.provider.Settings.Global#HIDDEN_API_BLACKLIST_EXEMPTIONS:
     
+NoSettingsProvider: android.provider.Settings.Global#HIDDEN_API_POLICY:
+    
 NoSettingsProvider: android.provider.Settings.Global#HIDE_ERROR_DIALOGS:
     
 NoSettingsProvider: android.provider.Settings.Global#LOCATION_GLOBAL_KILL_SWITCH:
diff --git a/core/java/android/companion/CompanionDeviceManager.java b/core/java/android/companion/CompanionDeviceManager.java
index 591a714..ce4024a 100644
--- a/core/java/android/companion/CompanionDeviceManager.java
+++ b/core/java/android/companion/CompanionDeviceManager.java
@@ -131,7 +131,7 @@
      * you use the {@link android.Manifest.permission#REQUEST_COMPANION_RUN_IN_BACKGROUND} and {@link
      * android.Manifest.permission#REQUEST_COMPANION_USE_DATA_IN_BACKGROUND} respectively. Note that these
      * special capabilities have a negative effect on the device's battery and user's data
-     * usage, therefore you should requested them when absolutely necessary.</p>
+     * usage, therefore you should request them when absolutely necessary.</p>
      *
      * <p>You can call {@link #getAssociations} to get the list of currently associated
      * devices, and {@link #disassociate} to remove an association. Consider doing so when the
diff --git a/core/java/android/net/TEST_MAPPING b/core/java/android/net/TEST_MAPPING
new file mode 100644
index 0000000..abac811
--- /dev/null
+++ b/core/java/android/net/TEST_MAPPING
@@ -0,0 +1,20 @@
+{
+  "imports": [
+    {
+      // Also includes cts/tests/tests/net
+      "path": "frameworks/base/tests/net"
+    },
+    {
+      "path": "packages/modules/NetworkStack"
+    },
+    {
+      "path": "packages/modules/CaptivePortalLogin"
+    },
+    {
+      "path": "frameworks/base/packages/Tethering"
+    },
+    {
+      "path": "frameworks/opt/net/wifi"
+    }
+  ]
+}
\ No newline at end of file
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 9ee8898..d9aae25 100755
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -13278,6 +13278,7 @@
          *
          * @hide
          */
+        @TestApi
         public static final String HIDDEN_API_POLICY = "hidden_api_policy";
 
         /**
diff --git a/core/java/android/text/format/DateFormat.java b/core/java/android/text/format/DateFormat.java
index 38e3b39..4a0bec1 100755
--- a/core/java/android/text/format/DateFormat.java
+++ b/core/java/android/text/format/DateFormat.java
@@ -17,10 +17,14 @@
 package android.text.format;
 
 import android.annotation.NonNull;
+import android.app.compat.CompatChanges;
+import android.compat.annotation.ChangeId;
+import android.compat.annotation.EnabledAfter;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.Context;
 import android.icu.text.DateFormatSymbols;
 import android.icu.text.DateTimePatternGenerator;
+import android.os.Build;
 import android.provider.Settings;
 import android.text.SpannableStringBuilder;
 import android.text.Spanned;
@@ -159,6 +163,16 @@
     private static boolean sIs24Hour;
 
     /**
+     * {@link #getBestDateTimePattern(Locale, String)} does not allow non-consecutive repeated
+     * symbol in the skeleton. For example, please use a skeleton of {@code "jmm"} or
+     * {@code "hmma"} instead of {@code "ahmma"} or {@code "jmma"}, because the field 'j' could
+     * mean using 12-hour in some locales and, in this case, is duplicated as the 'a' field.
+     */
+    @ChangeId
+    @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.R)
+    static final long DISALLOW_DUPLICATE_FIELD_IN_SKELETON = 170233598L;
+
+    /**
      * Returns true if times should be formatted as 24 hour times, false if times should be
      * formatted as 12 hour (AM/PM) times. Based on the user's chosen locale and other preferences.
      * @param context the context to use for the content resolver
@@ -251,7 +265,9 @@
      */
     public static String getBestDateTimePattern(Locale locale, String skeleton) {
         DateTimePatternGenerator dtpg = DateTimePatternGenerator.getInstance(locale);
-        return dtpg.getBestPattern(skeleton);
+        boolean allowDuplicateFields = !CompatChanges.isChangeEnabled(
+                DISALLOW_DUPLICATE_FIELD_IN_SKELETON);
+        return dtpg.getBestPattern(skeleton, allowDuplicateFields);
     }
 
     /**
diff --git a/core/java/android/uwb/UwbAddress.java b/core/java/android/uwb/UwbAddress.java
new file mode 100644
index 0000000..48fcb10e
--- /dev/null
+++ b/core/java/android/uwb/UwbAddress.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.uwb;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+
+/**
+ * A class representing a UWB address
+ *
+ * @hide
+ */
+public final class UwbAddress {
+    public static final int SHORT_ADDRESS_BYTE_LENGTH = 2;
+    public static final int EXTENDED_ADDRESS_BYTE_LENGTH = 8;
+
+    /**
+     * Create a {@link UwbAddress} from a byte array.
+     *
+     * <p>If the provided array is {@link #SHORT_ADDRESS_BYTE_LENGTH} bytes, a short address is
+     * created. If the provided array is {@link #EXTENDED_ADDRESS_BYTE_LENGTH} bytes, then an
+     * extended address is created.
+     *
+     * @param address a byte array to convert to a {@link UwbAddress}
+     * @return a {@link UwbAddress} created from the input byte array
+     * @throw IllegableArumentException when the length is not one of
+     *       {@link #SHORT_ADDRESS_BYTE_LENGTH} or {@link #EXTENDED_ADDRESS_BYTE_LENGTH} bytes
+     */
+    @NonNull
+    public static UwbAddress fromBytes(byte[] address) throws IllegalArgumentException {
+        throw new UnsupportedOperationException();
+    }
+
+    /**
+     * Get the address as a byte array
+     *
+     * @return the byte representation of this {@link UwbAddress}
+     */
+    @NonNull
+    public byte[] toBytes() {
+        throw new UnsupportedOperationException();
+    }
+
+    /**
+     * The length of the address in bytes
+     * <p>Possible values are {@link #SHORT_ADDRESS_BYTE_LENGTH} and
+     * {@link #EXTENDED_ADDRESS_BYTE_LENGTH}.
+     */
+    public int size() {
+        throw new UnsupportedOperationException();
+    }
+
+    @NonNull
+    @Override
+    public String toString() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public boolean equals(@Nullable Object obj) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public int hashCode() {
+        throw new UnsupportedOperationException();
+    }
+}
diff --git a/core/java/com/android/internal/app/ResolverActivity.java b/core/java/com/android/internal/app/ResolverActivity.java
index 233231c..16991b4 100644
--- a/core/java/com/android/internal/app/ResolverActivity.java
+++ b/core/java/com/android/internal/app/ResolverActivity.java
@@ -1281,7 +1281,7 @@
     private void safelyStartActivityInternal(TargetInfo cti) {
         // If the target is suspended, the activity will not be successfully launched.
         // Do not unregister from package manager updates in this case
-        if (!cti.isSuspended()) {
+        if (!cti.isSuspended() && mRegistered) {
             if (mPersonalPackageMonitor != null) {
                 mPersonalPackageMonitor.unregister();
             }
diff --git a/core/jni/android_view_InputEventReceiver.cpp b/core/jni/android_view_InputEventReceiver.cpp
index cc94d6f..4220c1d 100644
--- a/core/jni/android_view_InputEventReceiver.cpp
+++ b/core/jni/android_view_InputEventReceiver.cpp
@@ -321,7 +321,7 @@
                                     jboolean(focusEvent->getHasFocus()),
                                     jboolean(focusEvent->getInTouchMode()));
                 finishInputEvent(seq, true /* handled */);
-                return OK;
+                continue;
             }
 
             default:
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index e0b58dd..b9199f7 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -1780,6 +1780,9 @@
          Note: This config is deprecated, please use config_defaultSms instead. -->
     <string name="default_sms_application" translatable="false">com.android.messaging</string>
 
+    <!-- Flag indicating whether the current device supports "Ask every time" for sms-->
+    <bool name="config_sms_ask_every_time_support">true</bool>
+
     <!-- Flag indicating whether the current device allows data.
          If true, this means that the device supports data connectivity through
          the telephony network.
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index b130b91..f9a7ba0 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -312,6 +312,7 @@
   <java-symbol type="bool" name="config_networkSamplingWakesDevice" />
   <java-symbol type="bool" name="config_showMenuShortcutsWhenKeyboardPresent" />
   <java-symbol type="bool" name="config_sip_wifi_only" />
+  <java-symbol type="bool" name="config_sms_ask_every_time_support" />
   <java-symbol type="bool" name="config_sms_capable" />
   <java-symbol type="bool" name="config_sms_utf8_support" />
   <java-symbol type="bool" name="config_mobile_data_capable" />
diff --git a/core/sysprop/Android.bp b/core/sysprop/Android.bp
index 7f20a0b..237ede2 100644
--- a/core/sysprop/Android.bp
+++ b/core/sysprop/Android.bp
@@ -19,3 +19,11 @@
     api_packages: ["android.sysprop"],
     vendor_available: false,
 }
+
+sysprop_library {
+    name: "com.android.sysprop.watchdog",
+    srcs: ["WatchdogProperties.sysprop"],
+    property_owner: "Platform",
+    api_packages: ["android.sysprop"],
+    vendor_available: false,
+}
diff --git a/core/sysprop/WatchdogProperties.sysprop b/core/sysprop/WatchdogProperties.sysprop
new file mode 100644
index 0000000..1bcc773
--- /dev/null
+++ b/core/sysprop/WatchdogProperties.sysprop
@@ -0,0 +1,45 @@
+# Copyright (C) 2020 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+module: "android.sysprop.WatchdogProperties"
+owner: Platform
+
+# To escape the watchdog timeout loop, fatal reboot the system when
+# watchdog timed out 'fatal_count' times in 'fatal_window_second'
+# seconds, if both values are not 0. Default value of both is 0.
+prop {
+    api_name: "fatal_count"
+    type: Integer
+    prop_name: "framework_watchdog.fatal_count"
+    scope: Internal
+    access: Readonly
+}
+
+prop {
+    api_name: "fatal_window_second"
+    type: Integer
+    prop_name: "framework_watchdog.fatal_window.second"
+    scope: Internal
+    access: Readonly
+}
+
+# The fatal counting can be disabled by setting property
+# 'is_fatal_ignore' to true.
+prop {
+    api_name: "is_fatal_ignore"
+    type: Boolean
+    prop_name: "persist.debug.framework_watchdog.fatal_ignore"
+    scope: Internal
+    access: Readonly
+}
diff --git a/core/sysprop/api/com.android.sysprop.localization-current.txt b/core/sysprop/api/com.android.sysprop.localization-current.txt
index fe4f457..e69de29 100644
--- a/core/sysprop/api/com.android.sysprop.localization-current.txt
+++ b/core/sysprop/api/com.android.sysprop.localization-current.txt
@@ -1,9 +0,0 @@
-props {
-  module: "android.sysprop.LocalizationProperties"
-  prop {
-    api_name: "locale_filter"
-    type: String
-    scope: Internal
-    prop_name: "ro.localization.locale_filter"
-  }
-}
diff --git a/core/sysprop/api/com.android.sysprop.watchdog-current.txt b/core/sysprop/api/com.android.sysprop.watchdog-current.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/core/sysprop/api/com.android.sysprop.watchdog-current.txt
diff --git a/core/sysprop/api/com.android.sysprop.watchdog-latest.txt b/core/sysprop/api/com.android.sysprop.watchdog-latest.txt
new file mode 100644
index 0000000..d901aef
--- /dev/null
+++ b/core/sysprop/api/com.android.sysprop.watchdog-latest.txt
@@ -0,0 +1,20 @@
+props {
+  module: "android.sysprop.WatchdogProperties"
+  prop {
+    api_name: "fatal_count"
+    type: Integer
+    scope: Internal
+    prop_name: "framework_watchdog.fatal_count"
+  }
+  prop {
+    api_name: "fatal_window_second"
+    type: Integer
+    scope: Internal
+    prop_name: "framework_watchdog.fatal_window.second"
+  }
+  prop {
+    api_name: "is_fatal_ignore"
+    scope: Internal
+    prop_name: "persist.debug.framework_watchdog.fatal_ignore"
+  }
+}
diff --git a/core/tests/coretests/src/android/text/format/DateFormatTest.java b/core/tests/coretests/src/android/text/format/DateFormatTest.java
index a3434e8..212cc44 100644
--- a/core/tests/coretests/src/android/text/format/DateFormatTest.java
+++ b/core/tests/coretests/src/android/text/format/DateFormatTest.java
@@ -21,13 +21,19 @@
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 
+import android.compat.testing.PlatformCompatChangeRule;
 import android.icu.text.DateFormatSymbols;
 import android.platform.test.annotations.Presubmit;
 
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
+import libcore.junit.util.compat.CoreCompatChangeRule.DisableCompatChanges;
+import libcore.junit.util.compat.CoreCompatChangeRule.EnableCompatChanges;
+
+import org.junit.Rule;
 import org.junit.Test;
+import org.junit.rules.TestRule;
 import org.junit.runner.RunWith;
 
 import java.util.Arrays;
@@ -38,6 +44,9 @@
 @RunWith(AndroidJUnit4.class)
 public class DateFormatTest {
 
+    @Rule
+    public TestRule compatChangeRule = new PlatformCompatChangeRule();
+
     @Test
     public void testHasDesignator() {
         assertTrue(DateFormat.hasDesignator("hh:mm:ss", DateFormat.MINUTE));
@@ -135,4 +144,29 @@
     private static String best(Locale l, String skeleton) {
         return DateFormat.getBestDateTimePattern(l, skeleton);
     }
+
+    @Test
+    @EnableCompatChanges({DateFormat.DISALLOW_DUPLICATE_FIELD_IN_SKELETON})
+    public void testGetBestDateTimePattern_disableDuplicateField() {
+        assertIllegalArgumentException(Locale.US, "jmma");
+        assertIllegalArgumentException(Locale.US, "ahmma");
+    }
+
+    @Test
+    @DisableCompatChanges({DateFormat.DISALLOW_DUPLICATE_FIELD_IN_SKELETON})
+    public void testGetBestDateTimePattern_enableDuplicateField() {
+        // en-US uses 12-hour format by default.
+        assertEquals("h:mm a", DateFormat.getBestDateTimePattern(Locale.US, "jmma"));
+        assertEquals("h:mm a", DateFormat.getBestDateTimePattern(Locale.US, "ahmma"));
+    }
+
+    private static void assertIllegalArgumentException(Locale l, String skeleton) {
+        try {
+            DateFormat.getBestDateTimePattern(l, skeleton);
+            fail("getBestDateTimePattern() does not fail with Locale: " + l
+                    + " skeleton: " + skeleton);
+        } catch (IllegalArgumentException expected) {
+            // ignored
+        }
+    }
 }
diff --git a/core/tests/coretests/src/android/text/format/OWNERS b/core/tests/coretests/src/android/text/format/OWNERS
new file mode 100644
index 0000000..32adc12
--- /dev/null
+++ b/core/tests/coretests/src/android/text/format/OWNERS
@@ -0,0 +1,3 @@
+# Inherits OWNERS from parent directory, plus the following
+
+vichang@google.com
diff --git a/location/java/com/android/internal/location/GpsNetInitiatedHandler.java b/location/java/com/android/internal/location/GpsNetInitiatedHandler.java
index 67a040d..139474c 100644
--- a/location/java/com/android/internal/location/GpsNetInitiatedHandler.java
+++ b/location/java/com/android/internal/location/GpsNetInitiatedHandler.java
@@ -28,7 +28,6 @@
 import android.os.RemoteException;
 import android.os.SystemClock;
 import android.os.UserHandle;
-import android.telephony.PhoneNumberUtils;
 import android.telephony.PhoneStateListener;
 import android.telephony.TelephonyManager;
 import android.util.Log;
@@ -161,7 +160,7 @@
                        be set to true when the phone is having emergency call, and then will
                        be set to false by mPhoneStateListener when the emergency call ends.
                 */
-                mIsInEmergencyCall = PhoneNumberUtils.isEmergencyNumber(phoneNumber);
+                mIsInEmergencyCall = mTelephonyManager.isEmergencyNumber(phoneNumber);
                 if (DEBUG) Log.v(TAG, "ACTION_NEW_OUTGOING_CALL - " + getInEmergency());
             } else if (action.equals(LocationManager.MODE_CHANGED_ACTION)) {
                 updateLocationMode();
diff --git a/media/java/android/media/AudioTrack.java b/media/java/android/media/AudioTrack.java
index 1c0a526..eff56f3 100644
--- a/media/java/android/media/AudioTrack.java
+++ b/media/java/android/media/AudioTrack.java
@@ -1261,14 +1261,20 @@
 
             // TODO: Check mEncapsulationMode compatibility with MODE_STATIC, etc?
 
-            try {
-                // If the buffer size is not specified in streaming mode,
-                // use a single frame for the buffer size and let the
-                // native code figure out the minimum buffer size.
-                if (mMode == MODE_STREAM && mBufferSizeInBytes == 0) {
-                    mBufferSizeInBytes = mFormat.getChannelCount()
-                            * mFormat.getBytesPerSample(mFormat.getEncoding());
+            // If the buffer size is not specified in streaming mode,
+            // use a single frame for the buffer size and let the
+            // native code figure out the minimum buffer size.
+            if (mMode == MODE_STREAM && mBufferSizeInBytes == 0) {
+                int bytesPerSample = 1;
+                try {
+                    bytesPerSample = mFormat.getBytesPerSample(mFormat.getEncoding());
+                } catch (IllegalArgumentException e) {
+                    // do nothing
                 }
+                mBufferSizeInBytes = mFormat.getChannelCount() * bytesPerSample;
+            }
+
+            try {
                 final AudioTrack track = new AudioTrack(
                         mAttributes, mFormat, mBufferSizeInBytes, mMode, mSessionId, mOffload,
                         mEncapsulationMode, mTunerConfiguration);
diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenMediaRecorder.java b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenMediaRecorder.java
index 1a9abb9..e6f43c1 100644
--- a/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenMediaRecorder.java
+++ b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenMediaRecorder.java
@@ -132,7 +132,7 @@
         mMediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.H264);
         mMediaRecorder.setVideoEncodingProfileLevel(
                 MediaCodecInfo.CodecProfileLevel.AVCProfileHigh,
-                MediaCodecInfo.CodecProfileLevel.AVCLevel42);
+                MediaCodecInfo.CodecProfileLevel.AVCLevel3);
         mMediaRecorder.setVideoSize(screenWidth, screenHeight);
         mMediaRecorder.setVideoFrameRate(refereshRate);
         mMediaRecorder.setVideoEncodingBitRate(vidBitRate);
diff --git a/packages/Tethering/src/com/android/networkstack/tethering/PrivateAddressCoordinator.java b/packages/Tethering/src/com/android/networkstack/tethering/PrivateAddressCoordinator.java
index 0cf14e3..9fc1d7e 100644
--- a/packages/Tethering/src/com/android/networkstack/tethering/PrivateAddressCoordinator.java
+++ b/packages/Tethering/src/com/android/networkstack/tethering/PrivateAddressCoordinator.java
@@ -36,6 +36,7 @@
 import android.util.ArraySet;
 import android.util.SparseArray;
 
+import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 
 import com.android.internal.annotations.VisibleForTesting;
@@ -135,7 +136,6 @@
     private void handleMaybePrefixConflict(final List<IpPrefix> prefixes) {
         for (IpServer downstream : mDownstreams) {
             final IpPrefix target = getDownstreamPrefix(downstream);
-            if (target == null) continue;
 
             for (IpPrefix source : prefixes) {
                 if (isConflictPrefix(source, target)) {
@@ -179,6 +179,7 @@
         final LinkAddress cachedAddress = mCachedAddresses.get(ipServer.interfaceType());
         if (useLastAddress && cachedAddress != null
                 && !isConflictWithUpstream(asIpPrefix(cachedAddress))) {
+            mDownstreams.add(ipServer);
             return cachedAddress;
         }
 
@@ -370,7 +371,6 @@
         // in mCachedAddresses.
         for (IpServer downstream : mDownstreams) {
             final IpPrefix target = getDownstreamPrefix(downstream);
-            if (target == null) continue;
 
             if (isConflictPrefix(prefix, target)) return target;
         }
@@ -378,9 +378,9 @@
         return null;
     }
 
+    @NonNull
     private IpPrefix getDownstreamPrefix(final IpServer downstream) {
         final LinkAddress address = downstream.getAddress();
-        if (address == null) return null;
 
         return asIpPrefix(address);
     }
diff --git a/packages/Tethering/tests/unit/src/com/android/networkstack/tethering/PrivateAddressCoordinatorTest.java b/packages/Tethering/tests/unit/src/com/android/networkstack/tethering/PrivateAddressCoordinatorTest.java
index da13e34..8cb80ba 100644
--- a/packages/Tethering/tests/unit/src/com/android/networkstack/tethering/PrivateAddressCoordinatorTest.java
+++ b/packages/Tethering/tests/unit/src/com/android/networkstack/tethering/PrivateAddressCoordinatorTest.java
@@ -104,24 +104,30 @@
                 mTetheringPrefixes));
     }
 
+    private LinkAddress requestDownstreamAddress(final IpServer ipServer, boolean useLastAddress) {
+        final LinkAddress address = mPrivateAddressCoordinator.requestDownstreamAddress(
+                ipServer, useLastAddress);
+        when(ipServer.getAddress()).thenReturn(address);
+        return address;
+    }
+
     @Test
     public void testRequestDownstreamAddressWithoutUsingLastAddress() throws Exception {
         final IpPrefix bluetoothPrefix = asIpPrefix(mBluetoothAddress);
-        final LinkAddress address = mPrivateAddressCoordinator.requestDownstreamAddress(
-                mHotspotIpServer, false /* useLastAddress */);
+        final LinkAddress address = requestDownstreamAddress(mHotspotIpServer,
+                false /* useLastAddress */);
         final IpPrefix hotspotPrefix = asIpPrefix(address);
         assertNotEquals(hotspotPrefix, bluetoothPrefix);
-        when(mHotspotIpServer.getAddress()).thenReturn(address);
 
-        final LinkAddress newAddress = mPrivateAddressCoordinator.requestDownstreamAddress(
-                mHotspotIpServer, false /* useLastAddress */);
+        final LinkAddress newAddress = requestDownstreamAddress(mHotspotIpServer,
+                false /* useLastAddress */);
         final IpPrefix testDupRequest = asIpPrefix(newAddress);
         assertNotEquals(hotspotPrefix, testDupRequest);
         assertNotEquals(bluetoothPrefix, testDupRequest);
         mPrivateAddressCoordinator.releaseDownstream(mHotspotIpServer);
 
-        final LinkAddress usbAddress = mPrivateAddressCoordinator.requestDownstreamAddress(
-                mUsbIpServer, false /* useLastAddress */);
+        final LinkAddress usbAddress = requestDownstreamAddress(mUsbIpServer,
+                false /* useLastAddress */);
         final IpPrefix usbPrefix = asIpPrefix(usbAddress);
         assertNotEquals(usbPrefix, bluetoothPrefix);
         assertNotEquals(usbPrefix, hotspotPrefix);
@@ -132,29 +138,26 @@
     public void testSanitizedAddress() throws Exception {
         int fakeSubAddr = 0x2b00; // 43.0.
         when(mPrivateAddressCoordinator.getRandomInt()).thenReturn(fakeSubAddr);
-        LinkAddress actualAddress = mPrivateAddressCoordinator.requestDownstreamAddress(
-                mHotspotIpServer, false /* useLastAddress */);
+        LinkAddress actualAddress = requestDownstreamAddress(mHotspotIpServer,
+                false /* useLastAddress */);
         assertEquals(new LinkAddress("192.168.43.2/24"), actualAddress);
         mPrivateAddressCoordinator.releaseDownstream(mHotspotIpServer);
 
         fakeSubAddr = 0x2d01; // 45.1.
         when(mPrivateAddressCoordinator.getRandomInt()).thenReturn(fakeSubAddr);
-        actualAddress = mPrivateAddressCoordinator.requestDownstreamAddress(
-                mHotspotIpServer, false /* useLastAddress */);
+        actualAddress = requestDownstreamAddress(mHotspotIpServer, false /* useLastAddress */);
         assertEquals(new LinkAddress("192.168.45.2/24"), actualAddress);
         mPrivateAddressCoordinator.releaseDownstream(mHotspotIpServer);
 
         fakeSubAddr = 0x2eff; // 46.255.
         when(mPrivateAddressCoordinator.getRandomInt()).thenReturn(fakeSubAddr);
-        actualAddress = mPrivateAddressCoordinator.requestDownstreamAddress(
-                mHotspotIpServer, false /* useLastAddress */);
+        actualAddress = requestDownstreamAddress(mHotspotIpServer, false /* useLastAddress */);
         assertEquals(new LinkAddress("192.168.46.254/24"), actualAddress);
         mPrivateAddressCoordinator.releaseDownstream(mHotspotIpServer);
 
         fakeSubAddr = 0x2f05; // 47.5.
         when(mPrivateAddressCoordinator.getRandomInt()).thenReturn(fakeSubAddr);
-        actualAddress = mPrivateAddressCoordinator.requestDownstreamAddress(
-                mHotspotIpServer, false /* useLastAddress */);
+        actualAddress = requestDownstreamAddress(mHotspotIpServer, false /* useLastAddress */);
         assertEquals(new LinkAddress("192.168.47.5/24"), actualAddress);
         mPrivateAddressCoordinator.releaseDownstream(mHotspotIpServer);
     }
@@ -164,8 +167,8 @@
         // - Test bluetooth prefix is reserved.
         when(mPrivateAddressCoordinator.getRandomInt()).thenReturn(
                 getSubAddress(mBluetoothAddress.getAddress().getAddress()));
-        final LinkAddress hotspotAddress = mPrivateAddressCoordinator.requestDownstreamAddress(
-                mHotspotIpServer, false /* useLastAddress */);
+        final LinkAddress hotspotAddress = requestDownstreamAddress(mHotspotIpServer,
+                false /* useLastAddress */);
         final IpPrefix hotspotPrefix = asIpPrefix(hotspotAddress);
         assertNotEquals(asIpPrefix(mBluetoothAddress), hotspotPrefix);
         mPrivateAddressCoordinator.releaseDownstream(mHotspotIpServer);
@@ -173,8 +176,8 @@
         // - Test previous enabled hotspot prefix(cached prefix) is reserved.
         when(mPrivateAddressCoordinator.getRandomInt()).thenReturn(
                 getSubAddress(hotspotAddress.getAddress().getAddress()));
-        final LinkAddress usbAddress = mPrivateAddressCoordinator.requestDownstreamAddress(
-                mUsbIpServer, false /* useLastAddress */);
+        final LinkAddress usbAddress = requestDownstreamAddress(mUsbIpServer,
+                false /* useLastAddress */);
         final IpPrefix usbPrefix = asIpPrefix(usbAddress);
         assertNotEquals(asIpPrefix(mBluetoothAddress), usbPrefix);
         assertNotEquals(hotspotPrefix, usbPrefix);
@@ -183,8 +186,8 @@
         // - Test wifi p2p prefix is reserved.
         when(mPrivateAddressCoordinator.getRandomInt()).thenReturn(
                 getSubAddress(mLegacyWifiP2pAddress.getAddress().getAddress()));
-        final LinkAddress etherAddress = mPrivateAddressCoordinator.requestDownstreamAddress(
-                mEthernetIpServer, false /* useLastAddress */);
+        final LinkAddress etherAddress = requestDownstreamAddress(mEthernetIpServer,
+                false /* useLastAddress */);
         final IpPrefix etherPrefix = asIpPrefix(etherAddress);
         assertNotEquals(asIpPrefix(mLegacyWifiP2pAddress), etherPrefix);
         assertNotEquals(asIpPrefix(mBluetoothAddress), etherPrefix);
@@ -196,13 +199,12 @@
     public void testRequestLastDownstreamAddress() throws Exception {
         final int fakeHotspotSubAddr = 0x2b05; // 43.5
         when(mPrivateAddressCoordinator.getRandomInt()).thenReturn(fakeHotspotSubAddr);
-        final LinkAddress hotspotAddress = mPrivateAddressCoordinator.requestDownstreamAddress(
-                mHotspotIpServer, true /* useLastAddress */);
+        final LinkAddress hotspotAddress = requestDownstreamAddress(mHotspotIpServer,
+                true /* useLastAddress */);
         assertEquals("Wrong wifi prefix: ", new LinkAddress("192.168.43.5/24"), hotspotAddress);
-        when(mHotspotIpServer.getAddress()).thenReturn(hotspotAddress);
 
-        final LinkAddress usbAddress = mPrivateAddressCoordinator.requestDownstreamAddress(
-                mUsbIpServer, true /* useLastAddress */);
+        final LinkAddress usbAddress = requestDownstreamAddress(mUsbIpServer,
+                true /* useLastAddress */);
         assertEquals("Wrong wifi prefix: ", new LinkAddress("192.168.45.5/24"), usbAddress);
 
         mPrivateAddressCoordinator.releaseDownstream(mHotspotIpServer);
@@ -211,24 +213,19 @@
         final int newFakeSubAddr = 0x3c05;
         when(mPrivateAddressCoordinator.getRandomInt()).thenReturn(fakeHotspotSubAddr);
 
-        final LinkAddress newHotspotAddress = mPrivateAddressCoordinator.requestDownstreamAddress(
-                mHotspotIpServer, true /* useLastAddress */);
+        final LinkAddress newHotspotAddress = requestDownstreamAddress(mHotspotIpServer,
+                true /* useLastAddress */);
         assertEquals(hotspotAddress, newHotspotAddress);
-        final LinkAddress newUsbAddress = mPrivateAddressCoordinator.requestDownstreamAddress(
-                mUsbIpServer, true /* useLastAddress */);
+        final LinkAddress newUsbAddress = requestDownstreamAddress(mUsbIpServer,
+                true /* useLastAddress */);
         assertEquals(usbAddress, newUsbAddress);
 
-        // BUG: the code should detect a conflict, but it doesn't.
-        // Regression introduced in r.android.com/168169687.
-        // Ensure conflict notification works when using cached address.
-        when(mHotspotIpServer.getAddress()).thenReturn(newHotspotAddress);
-        when(mUsbIpServer.getAddress()).thenReturn(usbAddress);
         final UpstreamNetworkState wifiUpstream = buildUpstreamNetworkState(mWifiNetwork,
                 new LinkAddress("192.168.88.23/16"), null,
                 makeNetworkCapabilities(TRANSPORT_WIFI));
         mPrivateAddressCoordinator.updateUpstreamPrefix(wifiUpstream);
-        verify(mHotspotIpServer, never()).sendMessage(IpServer.CMD_NOTIFY_PREFIX_CONFLICT);
-        verify(mUsbIpServer, never()).sendMessage(IpServer.CMD_NOTIFY_PREFIX_CONFLICT);
+        verify(mHotspotIpServer).sendMessage(IpServer.CMD_NOTIFY_PREFIX_CONFLICT);
+        verify(mUsbIpServer).sendMessage(IpServer.CMD_NOTIFY_PREFIX_CONFLICT);
     }
 
     private UpstreamNetworkState buildUpstreamNetworkState(final Network network,
@@ -259,11 +256,10 @@
         // Force always get subAddress "43.5" for conflict testing.
         when(mPrivateAddressCoordinator.getRandomInt()).thenReturn(fakeHotspotSubAddr);
         // - Enable hotspot with prefix 192.168.43.0/24
-        final LinkAddress hotspotAddr = mPrivateAddressCoordinator.requestDownstreamAddress(
-                mHotspotIpServer, true /* useLastAddress */);
+        final LinkAddress hotspotAddr = requestDownstreamAddress(mHotspotIpServer,
+                true /* useLastAddress */);
         final IpPrefix hotspotPrefix = asIpPrefix(hotspotAddr);
         assertEquals("Wrong wifi prefix: ", predefinedPrefix, hotspotPrefix);
-        when(mHotspotIpServer.getAddress()).thenReturn(hotspotAddr);
         // - test mobile network with null NetworkCapabilities. Ideally this should not happen
         // because NetworkCapabilities update should always happen before LinkProperties update
         // and the UpstreamNetworkState update, just make sure no crash in this case.
@@ -314,24 +310,22 @@
         reset(mHotspotIpServer);
         // - Restart hotspot again and its prefix is different previous.
         mPrivateAddressCoordinator.releaseDownstream(mHotspotIpServer);
-        final LinkAddress hotspotAddr2 = mPrivateAddressCoordinator.requestDownstreamAddress(
-                mHotspotIpServer, true /* useLastAddress */);
+        final LinkAddress hotspotAddr2 = requestDownstreamAddress(mHotspotIpServer,
+                true /* useLastAddress */);
         final IpPrefix hotspotPrefix2 = asIpPrefix(hotspotAddr2);
         assertNotEquals(hotspotPrefix, hotspotPrefix2);
-        when(mHotspotIpServer.getAddress()).thenReturn(hotspotAddr2);
         mPrivateAddressCoordinator.updateUpstreamPrefix(v4OnlyWifi);
         verify(mHotspotIpServer, never()).sendMessage(IpServer.CMD_NOTIFY_PREFIX_CONFLICT);
         // - Usb tethering can be enabled and its prefix is different with conflict one.
-        final LinkAddress usbAddr = mPrivateAddressCoordinator.requestDownstreamAddress(
-                mUsbIpServer, true /* useLastAddress */);
+        final LinkAddress usbAddr = requestDownstreamAddress(mUsbIpServer,
+                true /* useLastAddress */);
         final IpPrefix usbPrefix = asIpPrefix(usbAddr);
         assertNotEquals(predefinedPrefix, usbPrefix);
         assertNotEquals(hotspotPrefix2, usbPrefix);
-        when(mUsbIpServer.getAddress()).thenReturn(usbAddr);
         // - Disable wifi upstream, then wifi's prefix can be selected again.
         mPrivateAddressCoordinator.removeUpstreamPrefix(mWifiNetwork);
-        final LinkAddress ethAddr = mPrivateAddressCoordinator.requestDownstreamAddress(
-                mEthernetIpServer, true /* useLastAddress */);
+        final LinkAddress ethAddr = requestDownstreamAddress(mEthernetIpServer,
+                true /* useLastAddress */);
         final IpPrefix ethPrefix = asIpPrefix(ethAddr);
         assertEquals(predefinedPrefix, ethPrefix);
     }
@@ -340,21 +334,19 @@
     public void testChooseAvailablePrefix() throws Exception {
         final int randomAddress = 0x8605; // 134.5
         when(mPrivateAddressCoordinator.getRandomInt()).thenReturn(randomAddress);
-        final LinkAddress addr0 = mPrivateAddressCoordinator.requestDownstreamAddress(
-                mHotspotIpServer, true/* useLastAddress */);
+        final LinkAddress addr0 = requestDownstreamAddress(mHotspotIpServer,
+                true /* useLastAddress */);
         // Check whether return address is prefix 192.168.0.0/16 + subAddress 0.0.134.5.
         assertEquals("Wrong prefix: ", new LinkAddress("192.168.134.5/24"), addr0);
-        when(mHotspotIpServer.getAddress()).thenReturn(addr0);
         final UpstreamNetworkState wifiUpstream = buildUpstreamNetworkState(mWifiNetwork,
                 new LinkAddress("192.168.134.13/26"), null,
                 makeNetworkCapabilities(TRANSPORT_WIFI));
         mPrivateAddressCoordinator.updateUpstreamPrefix(wifiUpstream);
 
         // Check whether return address is next prefix of 192.168.134.0/24.
-        final LinkAddress addr1 = mPrivateAddressCoordinator.requestDownstreamAddress(
-                mHotspotIpServer, true/* useLastAddress */);
+        final LinkAddress addr1 = requestDownstreamAddress(mHotspotIpServer,
+                true /* useLastAddress */);
         assertEquals("Wrong prefix: ", new LinkAddress("192.168.135.5/24"), addr1);
-        when(mHotspotIpServer.getAddress()).thenReturn(addr1);
         final UpstreamNetworkState wifiUpstream2 = buildUpstreamNetworkState(mWifiNetwork,
                 new LinkAddress("192.168.149.16/19"), null,
                 makeNetworkCapabilities(TRANSPORT_WIFI));
@@ -362,10 +354,9 @@
 
 
         // The conflict range is 128 ~ 159, so the address is 192.168.160.5/24.
-        final LinkAddress addr2 = mPrivateAddressCoordinator.requestDownstreamAddress(
-                mHotspotIpServer, true/* useLastAddress */);
+        final LinkAddress addr2 = requestDownstreamAddress(mHotspotIpServer,
+                true /* useLastAddress */);
         assertEquals("Wrong prefix: ", new LinkAddress("192.168.160.5/24"), addr2);
-        when(mHotspotIpServer.getAddress()).thenReturn(addr2);
         final UpstreamNetworkState mobileUpstream = buildUpstreamNetworkState(mMobileNetwork,
                 new LinkAddress("192.168.129.53/18"), null,
                 makeNetworkCapabilities(TRANSPORT_CELLULAR));
@@ -378,10 +369,9 @@
         mPrivateAddressCoordinator.updateUpstreamPrefix(mobileUpstream2);
 
         // The conflict range are 128 ~ 159 and 159 ~ 191, so the address is 192.168.192.5/24.
-        final LinkAddress addr3 = mPrivateAddressCoordinator.requestDownstreamAddress(
-                mHotspotIpServer, true/* useLastAddress */);
+        final LinkAddress addr3 = requestDownstreamAddress(mHotspotIpServer,
+                true /* useLastAddress */);
         assertEquals("Wrong prefix: ", new LinkAddress("192.168.192.5/24"), addr3);
-        when(mHotspotIpServer.getAddress()).thenReturn(addr3);
         final UpstreamNetworkState mobileUpstream3 = buildUpstreamNetworkState(mMobileNetwork3,
                 new LinkAddress("192.168.188.133/17"), null,
                 makeNetworkCapabilities(TRANSPORT_CELLULAR));
@@ -389,20 +379,18 @@
 
         // Conflict range: 128 ~ 255. The next available address is 192.168.0.5 because
         // 192.168.134/24 ~ 192.168.255.255/24 is not available.
-        final LinkAddress addr4 = mPrivateAddressCoordinator.requestDownstreamAddress(
-                mHotspotIpServer, true/* useLastAddress */);
+        final LinkAddress addr4 = requestDownstreamAddress(mHotspotIpServer,
+                true /* useLastAddress */);
         assertEquals("Wrong prefix: ", new LinkAddress("192.168.0.5/24"), addr4);
-        when(mHotspotIpServer.getAddress()).thenReturn(addr4);
         final UpstreamNetworkState mobileUpstream4 = buildUpstreamNetworkState(mMobileNetwork4,
                 new LinkAddress("192.168.3.59/21"), null,
                 makeNetworkCapabilities(TRANSPORT_CELLULAR));
         mPrivateAddressCoordinator.updateUpstreamPrefix(mobileUpstream4);
 
         // Conflict ranges: 128 ~ 255 and 0 ~ 7, so the address is 192.168.8.5/24.
-        final LinkAddress addr5 = mPrivateAddressCoordinator.requestDownstreamAddress(
-                mHotspotIpServer, true/* useLastAddress */);
+        final LinkAddress addr5 = requestDownstreamAddress(mHotspotIpServer,
+                true /* useLastAddress */);
         assertEquals("Wrong prefix: ", new LinkAddress("192.168.8.5/24"), addr5);
-        when(mHotspotIpServer.getAddress()).thenReturn(addr5);
         final UpstreamNetworkState mobileUpstream5 = buildUpstreamNetworkState(mMobileNetwork5,
                 new LinkAddress("192.168.68.43/21"), null,
                 makeNetworkCapabilities(TRANSPORT_CELLULAR));
@@ -410,41 +398,37 @@
 
         // Update an upstream that does *not* conflict, check whether return the same address
         // 192.168.5/24.
-        final LinkAddress addr6 = mPrivateAddressCoordinator.requestDownstreamAddress(
-                mHotspotIpServer, true/* useLastAddress */);
+        final LinkAddress addr6 = requestDownstreamAddress(mHotspotIpServer,
+                true /* useLastAddress */);
         assertEquals("Wrong prefix: ", new LinkAddress("192.168.8.5/24"), addr6);
-        when(mHotspotIpServer.getAddress()).thenReturn(addr6);
         final UpstreamNetworkState mobileUpstream6 = buildUpstreamNetworkState(mMobileNetwork6,
                 new LinkAddress("192.168.10.97/21"), null,
                 makeNetworkCapabilities(TRANSPORT_CELLULAR));
         mPrivateAddressCoordinator.updateUpstreamPrefix(mobileUpstream6);
 
         // Conflict ranges: 0 ~ 15 and 128 ~ 255, so the address is 192.168.16.5/24.
-        final LinkAddress addr7 = mPrivateAddressCoordinator.requestDownstreamAddress(
-                mHotspotIpServer, true/* useLastAddress */);
+        final LinkAddress addr7 = requestDownstreamAddress(mHotspotIpServer,
+                true /* useLastAddress */);
         assertEquals("Wrong prefix: ", new LinkAddress("192.168.16.5/24"), addr7);
-        when(mHotspotIpServer.getAddress()).thenReturn(addr7);
         final UpstreamNetworkState mobileUpstream7 = buildUpstreamNetworkState(mMobileNetwork6,
                 new LinkAddress("192.168.0.0/17"), null,
                 makeNetworkCapabilities(TRANSPORT_CELLULAR));
         mPrivateAddressCoordinator.updateUpstreamPrefix(mobileUpstream7);
 
         // Choose prefix from next range(172.16.0.0/12) when no available prefix in 192.168.0.0/16.
-        final LinkAddress addr8 = mPrivateAddressCoordinator.requestDownstreamAddress(
-                mHotspotIpServer, true/* useLastAddress */);
+        final LinkAddress addr8 = requestDownstreamAddress(mHotspotIpServer,
+                true /* useLastAddress */);
         assertEquals("Wrong prefix: ", new LinkAddress("172.16.134.5/24"), addr8);
-        when(mHotspotIpServer.getAddress()).thenReturn(addr6);
     }
 
     @Test
     public void testChoosePrefixFromDifferentRanges() throws Exception {
         final int randomAddress = 0x1f2b2a; // 31.43.42
         when(mPrivateAddressCoordinator.getRandomInt()).thenReturn(randomAddress);
-        final LinkAddress classC1 = mPrivateAddressCoordinator.requestDownstreamAddress(
-                mHotspotIpServer, true/* useLastAddress */);
+        final LinkAddress classC1 = requestDownstreamAddress(mHotspotIpServer,
+                true /* useLastAddress */);
         // Check whether return address is prefix 192.168.0.0/16 + subAddress 0.0.43.42.
         assertEquals("Wrong prefix: ", new LinkAddress("192.168.43.42/24"), classC1);
-        when(mHotspotIpServer.getAddress()).thenReturn(classC1);
         final UpstreamNetworkState wifiUpstream = buildUpstreamNetworkState(mWifiNetwork,
                 new LinkAddress("192.168.88.23/17"), null,
                 makeNetworkCapabilities(TRANSPORT_WIFI));
@@ -452,10 +436,9 @@
         verifyNotifyConflictAndRelease(mHotspotIpServer);
 
         // Check whether return address is next address of prefix 192.168.128.0/17.
-        final LinkAddress classC2 = mPrivateAddressCoordinator.requestDownstreamAddress(
-                mHotspotIpServer, true/* useLastAddress */);
+        final LinkAddress classC2 = requestDownstreamAddress(mHotspotIpServer,
+                true /* useLastAddress */);
         assertEquals("Wrong prefix: ", new LinkAddress("192.168.128.42/24"), classC2);
-        when(mHotspotIpServer.getAddress()).thenReturn(classC2);
         final UpstreamNetworkState mobileUpstream = buildUpstreamNetworkState(mMobileNetwork,
                 new LinkAddress("192.1.2.3/8"), null,
                 makeNetworkCapabilities(TRANSPORT_CELLULAR));
@@ -463,10 +446,9 @@
         verifyNotifyConflictAndRelease(mHotspotIpServer);
 
         // Check whether return address is under prefix 172.16.0.0/12.
-        final LinkAddress classB1 = mPrivateAddressCoordinator.requestDownstreamAddress(
-                mHotspotIpServer, true/* useLastAddress */);
+        final LinkAddress classB1 = requestDownstreamAddress(mHotspotIpServer,
+                true /* useLastAddress */);
         assertEquals("Wrong prefix: ", new LinkAddress("172.31.43.42/24"), classB1);
-        when(mHotspotIpServer.getAddress()).thenReturn(classB1);
         final UpstreamNetworkState mobileUpstream2 = buildUpstreamNetworkState(mMobileNetwork2,
                 new LinkAddress("172.28.123.100/14"), null,
                 makeNetworkCapabilities(TRANSPORT_CELLULAR));
@@ -475,16 +457,14 @@
 
         // 172.28.0.0 ~ 172.31.255.255 is not available.
         // Check whether return address is next address of prefix 172.16.0.0/14.
-        final LinkAddress classB2 = mPrivateAddressCoordinator.requestDownstreamAddress(
-                mHotspotIpServer, true/* useLastAddress */);
+        final LinkAddress classB2 = requestDownstreamAddress(mHotspotIpServer,
+                true /* useLastAddress */);
         assertEquals("Wrong prefix: ", new LinkAddress("172.16.0.42/24"), classB2);
-        when(mHotspotIpServer.getAddress()).thenReturn(classB2);
 
         // Check whether new downstream is next address of address 172.16.0.42/24.
-        final LinkAddress classB3 = mPrivateAddressCoordinator.requestDownstreamAddress(
-                mUsbIpServer, true/* useLastAddress */);
+        final LinkAddress classB3 = requestDownstreamAddress(mUsbIpServer,
+                true /* useLastAddress */);
         assertEquals("Wrong prefix: ", new LinkAddress("172.16.1.42/24"), classB3);
-        when(mUsbIpServer.getAddress()).thenReturn(classB3);
         final UpstreamNetworkState mobileUpstream3 = buildUpstreamNetworkState(mMobileNetwork3,
                 new LinkAddress("172.16.0.1/24"), null,
                 makeNetworkCapabilities(TRANSPORT_CELLULAR));
@@ -493,10 +473,9 @@
         verify(mUsbIpServer, never()).sendMessage(IpServer.CMD_NOTIFY_PREFIX_CONFLICT);
 
         // Check whether return address is next address of prefix 172.16.1.42/24.
-        final LinkAddress classB4 = mPrivateAddressCoordinator.requestDownstreamAddress(
-                mHotspotIpServer, true/* useLastAddress */);
+        final LinkAddress classB4 = requestDownstreamAddress(mHotspotIpServer,
+                true /* useLastAddress */);
         assertEquals("Wrong prefix: ", new LinkAddress("172.16.2.42/24"), classB4);
-        when(mHotspotIpServer.getAddress()).thenReturn(classB4);
         final UpstreamNetworkState mobileUpstream4 = buildUpstreamNetworkState(mMobileNetwork4,
                 new LinkAddress("172.16.0.1/13"), null,
                 makeNetworkCapabilities(TRANSPORT_CELLULAR));
@@ -505,15 +484,13 @@
         verifyNotifyConflictAndRelease(mUsbIpServer);
 
         // Check whether return address is next address of prefix 172.16.0.1/13.
-        final LinkAddress classB5 = mPrivateAddressCoordinator.requestDownstreamAddress(
-                mHotspotIpServer, true/* useLastAddress */);
+        final LinkAddress classB5 = requestDownstreamAddress(mHotspotIpServer,
+                true /* useLastAddress */);
         assertEquals("Wrong prefix: ", new LinkAddress("172.24.0.42/24"), classB5);
-        when(mHotspotIpServer.getAddress()).thenReturn(classB5);
         // Check whether return address is next address of prefix 172.24.0.42/24.
-        final LinkAddress classB6 = mPrivateAddressCoordinator.requestDownstreamAddress(
-                mUsbIpServer, true/* useLastAddress */);
+        final LinkAddress classB6 = requestDownstreamAddress(mUsbIpServer,
+                true /* useLastAddress */);
         assertEquals("Wrong prefix: ", new LinkAddress("172.24.1.42/24"), classB6);
-        when(mUsbIpServer.getAddress()).thenReturn(classB6);
         final UpstreamNetworkState mobileUpstream5 = buildUpstreamNetworkState(mMobileNetwork5,
                 new LinkAddress("172.24.0.1/12"), null,
                 makeNetworkCapabilities(TRANSPORT_CELLULAR));
@@ -522,13 +499,12 @@
         verifyNotifyConflictAndRelease(mUsbIpServer);
 
         // Check whether return address is prefix 10.0.0.0/8 + subAddress 0.31.43.42.
-        final LinkAddress classA1 = mPrivateAddressCoordinator.requestDownstreamAddress(
-                mHotspotIpServer, true/* useLastAddress */);
+        final LinkAddress classA1 = requestDownstreamAddress(mHotspotIpServer,
+                true /* useLastAddress */);
         assertEquals("Wrong prefix: ", new LinkAddress("10.31.43.42/24"), classA1);
-        when(mHotspotIpServer.getAddress()).thenReturn(classA1);
         // Check whether new downstream is next address of address 10.31.43.42/24.
-        final LinkAddress classA2 = mPrivateAddressCoordinator.requestDownstreamAddress(
-                mUsbIpServer, true/* useLastAddress */);
+        final LinkAddress classA2 = requestDownstreamAddress(mUsbIpServer,
+                true /* useLastAddress */);
         assertEquals("Wrong prefix: ", new LinkAddress("10.31.44.42/24"), classA2);
     }
 
@@ -547,8 +523,8 @@
     }
 
     private void assertReseveredWifiP2pPrefix() throws Exception {
-        LinkAddress address = mPrivateAddressCoordinator.requestDownstreamAddress(
-                mHotspotIpServer, true /* useLastAddress */);
+        LinkAddress address = requestDownstreamAddress(mHotspotIpServer,
+                true /* useLastAddress */);
         final IpPrefix hotspotPrefix = asIpPrefix(address);
         final IpPrefix legacyWifiP2pPrefix = asIpPrefix(mLegacyWifiP2pAddress);
         assertNotEquals(legacyWifiP2pPrefix, hotspotPrefix);
@@ -567,8 +543,8 @@
         assertReseveredWifiP2pPrefix();
 
         // If #shouldEnableWifiP2pDedicatedIp() is enabled, wifi P2P gets the configured address.
-        LinkAddress address = mPrivateAddressCoordinator.requestDownstreamAddress(
-                mWifiP2pIpServer, true /* useLastAddress */);
+        LinkAddress address = requestDownstreamAddress(mWifiP2pIpServer,
+                true /* useLastAddress */);
         assertEquals(mLegacyWifiP2pAddress, address);
         mPrivateAddressCoordinator.releaseDownstream(mWifiP2pIpServer);
     }
diff --git a/services/core/Android.bp b/services/core/Android.bp
index 776c8f5..431555b 100644
--- a/services/core/Android.bp
+++ b/services/core/Android.bp
@@ -132,6 +132,7 @@
         "netd_aidl_interfaces-platform-java",
         "overlayable_policy_aidl-java",
         "SurfaceFlingerProperties",
+        "com.android.sysprop.watchdog",
     ],
 }
 
diff --git a/services/core/java/com/android/server/Watchdog.java b/services/core/java/com/android/server/Watchdog.java
index 17c0970..418deb8 100644
--- a/services/core/java/com/android/server/Watchdog.java
+++ b/services/core/java/com/android/server/Watchdog.java
@@ -23,7 +23,9 @@
 import android.content.IntentFilter;
 import android.hidl.manager.V1_0.IServiceManager;
 import android.os.Binder;
+import android.os.Build;
 import android.os.Debug;
+import android.os.FileUtils;
 import android.os.Handler;
 import android.os.IPowerManager;
 import android.os.Looper;
@@ -31,10 +33,12 @@
 import android.os.RemoteException;
 import android.os.ServiceManager;
 import android.os.SystemClock;
+import android.os.SystemProperties;
 import android.util.EventLog;
 import android.util.Log;
 import android.util.Slog;
 import android.util.SparseArray;
+import android.sysprop.WatchdogProperties;
 
 import com.android.internal.os.ProcessCpuTracker;
 import com.android.internal.os.ZygoteConnectionConstants;
@@ -42,12 +46,16 @@
 import com.android.server.am.ActivityManagerService;
 import com.android.server.wm.SurfaceAnimationThread;
 
+import java.io.BufferedReader;
 import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileReader;
 import java.io.FileWriter;
 import java.io.IOException;
 import java.io.StringWriter;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.concurrent.TimeUnit;
 import java.util.HashSet;
 import java.util.List;
 
@@ -75,6 +83,12 @@
     private static final int WAITED_HALF = 2;
     private static final int OVERDUE = 3;
 
+    // Track watchdog timeout history and break the crash loop if there is.
+    private static final String TIMEOUT_HISTORY_FILE = "/data/system/watchdog-timeout-history.txt";
+    private static final String PROP_FATAL_LOOP_COUNT = "framework_watchdog.fatal_count";
+    private static final String PROP_FATAL_LOOP_WINDOWS_SECS =
+            "framework_watchdog.fatal_window.second";
+
     // Which native processes to dump into dropbox's stack traces
     public static final String[] NATIVE_STACKS_OF_INTEREST = new String[] {
         "/system/bin/audioserver",
@@ -688,6 +702,10 @@
                 Slog.w(TAG, "*** WATCHDOG KILLING SYSTEM PROCESS: " + subject);
                 WatchdogDiagnostics.diagnoseCheckers(blockedCheckers);
                 Slog.w(TAG, "*** GOODBYE!");
+                if (!Build.IS_USER && isCrashLoopFound()
+                        && !WatchdogProperties.is_fatal_ignore().orElse(false)) {
+                    breakCrashLoop();
+                }
                 Process.killProcess(Process.myPid());
                 System.exit(10);
             }
@@ -705,4 +723,107 @@
             Slog.w(TAG, "Failed to write to /proc/sysrq-trigger", e);
         }
     }
+
+    private void resetTimeoutHistory() {
+        writeTimeoutHistory(new ArrayList<String>());
+    }
+
+    private void writeTimeoutHistory(Iterable<String> crashHistory) {
+        String data = String.join(",", crashHistory);
+
+        try (FileWriter writer = new FileWriter(TIMEOUT_HISTORY_FILE)) {
+            writer.write(SystemProperties.get("ro.boottime.zygote"));
+            writer.write(":");
+            writer.write(data);
+        } catch (IOException e) {
+            Slog.e(TAG, "Failed to write file " + TIMEOUT_HISTORY_FILE, e);
+        }
+    }
+
+    private String[] readTimeoutHistory() {
+        final String[] emptyStringArray = {};
+
+        try (BufferedReader reader = new BufferedReader(new FileReader(TIMEOUT_HISTORY_FILE))) {
+            String line = reader.readLine();
+            if (line == null) {
+                return emptyStringArray;
+            }
+
+            String[] data = line.trim().split(":");
+            String boottime = data.length >= 1 ? data[0] : "";
+            String history = data.length >= 2 ? data[1] : "";
+            if (SystemProperties.get("ro.boottime.zygote").equals(boottime) && !history.isEmpty()) {
+                return history.split(",");
+            } else {
+                return emptyStringArray;
+            }
+        } catch (FileNotFoundException e) {
+            return emptyStringArray;
+        } catch (IOException e) {
+            Slog.e(TAG, "Failed to read file " + TIMEOUT_HISTORY_FILE, e);
+            return emptyStringArray;
+        }
+    }
+
+    private boolean hasActiveUsbConnection() {
+        try {
+            final String state = FileUtils.readTextFile(
+                    new File("/sys/class/android_usb/android0/state"),
+                    128 /*max*/, null /*ellipsis*/).trim();
+            if ("CONFIGURED".equals(state)) {
+                return true;
+            }
+        } catch (IOException e) {
+            Slog.w(TAG, "Failed to determine if device was on USB", e);
+        }
+        return false;
+    }
+
+    private boolean isCrashLoopFound() {
+        int fatalCount = WatchdogProperties.fatal_count().orElse(0);
+        long fatalWindowMs = TimeUnit.SECONDS.toMillis(
+                WatchdogProperties.fatal_window_second().orElse(0));
+        if (fatalCount == 0 || fatalWindowMs == 0) {
+            if (fatalCount != fatalWindowMs) {
+                Slog.w(TAG, String.format("sysprops '%s' and '%s' should be set or unset together",
+                            PROP_FATAL_LOOP_COUNT, PROP_FATAL_LOOP_WINDOWS_SECS));
+            }
+            return false;
+        }
+
+        // new-history = [last (fatalCount - 1) items in old-history] + [nowMs].
+        long nowMs = SystemClock.elapsedRealtime(); // Time since boot including deep sleep.
+        String[] rawCrashHistory = readTimeoutHistory();
+        ArrayList<String> crashHistory = new ArrayList<String>(Arrays.asList(Arrays.copyOfRange(
+                        rawCrashHistory,
+                        Math.max(0, rawCrashHistory.length - fatalCount - 1),
+                        rawCrashHistory.length)));
+        // Something wrong here.
+        crashHistory.add(String.valueOf(nowMs));
+        writeTimeoutHistory(crashHistory);
+
+        // Returns false if the device has an active USB connection.
+        if (hasActiveUsbConnection()) {
+            return false;
+        }
+
+        long firstCrashMs;
+        try {
+            firstCrashMs = Long.parseLong(crashHistory.get(0));
+        } catch (NumberFormatException t) {
+            Slog.w(TAG, "Failed to parseLong " + crashHistory.get(0), t);
+            resetTimeoutHistory();
+            return false;
+        }
+        return crashHistory.size() >= fatalCount && nowMs - firstCrashMs < fatalWindowMs;
+    }
+
+    private void breakCrashLoop() {
+        try (FileWriter kmsg = new FileWriter("/dev/kmsg_debug", /* append= */ true)) {
+            kmsg.append("Fatal reset to escape the system_server crashing loop\n");
+        } catch (IOException e) {
+            Slog.w(TAG, "Failed to append to kmsg", e);
+        }
+        doSysRq('c');
+    }
 }
diff --git a/services/core/java/com/android/server/net/NetworkStatsAccess.java b/services/core/java/com/android/server/net/NetworkStatsAccess.java
index 7c1c1c7..72559b4 100644
--- a/services/core/java/com/android/server/net/NetworkStatsAccess.java
+++ b/services/core/java/com/android/server/net/NetworkStatsAccess.java
@@ -28,6 +28,7 @@
 import android.app.admin.DevicePolicyManagerInternal;
 import android.content.Context;
 import android.content.pm.PackageManager;
+import android.os.Process;
 import android.os.UserHandle;
 import android.telephony.TelephonyManager;
 
@@ -113,10 +114,11 @@
                         TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS;
         boolean isDeviceOwner = dpmi != null && dpmi.isActiveAdminWithPolicy(callingUid,
                 DeviceAdminInfo.USES_POLICY_DEVICE_OWNER);
+        final int appId = UserHandle.getAppId(callingUid);
         if (hasCarrierPrivileges || isDeviceOwner
-                || UserHandle.getAppId(callingUid) == android.os.Process.SYSTEM_UID) {
-            // Carrier-privileged apps and device owners, and the system can access data usage for
-            // all apps on the device.
+                || appId == Process.SYSTEM_UID || appId == Process.NETWORK_STACK_UID) {
+            // Carrier-privileged apps and device owners, and the system (including the
+            // network stack) can access data usage for all apps on the device.
             return NetworkStatsAccess.Level.DEVICE;
         }
 
diff --git a/services/net/TEST_MAPPING b/services/net/TEST_MAPPING
new file mode 100644
index 0000000..7025dd1
--- /dev/null
+++ b/services/net/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+  "imports": [
+    {
+      "path": "frameworks/base/core/java/android/net"
+    }
+  ]
+}
\ No newline at end of file
diff --git a/services/usb/java/com/android/server/usb/MtpNotificationManager.java b/services/usb/java/com/android/server/usb/MtpNotificationManager.java
index 462ee19..39f2f29 100644
--- a/services/usb/java/com/android/server/usb/MtpNotificationManager.java
+++ b/services/usb/java/com/android/server/usb/MtpNotificationManager.java
@@ -64,12 +64,13 @@
 
     private final Context mContext;
     private final OnOpenInAppListener mListener;
+    private final Receiver mReceiver;
 
     MtpNotificationManager(Context context, OnOpenInAppListener listener) {
         mContext = context;
         mListener = listener;
-        final Receiver receiver = new Receiver();
-        context.registerReceiver(receiver, new IntentFilter(ACTION_OPEN_IN_APPS));
+        mReceiver = new Receiver();
+        context.registerReceiver(mReceiver, new IntentFilter(ACTION_OPEN_IN_APPS));
     }
 
     void showNotification(UsbDevice device) {
@@ -154,4 +155,8 @@
     static interface OnOpenInAppListener {
         void onOpenInApp(UsbDevice device);
     }
+
+    public void unregister() {
+        mContext.unregisterReceiver(mReceiver);
+    }
 }
diff --git a/services/usb/java/com/android/server/usb/UsbProfileGroupSettingsManager.java b/services/usb/java/com/android/server/usb/UsbProfileGroupSettingsManager.java
index d7b6b5d..26ee03c 100644
--- a/services/usb/java/com/android/server/usb/UsbProfileGroupSettingsManager.java
+++ b/services/usb/java/com/android/server/usb/UsbProfileGroupSettingsManager.java
@@ -261,6 +261,15 @@
     }
 
     /**
+     * Unregister all broadcast receivers. Must be called explicitly before
+     * object deletion.
+     */
+    public void unregisterReceivers() {
+        mPackageMonitor.unregister();
+        mMtpNotificationManager.unregister();
+    }
+
+    /**
      * Remove all defaults and denied packages for a user.
      *
      * @param userToRemove The user
diff --git a/services/usb/java/com/android/server/usb/UsbSettingsManager.java b/services/usb/java/com/android/server/usb/UsbSettingsManager.java
index 7b677ee..8e53ff4 100644
--- a/services/usb/java/com/android/server/usb/UsbSettingsManager.java
+++ b/services/usb/java/com/android/server/usb/UsbSettingsManager.java
@@ -124,6 +124,7 @@
             if (mSettingsByProfileGroup.indexOfKey(userToRemove.getIdentifier()) >= 0) {
                 // The user to remove is the parent user of the group. The parent is the last user
                 // that gets removed. All state will be removed with the user
+                mSettingsByProfileGroup.get(userToRemove.getIdentifier()).unregisterReceivers();
                 mSettingsByProfileGroup.remove(userToRemove.getIdentifier());
             } else {
                 // We cannot find the parent user of the user that is removed, hence try to remove
diff --git a/telecomm/java/android/telecom/CallerInfo.java b/telecomm/java/android/telecom/CallerInfo.java
index fb6f994..aff2f01 100644
--- a/telecomm/java/android/telecom/CallerInfo.java
+++ b/telecomm/java/android/telecom/CallerInfo.java
@@ -405,7 +405,8 @@
         // Change the callerInfo number ONLY if it is an emergency number
         // or if it is the voicemail number.  If it is either, take a
         // shortcut and skip the query.
-        if (PhoneNumberUtils.isLocalEmergencyNumber(context, number)) {
+        TelephonyManager tm = context.getSystemService(TelephonyManager.class);
+        if (tm.isEmergencyNumber(number)) {
             return new CallerInfo().markAsEmergency(context);
         } else if (PhoneNumberUtils.isVoiceMailNumber(null, subId, number)) {
             return new CallerInfo().markAsVoiceMail(context, subId);
diff --git a/telecomm/java/android/telecom/CallerInfoAsyncQuery.java b/telecomm/java/android/telecom/CallerInfoAsyncQuery.java
index 4a81a8e..a9e1a8f 100644
--- a/telecomm/java/android/telecom/CallerInfoAsyncQuery.java
+++ b/telecomm/java/android/telecom/CallerInfoAsyncQuery.java
@@ -34,6 +34,7 @@
 import android.provider.ContactsContract.PhoneLookup;
 import android.telephony.PhoneNumberUtils;
 import android.telephony.SubscriptionManager;
+import android.telephony.TelephonyManager;
 import android.text.TextUtils;
 
 import java.util.ArrayList;
@@ -481,7 +482,8 @@
         cw.subId = subId;
 
         // check to see if these are recognized numbers, and use shortcuts if we can.
-        if (PhoneNumberUtils.isLocalEmergencyNumber(context, number)) {
+        TelephonyManager tm = context.getSystemService(TelephonyManager.class);
+        if (tm.isEmergencyNumber(number)) {
             cw.event = EVENT_EMERGENCY_NUMBER;
         } else if (PhoneNumberUtils.isVoiceMailNumber(context, subId, number)) {
             cw.event = EVENT_VOICEMAIL_NUMBER;
diff --git a/telephony/java/android/telephony/SmsManager.java b/telephony/java/android/telephony/SmsManager.java
index af49dc4..a3cc0ab 100644
--- a/telephony/java/android/telephony/SmsManager.java
+++ b/telephony/java/android/telephony/SmsManager.java
@@ -2514,13 +2514,12 @@
     /**
      * Send an MMS message
      *
-     * <p class="note"><strong>Note:</strong> This method will never trigger an SMS disambiguation
-     * dialog. If this method is called on a device that has multiple active subscriptions, this
-     * {@link SmsManager} instance has been created with {@link #getDefault()}, and no user-defined
-     * default subscription is defined, the subscription ID associated with this message will be
-     * INVALID, which will result in the operation being completed on the subscription associated
-     * with logical slot 0. Use {@link #getSmsManagerForSubscriptionId(int)} to ensure the
-     * operation is performed on the correct subscription.
+     * <p class="note"><strong>Note:</strong> If {@link #getDefault()} is used to instantiate this
+     * manager on a multi-SIM device, this operation may fail sending the MMS message because no
+     * suitable default subscription could be found. In this case, if {@code sentIntent} is
+     * non-null, then the {@link PendingIntent} will be sent with an error code
+     * {@code RESULT_NO_DEFAULT_SMS_APP}. See {@link #getDefault()} for more information on the
+     * conditions where this operation may fail.
      * </p>
      *
      * @param context application context
@@ -2539,21 +2538,30 @@
         }
         MmsManager m = (MmsManager) context.getSystemService(Context.MMS_SERVICE);
         if (m != null) {
-            m.sendMultimediaMessage(getSubscriptionId(), contentUri, locationUrl, configOverrides,
-                    sentIntent, 0L /* messageId */);
+            resolveSubscriptionForOperation(new SubscriptionResolverResult() {
+                @Override
+                public void onSuccess(int subId) {
+                    m.sendMultimediaMessage(subId, contentUri, locationUrl, configOverrides,
+                            sentIntent, 0L /* messageId */);
+                }
+
+                @Override
+                public void onFailure() {
+                    notifySmsError(sentIntent, RESULT_NO_DEFAULT_SMS_APP);
+                }
+            });
         }
     }
 
     /**
      * Download an MMS message from carrier by a given location URL
      *
-     * <p class="note"><strong>Note:</strong> This method will never trigger an SMS disambiguation
-     * dialog. If this method is called on a device that has multiple active subscriptions, this
-     * {@link SmsManager} instance has been created with {@link #getDefault()}, and no user-defined
-     * default subscription is defined, the subscription ID associated with this message will be
-     * INVALID, which will result in the operation being completed on the subscription associated
-     * with logical slot 0. Use {@link #getSmsManagerForSubscriptionId(int)} to ensure the
-     * operation is performed on the correct subscription.
+     * <p class="note"><strong>Note:</strong> If {@link #getDefault()} is used to instantiate this
+     * manager on a multi-SIM device, this operation may fail downloading the MMS message because no
+     * suitable default subscription could be found. In this case, if {@code downloadedIntent} is
+     * non-null, then the {@link PendingIntent} will be sent with an error code
+     * {@code RESULT_NO_DEFAULT_SMS_APP}. See {@link #getDefault()} for more information on the
+     * conditions where this operation may fail.
      * </p>
      *
      * @param context application context
@@ -2576,8 +2584,18 @@
         }
         MmsManager m = (MmsManager) context.getSystemService(Context.MMS_SERVICE);
         if (m != null) {
-            m.downloadMultimediaMessage(getSubscriptionId(), locationUrl, contentUri,
-                    configOverrides, downloadedIntent, 0L /* messageId */);
+            resolveSubscriptionForOperation(new SubscriptionResolverResult() {
+                @Override
+                public void onSuccess(int subId) {
+                    m.downloadMultimediaMessage(subId, locationUrl, contentUri, configOverrides,
+                            downloadedIntent, 0L /* messageId */);
+                }
+
+                @Override
+                public void onFailure() {
+                    notifySmsError(downloadedIntent, RESULT_NO_DEFAULT_SMS_APP);
+                }
+            });
         }
     }
 
diff --git a/telephony/java/android/telephony/ims/RcsContactPresenceTuple.aidl b/telephony/java/android/telephony/ims/RcsContactPresenceTuple.aidl
new file mode 100644
index 0000000..a704702
--- /dev/null
+++ b/telephony/java/android/telephony/ims/RcsContactPresenceTuple.aidl
@@ -0,0 +1,20 @@
+/*
+ * Copyright (c) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package android.telephony.ims;
+
+parcelable RcsContactPresenceTuple;
diff --git a/telephony/java/android/telephony/ims/RcsContactPresenceTuple.java b/telephony/java/android/telephony/ims/RcsContactPresenceTuple.java
new file mode 100644
index 0000000..b0aaa92
--- /dev/null
+++ b/telephony/java/android/telephony/ims/RcsContactPresenceTuple.java
@@ -0,0 +1,360 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.telephony.ims;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.StringDef;
+import android.net.Uri;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Represents a PIDF tuple element that is part of the presence element returned from the carrier
+ * network during a SUBSCRIBE request. See RFC3863 for more information.
+ * @hide
+ */
+public class RcsContactPresenceTuple implements Parcelable {
+
+    /** The service id of the MMTEL */
+    public static final String SERVICE_ID_MMTEL = "org.3gpp.urn:urn-7:3gpp-service.ims.icsi.mmtel";
+
+    /** The service capabilities is available. */
+    public static final String TUPLE_BASIC_STATUS_OPEN = "open";
+
+    /** The service capabilities is unavailable. */
+    public static final String TUPLE_BASIC_STATUS_CLOSED = "closed";
+
+    /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
+    @StringDef(prefix = "TUPLE_BASIC_STATUS_", value = {
+        TUPLE_BASIC_STATUS_OPEN,
+        TUPLE_BASIC_STATUS_CLOSED
+    })
+    public @interface BasicStatus {}
+
+    /**
+     * An optional addition to the PIDF Presence Tuple containing service capabilities, which is
+     * defined in the servcaps element. See RFC5196, section 3.2.1.
+     */
+    public static class ServiceCapabilities implements Parcelable {
+
+        /** The service can simultaneously send and receive data. */
+        public static final String DUPLEX_MODE_FULL = "full";
+
+        /** The service can alternate between sending and receiving data.*/
+        public static final String DUPLEX_MODE_HALF = "half";
+
+        /** The service can only receive data. */
+        public static final String DUPLEX_MODE_RECEIVE_ONLY = "receive-only";
+
+        /** The service can only send data. */
+        public static final String DUPLEX_MODE_SEND_ONLY = "send-only";
+
+        /** @hide */
+        @Retention(RetentionPolicy.SOURCE)
+        @StringDef(prefix = "DUPLEX_MODE_", value = {
+            DUPLEX_MODE_FULL,
+            DUPLEX_MODE_HALF,
+            DUPLEX_MODE_RECEIVE_ONLY,
+            DUPLEX_MODE_SEND_ONLY
+        })
+        public @interface DuplexMode {}
+
+        /**
+         * Builder to help construct {@link ServiceCapabilities} instances.
+         */
+        public static class Builder {
+
+            private ServiceCapabilities mCapabilities;
+
+            /**
+             * Create the ServiceCapabilities builder, which can be used to set service capabilities
+             * as well as custom capability extensions.
+             * @param isAudioCapable Whether the audio is capable or not.
+             * @param isVideoCapable Whether the video is capable or not.
+             */
+            public Builder(boolean isAudioCapable, boolean isVideoCapable) {
+                mCapabilities = new ServiceCapabilities(isAudioCapable, isVideoCapable);
+            }
+
+            /**
+             * Add the supported duplex mode.
+             * @param mode The supported duplex mode
+             */
+            public Builder addSupportedDuplexMode(@NonNull @DuplexMode String mode) {
+                mCapabilities.mSupportedDuplexModeList.add(mode);
+                return this;
+            }
+
+            /**
+             * Add the unsupported duplex mode.
+             * @param mode The unsupported duplex mode
+             */
+            public Builder addUnsupportedDuplexMode(@NonNull @DuplexMode String mode) {
+                mCapabilities.mUnsupportedDuplexModeList.add(mode);
+                return this;
+            }
+
+            /**
+             * @return the ServiceCapabilities instance.
+             */
+            public ServiceCapabilities build() {
+                return mCapabilities;
+            }
+        }
+
+        private final boolean mIsAudioCapable;
+        private final boolean mIsVideoCapable;
+        private final @DuplexMode List<String> mSupportedDuplexModeList = new ArrayList<>();
+        private final @DuplexMode List<String> mUnsupportedDuplexModeList = new ArrayList<>();
+
+        /**
+         * Use {@link Builder} to build an instance of this interface.
+         * @param isAudioCapable Whether the audio is capable.
+         * @param isVideoCapable Whether the video is capable.
+         */
+        ServiceCapabilities(boolean isAudioCapable, boolean isVideoCapable) {
+            mIsAudioCapable = isAudioCapable;
+            mIsVideoCapable = isVideoCapable;
+        }
+
+        private ServiceCapabilities(Parcel in) {
+            mIsAudioCapable = in.readBoolean();
+            mIsVideoCapable = in.readBoolean();
+            in.readStringList(mSupportedDuplexModeList);
+            in.readStringList(mUnsupportedDuplexModeList);
+        }
+        @Override
+        public void writeToParcel(@NonNull Parcel out, int flags) {
+            out.writeBoolean(mIsAudioCapable);
+            out.writeBoolean(mIsVideoCapable);
+            out.writeStringList(mSupportedDuplexModeList);
+            out.writeStringList(mUnsupportedDuplexModeList);
+        }
+
+        @Override
+        public int describeContents() {
+            return 0;
+        }
+
+        public static final @NonNull Creator<ServiceCapabilities> CREATOR =
+                new Creator<ServiceCapabilities>() {
+                    @Override
+                    public ServiceCapabilities createFromParcel(Parcel in) {
+                        return new ServiceCapabilities(in);
+                    }
+
+                    @Override
+                    public ServiceCapabilities[] newArray(int size) {
+                        return new ServiceCapabilities[size];
+                    }
+                };
+
+        /**
+         * Query the audio capable.
+         * @return true if the audio is capable, false otherwise.
+         */
+        public boolean isAudioCapable() {
+            return mIsAudioCapable;
+        }
+
+        /**
+         * Query the video capable.
+         * @return true if the video is capable, false otherwise.
+         */
+        public boolean isVideoCapable() {
+            return mIsVideoCapable;
+        }
+
+        /**
+         * Get the supported duplex mode list.
+         * @return The list of supported duplex mode
+         */
+        public @NonNull @DuplexMode List<String> getSupportedDuplexModes() {
+            return Collections.unmodifiableList(mSupportedDuplexModeList);
+        }
+
+        /**
+         * Get the unsupported duplex mode list.
+         * @return The list of unsupported duplex mode
+         */
+        public @NonNull @DuplexMode List<String> getUnsupportedDuplexModes() {
+            return Collections.unmodifiableList(mUnsupportedDuplexModeList);
+        }
+    }
+
+    /**
+     * Builder to help construct {@link RcsContactPresenceTuple} instances.
+     */
+    public static class Builder {
+
+        private RcsContactPresenceTuple mPresenceTuple;
+
+        /**
+         * Builds a RcsContactPresenceTuple instance.
+         * @param serviceId The OMA Presence service-id associated with this capability. See the
+         * OMA Presence SIMPLE specification v1.1, section 10.5.1.
+         * @param serviceVersion The OMA Presence version associated with the service capability.
+         * See the OMA Presence SIMPLE specification v1.1, section 10.5.1.
+         */
+        public Builder(@NonNull @BasicStatus String status, @NonNull String serviceId,
+                @NonNull String serviceVersion) {
+            mPresenceTuple = new RcsContactPresenceTuple(status, serviceId, serviceVersion);
+        }
+
+        /**
+         * The optional SIP Contact URI associated with the PIDF tuple element.
+         */
+        public Builder addContactUri(@NonNull Uri contactUri) {
+            mPresenceTuple.mContactUri = contactUri;
+            return this;
+        }
+
+        /**
+         * The optional timestamp indicating the data and time of the status change of this tuple.
+         * See RFC3863, section 4.1.7 for more information on the expected format.
+         */
+        public Builder addTimeStamp(@NonNull String timestamp) {
+            mPresenceTuple.mTimestamp = timestamp;
+            return this;
+        }
+
+        /**
+         * An optional parameter containing the description element of the service-description. See
+         * OMA Presence SIMPLE specification v1.1
+         */
+        public Builder addDescription(@NonNull String description) {
+            mPresenceTuple.mServiceDescription = description;
+            return this;
+        }
+
+        /**
+         * An optional parameter containing the service capabilities of the presence tuple if they
+         * are present in the servcaps element.
+         */
+        public Builder addServiceCapabilities(@NonNull ServiceCapabilities caps) {
+            mPresenceTuple.mServiceCapabilities = caps;
+            return this;
+        }
+
+        /**
+         * @return the constructed instance.
+         */
+        public RcsContactPresenceTuple build() {
+            return mPresenceTuple;
+        }
+    }
+
+    private Uri mContactUri;
+    private String mTimestamp;
+    private @BasicStatus String mStatus;
+
+    // The service information in the service-description element.
+    private String mServiceId;
+    private String mServiceVersion;
+    private String mServiceDescription;
+
+    private ServiceCapabilities mServiceCapabilities;
+
+    private RcsContactPresenceTuple(@NonNull @BasicStatus String status, @NonNull String serviceId,
+            @NonNull String serviceVersion) {
+        mStatus = status;
+        mServiceId = serviceId;
+        mServiceVersion = serviceVersion;
+    }
+
+    private RcsContactPresenceTuple(Parcel in) {
+        mContactUri = in.readParcelable(Uri.class.getClassLoader());
+        mTimestamp = in.readString();
+        mStatus = in.readString();
+        mServiceId = in.readString();
+        mServiceVersion = in.readString();
+        mServiceDescription = in.readString();
+        mServiceCapabilities = in.readParcelable(ServiceCapabilities.class.getClassLoader());
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel out, int flags) {
+        out.writeParcelable(mContactUri, flags);
+        out.writeString(mTimestamp);
+        out.writeString(mStatus);
+        out.writeString(mServiceId);
+        out.writeString(mServiceVersion);
+        out.writeString(mServiceDescription);
+        out.writeParcelable(mServiceCapabilities, flags);
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    public static final @NonNull Creator<RcsContactPresenceTuple> CREATOR =
+            new Creator<RcsContactPresenceTuple>() {
+                @Override
+                public RcsContactPresenceTuple createFromParcel(Parcel in) {
+                    return new RcsContactPresenceTuple(in);
+                }
+
+                @Override
+                public RcsContactPresenceTuple[] newArray(int size) {
+                    return new RcsContactPresenceTuple[size];
+                }
+            };
+
+    /** @return the status of the tuple element. */
+    public @NonNull @BasicStatus String getStatus() {
+        return mStatus;
+    }
+
+    /** @return the service-id element of the service-description */
+    public @NonNull String getServiceId() {
+        return mServiceId;
+    }
+
+    /** @return the version element of the service-description */
+    public @NonNull String getServiceVersion() {
+        return mServiceVersion;
+    }
+
+    /** @return the SIP URI contained in the contact element of the tuple if it exists. */
+    public @Nullable Uri getContactUri() {
+        return mContactUri;
+    }
+
+    /** @return the timestamp element contained in the tuple if it exists */
+    public @Nullable String getTimestamp() {
+        return mTimestamp;
+    }
+
+    /** @return the description element contained in the service-description if it exists */
+    public @Nullable String getServiceDescription() {
+        return mServiceDescription;
+    }
+
+    /** @return the {@link ServiceCapabilities} of the tuple if it exists. */
+    public @Nullable ServiceCapabilities getServiceCapabilities() {
+        return mServiceCapabilities;
+    }
+}
diff --git a/telephony/java/android/telephony/ims/RcsContactUceCapability.java b/telephony/java/android/telephony/ims/RcsContactUceCapability.java
index dc36edf..d12a6ae 100644
--- a/telephony/java/android/telephony/ims/RcsContactUceCapability.java
+++ b/telephony/java/android/telephony/ims/RcsContactUceCapability.java
@@ -16,7 +16,7 @@
 
 package android.telephony.ims;
 
-import android.annotation.LongDef;
+import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.net.Uri;
@@ -27,9 +27,7 @@
 import java.lang.annotation.RetentionPolicy;
 import java.util.ArrayList;
 import java.util.Collections;
-import java.util.HashMap;
 import java.util.List;
-import java.util.Map;
 
 /**
  * Contains the User Capability Exchange capabilities corresponding to a contact's URI.
@@ -37,114 +35,80 @@
  */
 public final class RcsContactUceCapability implements Parcelable {
 
-    /** Supports 1-to-1 chat */
-    public static final int CAPABILITY_CHAT_STANDALONE = (1 << 0);
-    /** Supports group chat */
-    public static final int CAPABILITY_CHAT_SESSION = (1 << 1);
-    /** Supports full store and forward group chat information. */
-    public static final int CAPABILITY_CHAT_SESSION_STORE_FORWARD = (1 << 2);
-    /**
-     * Supports file transfer via Message Session Relay Protocol (MSRP) without Store and Forward.
-     */
-    public static final int CAPABILITY_FILE_TRANSFER = (1 << 3);
-    /** Supports File Transfer Thumbnail */
-    public static final int CAPABILITY_FILE_TRANSFER_THUMBNAIL = (1 << 4);
-    /** Supports File Transfer with Store and Forward */
-    public static final int CAPABILITY_FILE_TRANSFER_STORE_FORWARD = (1 << 5);
-    /** Supports File Transfer via HTTP */
-    public static final int CAPABILITY_FILE_TRANSFER_HTTP = (1 << 6);
-    /** Supports file transfer via SMS */
-    public static final int CAPABILITY_FILE_TRANSFER_SMS = (1 << 7);
-    /** Supports image sharing */
-    public static final int CAPABILITY_IMAGE_SHARE = (1 << 8);
-    /** Supports video sharing during a circuit-switch call (IR.74)*/
-    public static final int CAPABILITY_VIDEO_SHARE_DURING_CS_CALL = (1 << 9);
-    /** Supports video share outside of voice call (IR.84) */
-    public static final int CAPABILITY_VIDEO_SHARE = (1 << 10);
-    /** Supports social presence information */
-    public static final int CAPABILITY_SOCIAL_PRESENCE = (1 << 11);
-    /** Supports capability discovery via presence */
-    public static final int CAPABILITY_DISCOVERY_VIA_PRESENCE = (1 << 12);
-    /** Supports IP Voice calling over LTE or IWLAN (IR.92/IR.51) */
-    public static final int CAPABILITY_IP_VOICE_CALL = (1 << 13);
-    /** Supports IP video calling (IR.94) */
-    public static final int CAPABILITY_IP_VIDEO_CALL = (1 << 14);
-    /** Supports Geolocation PUSH during 1-to-1 or multiparty chat */
-    public static final int CAPABILITY_GEOLOCATION_PUSH = (1 << 15);
-    /** Supports Geolocation PUSH via SMS for fallback.  */
-    public static final int CAPABILITY_GEOLOCATION_PUSH_SMS = (1 << 16);
-    /** Supports Geolocation pull. */
-    public static final int CAPABILITY_GEOLOCATION_PULL = (1 << 17);
-    /** Supports Geolocation pull using file transfer support. */
-    public static final int CAPABILITY_GEOLOCATION_PULL_FILE_TRANSFER = (1 << 18);
-    /** Supports RCS voice calling */
-    public static final int CAPABILITY_RCS_VOICE_CALL = (1 << 19);
-    /** Supports RCS video calling */
-    public static final int CAPABILITY_RCS_VIDEO_CALL = (1 << 20);
-    /** Supports RCS video calling, where video media can not be dropped. */
-    public static final int CAPABILITY_RCS_VIDEO_ONLY_CALL = (1 << 21);
-    /** Supports call composer, where outgoing calls can be enriched with pre-call content.*/
-    public static final int CAPABILITY_CALL_COMPOSER = (1 << 22);
-    /** Supports post call information that is included in the call if the call is missed.*/
-    public static final int CAPABILITY_POST_CALL = (1 << 23);
-    /** Supports sharing a map where the user can draw, share markers, and share their position. */
-    public static final int CAPABILITY_SHARED_MAP = (1 << 24);
-    /** Supports sharing a canvas, where users can draw, add images, and change background colors.*/
-    public static final int CAPABILITY_SHARED_SKETCH = (1 << 25);
-    /** Supports communication with Chatbots. */
-    public static final int CAPABILITY_CHAT_BOT = (1 << 26);
-    /** Supports Chatbot roles. */
-    public static final int CAPABILITY_CHAT_BOT_ROLE = (1 << 27);
-    /** Supports the unidirectional plug-ins framework. */
-    public static final int CAPABILITY_PLUG_IN = (1 << 28);
-    /** Supports standalone Chatbot communication. */
-    public static final int CAPABILITY_STANDALONE_CHAT_BOT = (1 << 29);
-    /** Supports MMTEL based call composer. */
-    public static final int CAPABILITY_MMTEL_CALL_COMPOSER = (1 << 30);
+    /** Contains presence information associated with the contact */
+    public static final int CAPABILITY_MECHANISM_PRESENCE = 1;
 
+    /** Contains OPTIONS information associated with the contact */
+    public static final int CAPABILITY_MECHANISM_OPTIONS = 2;
 
-
-    /** @hide*/
+    /** @hide */
     @Retention(RetentionPolicy.SOURCE)
-    @LongDef(prefix = "CAPABILITY_", flag = true, value = {
-            CAPABILITY_CHAT_STANDALONE,
-            CAPABILITY_CHAT_SESSION,
-            CAPABILITY_CHAT_SESSION_STORE_FORWARD,
-            CAPABILITY_FILE_TRANSFER,
-            CAPABILITY_FILE_TRANSFER_THUMBNAIL,
-            CAPABILITY_FILE_TRANSFER_STORE_FORWARD,
-            CAPABILITY_FILE_TRANSFER_HTTP,
-            CAPABILITY_FILE_TRANSFER_SMS,
-            CAPABILITY_IMAGE_SHARE,
-            CAPABILITY_VIDEO_SHARE_DURING_CS_CALL,
-            CAPABILITY_VIDEO_SHARE,
-            CAPABILITY_SOCIAL_PRESENCE,
-            CAPABILITY_DISCOVERY_VIA_PRESENCE,
-            CAPABILITY_IP_VOICE_CALL,
-            CAPABILITY_IP_VIDEO_CALL,
-            CAPABILITY_GEOLOCATION_PUSH,
-            CAPABILITY_GEOLOCATION_PUSH_SMS,
-            CAPABILITY_GEOLOCATION_PULL,
-            CAPABILITY_GEOLOCATION_PULL_FILE_TRANSFER,
-            CAPABILITY_RCS_VOICE_CALL,
-            CAPABILITY_RCS_VIDEO_CALL,
-            CAPABILITY_RCS_VIDEO_ONLY_CALL,
-            CAPABILITY_CALL_COMPOSER,
-            CAPABILITY_POST_CALL,
-            CAPABILITY_SHARED_MAP,
-            CAPABILITY_SHARED_SKETCH,
-            CAPABILITY_CHAT_BOT,
-            CAPABILITY_CHAT_BOT_ROLE,
-            CAPABILITY_PLUG_IN,
-            CAPABILITY_STANDALONE_CHAT_BOT,
-            CAPABILITY_MMTEL_CALL_COMPOSER
+    @IntDef(prefix = "CAPABILITY_MECHANISM_", value = {
+        CAPABILITY_MECHANISM_PRESENCE,
+        CAPABILITY_MECHANISM_OPTIONS
     })
-    public @interface CapabilityFlag {}
+    public @interface CapabilityMechanism {}
 
     /**
-     * Builder to help construct {@link RcsContactUceCapability} instances.
+     * The capabilities of this contact were requested recently enough to still be considered in
+     * the availability window.
      */
-    public static class Builder {
+    public static final int SOURCE_TYPE_NETWORK = 0;
+
+    /**
+     * The capabilities of this contact were retrieved from the cached information in the Enhanced
+     * Address Book.
+     */
+    public static final int SOURCE_TYPE_CACHED = 1;
+
+    /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(prefix = "SOURCE_TYPE_", value = {
+        SOURCE_TYPE_NETWORK,
+        SOURCE_TYPE_CACHED
+    })
+    public @interface SourceType {}
+
+    /**
+     * The requested contact was found to be offline when queried. This is only applicable to
+     * contact capabilities that were queried via OPTIONS requests and the network returned a
+     * 408/480 response.
+     */
+    public static final int REQUEST_RESULT_NOT_ONLINE = 0;
+
+    /**
+     * Capability information for the requested contact was not found. The contact should not be
+     * considered an RCS user.
+     */
+    public static final int REQUEST_RESULT_NOT_FOUND = 1;
+
+    /**
+     * Capability information for the requested contact was found successfully.
+     */
+    public static final int REQUEST_RESULT_FOUND = 2;
+
+    /**
+     * Capability information for the requested contact has expired and can not be refreshed due to
+     * a temporary network error. This is a temporary error and the capabilities of the contact
+     * should be queried again at a later time.
+     */
+    public static final int REQUEST_RESULT_UNKNOWN = 3;
+
+    /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(prefix = "REQUEST_RESULT_", value = {
+        REQUEST_RESULT_NOT_ONLINE,
+        REQUEST_RESULT_NOT_FOUND,
+        REQUEST_RESULT_FOUND,
+        REQUEST_RESULT_UNKNOWN
+    })
+    public @interface RequestResult {}
+
+    /**
+     * Builder to help construct {@link RcsContactUceCapability} instances when capabilities were
+     * queried through SIP OPTIONS.
+     */
+    public static class OptionsBuilder {
 
         private final RcsContactUceCapability mCapabilities;
 
@@ -153,51 +117,38 @@
          * capability extensions.
          * @param contact The contact URI that the capabilities are attached to.
          */
-        public Builder(@NonNull Uri contact) {
-            mCapabilities = new RcsContactUceCapability(contact);
+        public OptionsBuilder(@NonNull Uri contact) {
+            mCapabilities = new RcsContactUceCapability(contact, CAPABILITY_MECHANISM_OPTIONS,
+                    SOURCE_TYPE_NETWORK);
         }
 
         /**
-         * Add a UCE capability bit-field as well as the associated URI that the framework should
-         * use for those services. This is mainly used for capabilities that may use a URI separate
-         * from the contact's URI, for example the URI to use for VT calls.
-         * @param type The capability to map to a service URI that is different from the contact's
-         *         URI.
+         * Set the result of the capabilities request.
+         * @param requestResult the request result
+         * @return this OptionBuilder
          */
-        public @NonNull Builder add(@CapabilityFlag long type, @NonNull Uri serviceUri) {
-            mCapabilities.mCapabilities |= type;
-            // Put each of these capabilities into the map separately.
-            for (long shift = 0; shift < Integer.SIZE; shift++) {
-                long cap = type & (1 << shift);
-                if (cap != 0) {
-                    mCapabilities.mServiceMap.put(cap, serviceUri);
-                    // remove that capability from the field.
-                    type &= ~cap;
-                }
-                if (type == 0) {
-                    // no need to keep going, end early.
-                    break;
-                }
-            }
+        public @NonNull OptionsBuilder setRequestResult(@RequestResult int requestResult) {
+            mCapabilities.mRequestResult = requestResult;
             return this;
         }
 
         /**
-         * Add a UCE capability flag that this contact supports.
-         * @param type the capability that the contact supports.
+         * Add the feature tag into the capabilities instance.
+         * @param tag the supported feature tag
+         * @return this OptionBuilder
          */
-        public @NonNull Builder add(@CapabilityFlag long type) {
-            mCapabilities.mCapabilities |= type;
+        public @NonNull OptionsBuilder addFeatureTag(String tag) {
+            mCapabilities.mFeatureTags.add(tag);
             return this;
         }
 
         /**
-         * Add a carrier specific service tag.
-         * @param extension A string containing a carrier specific service tag that is an extension
-         *         of the {@link CapabilityFlag}s that are defined here.
+         * Add the list of feature tag into the capabilities instance.
+         * @param tags the list of the supported feature tags
+         * @return this OptionBuilder
          */
-        public @NonNull Builder add(@NonNull String extension) {
-            mCapabilities.mExtensionTags.add(extension);
+        public @NonNull OptionsBuilder addFeatureTags(List<String> tags) {
+            mCapabilities.mFeatureTags.addAll(tags);
             return this;
         }
 
@@ -209,56 +160,88 @@
         }
     }
 
-    private final Uri mContactUri;
-    private long mCapabilities;
-    private List<String> mExtensionTags = new ArrayList<>();
-    private Map<Long, Uri> mServiceMap = new HashMap<>();
-
     /**
-     * Use {@link Builder} to build an instance of this interface.
-     * @param contact The URI associated with this capability information.
-     * @hide
+     * Builder to help construct {@link RcsContactUceCapability} instances when capabilities were
+     * queried through a presence server.
      */
-    RcsContactUceCapability(@NonNull Uri contact) {
-        mContactUri = contact;
+    public static class PresenceBuilder {
+
+        private final RcsContactUceCapability mCapabilities;
+
+        /**
+         * Create the builder, which can be used to set UCE capabilities as well as custom
+         * capability extensions.
+         * @param contact The contact URI that the capabilities are attached to.
+         * @param sourceType The type where the capabilities of this contact were retrieved from.
+         * @param requestResult the request result
+         */
+        public PresenceBuilder(@NonNull Uri contact, @SourceType int sourceType,
+                @RequestResult int requestResult) {
+            mCapabilities = new RcsContactUceCapability(contact, CAPABILITY_MECHANISM_PRESENCE,
+                sourceType);
+            mCapabilities.mRequestResult = requestResult;
+        }
+
+        /**
+         * Add the {@link RcsContactPresenceTuple} into the capabilities instance.
+         * @param tuple The {@link RcsContactPresenceTuple} to be added into.
+         * @return this PresenceBuilder
+         */
+        public @NonNull PresenceBuilder addCapabilityTuple(RcsContactPresenceTuple tuple) {
+            mCapabilities.mPresenceTuples.add(tuple);
+            return this;
+        }
+
+        /**
+         * Add the list of {@link RcsContactPresenceTuple} into the capabilities instance.
+         * @param tuples The list of the {@link RcsContactPresenceTuple} to be added into.
+         * @return this PresenceBuilder
+         */
+        public @NonNull PresenceBuilder addCapabilityTuples(List<RcsContactPresenceTuple> tuples) {
+            mCapabilities.mPresenceTuples.addAll(tuples);
+            return this;
+        }
+
+        /**
+         * @return the RcsContactUceCapability instance.
+         */
+        public @NonNull RcsContactUceCapability build() {
+            return mCapabilities;
+        }
+    }
+
+    private final Uri mContactUri;
+    private @SourceType int mSourceType;
+    private @CapabilityMechanism int mCapabilityMechanism;
+    private @RequestResult int mRequestResult;
+
+    private final List<String> mFeatureTags = new ArrayList<>();
+    private final List<RcsContactPresenceTuple> mPresenceTuples = new ArrayList<>();
+
+    private RcsContactUceCapability(@NonNull Uri contactUri, @CapabilityMechanism int mechanism,
+            @SourceType int sourceType) {
+        mContactUri = contactUri;
+        mCapabilityMechanism = mechanism;
+        mSourceType = sourceType;
     }
 
     private RcsContactUceCapability(Parcel in) {
         mContactUri = in.readParcelable(Uri.class.getClassLoader());
-        mCapabilities = in.readLong();
-        in.readStringList(mExtensionTags);
-        // read mServiceMap as key,value pair
-        int mapSize = in.readInt();
-        for (int i = 0; i < mapSize; i++) {
-            mServiceMap.put(in.readLong(), in.readParcelable(Uri.class.getClassLoader()));
-        }
+        mCapabilityMechanism = in.readInt();
+        mSourceType = in.readInt();
+        mRequestResult = in.readInt();
+        in.readStringList(mFeatureTags);
+        in.readParcelableList(mPresenceTuples, RcsContactPresenceTuple.class.getClassLoader());
     }
 
-    public static final @NonNull Creator<RcsContactUceCapability> CREATOR =
-            new Creator<RcsContactUceCapability>() {
-        @Override
-        public RcsContactUceCapability createFromParcel(Parcel in) {
-            return new RcsContactUceCapability(in);
-        }
-
-        @Override
-        public RcsContactUceCapability[] newArray(int size) {
-            return new RcsContactUceCapability[size];
-        }
-    };
-
     @Override
     public void writeToParcel(@NonNull Parcel out, int flags) {
-        out.writeParcelable(mContactUri, 0);
-        out.writeLong(mCapabilities);
-        out.writeStringList(mExtensionTags);
-        // write mServiceMap as key,value pairs
-        int mapSize = mServiceMap.keySet().size();
-        out.writeInt(mapSize);
-        for (long key : mServiceMap.keySet()) {
-            out.writeLong(key);
-            out.writeParcelable(mServiceMap.get(key), 0);
-        }
+        out.writeParcelable(mContactUri, flags);
+        out.writeInt(mCapabilityMechanism);
+        out.writeInt(mSourceType);
+        out.writeInt(mRequestResult);
+        out.writeStringList(mFeatureTags);
+        out.writeParcelableList(mPresenceTuples, flags);
     }
 
     @Override
@@ -266,49 +249,87 @@
         return 0;
     }
 
+    public static final @NonNull Creator<RcsContactUceCapability> CREATOR =
+            new Creator<RcsContactUceCapability>() {
+                @Override
+                public RcsContactUceCapability createFromParcel(Parcel in) {
+                    return new RcsContactUceCapability(in);
+                }
+
+                @Override
+                public RcsContactUceCapability[] newArray(int size) {
+                    return new RcsContactUceCapability[size];
+                }
+            };
+
     /**
-     * Query for a capability
-     * @param type The capability flag to query.
-     * @return true if the capability flag specified is set, false otherwise.
+     * @return The mechanism used to get the capabilities.
      */
-    public boolean isCapable(@CapabilityFlag long type) {
-        return (mCapabilities & type) > 0;
+    public @CapabilityMechanism int getCapabilityMechanism() {
+        return mCapabilityMechanism;
     }
 
     /**
-     * @return true if the extension service tag is set, false otherwise.
-     */
-    public boolean isCapable(@NonNull String extensionTag) {
-        return mExtensionTags.contains(extensionTag);
-    }
-
-    /**
-     * @return An immutable list containing all of the extension tags that have been set as capable.
-     * @throws UnsupportedOperationException if this list is modified.
-     */
-    public @NonNull List<String> getCapableExtensionTags() {
-        return Collections.unmodifiableList(mExtensionTags);
-    }
-
-    /**
-     * Retrieves the {@link Uri} associated with the capability being queried.
+     * @return The feature tags present in the OPTIONS response from the network.
      * <p>
-     * This will typically be the contact {@link Uri} available via {@link #getContactUri()} unless
-     * a different service {@link Uri} was associated with this capability using
-     * {@link Builder#add(long, Uri)}.
-     *
-     * @return a String containing the {@link Uri} associated with the service tag or
-     * {@code null} if this capability is not set as capable.
-     * @see #isCapable(long)
+     * Note: this is only populated if {@link #getCapabilityMechanism} is
+     * {@link CAPABILITY_MECHANISM_OPTIONS}
      */
-    public @Nullable Uri getServiceUri(@CapabilityFlag long type) {
-        Uri result = mServiceMap.getOrDefault(type, null);
-        // If the capability is capable, but does not have a service URI associated, use the default
-        // contact URI.
-        if (result == null) {
-            return isCapable(type) ? getContactUri() : null;
+    public @NonNull List<String> getOptionsFeatureTags() {
+        if (mCapabilityMechanism != CAPABILITY_MECHANISM_OPTIONS) {
+            return Collections.emptyList();
         }
-        return result;
+        return Collections.unmodifiableList(mFeatureTags);
+    }
+
+    /**
+     * @return The tuple elements associated with the presence element portion of the PIDF document
+     * contained in the NOTIFY response from the network.
+     * <p>
+     * Note: this is only populated if {@link #getCapabilityMechanism} is
+     * {@link CAPABILITY_MECHANISM_PRESENCE}
+     */
+    public @NonNull List<RcsContactPresenceTuple> getPresenceTuples() {
+        if (mCapabilityMechanism != CAPABILITY_MECHANISM_PRESENCE) {
+            return Collections.emptyList();
+        }
+        return Collections.unmodifiableList(mPresenceTuples);
+    }
+
+    /**
+     * Get the RcsContactPresenceTuple associated with the given service id.
+     * @param serviceId The service id to get the presence tuple.
+     * @return The RcsContactPresenceTuple which has the given service id.
+     *
+     * <p>
+     * Note: this is only populated if {@link #getCapabilityMechanism} is
+     * {@link CAPABILITY_MECHANISM_PRESENCE}
+     */
+    public @Nullable RcsContactPresenceTuple getPresenceTuple(String serviceId) {
+        if (mCapabilityMechanism != CAPABILITY_MECHANISM_PRESENCE) {
+            return null;
+        }
+        for (RcsContactPresenceTuple tuple : mPresenceTuples) {
+            if (tuple.getServiceId().equals(serviceId)) {
+                return tuple;
+            }
+        }
+        return null;
+    }
+
+    /**
+     * @return the source of the data that was used to populate the capabilities of the requested
+     * contact.
+     */
+    public @SourceType int getSourceType() {
+        return mSourceType;
+    }
+
+    /**
+     * @return the result of querying the capabilities of the requested contact.
+     */
+    public @RequestResult int getRequestResult() {
+        return mRequestResult;
     }
 
     /**
diff --git a/tests/net/TEST_MAPPING b/tests/net/TEST_MAPPING
index 005cbe9..89fc6ea 100644
--- a/tests/net/TEST_MAPPING
+++ b/tests/net/TEST_MAPPING
@@ -8,5 +8,10 @@
     {
       "name": "FrameworksNetDeflakeTest"
     }
+  ],
+  "imports": [
+    {
+      "path": "cts/tests/tests/net"
+    }
   ]
 }
\ No newline at end of file