Merge "Migrate Cellbroadcast modules to use modules-utils"
diff --git a/Android.bp b/Android.bp
index a343308..1fbdc15 100644
--- a/Android.bp
+++ b/Android.bp
@@ -118,7 +118,6 @@
         ":libbluetooth-binder-aidl",
         ":libcamera_client_aidl",
         ":libcamera_client_framework_aidl",
-        ":packagemanager_aidl",
         ":libupdate_engine_aidl",
         ":resourcemanager_aidl",
         ":storaged_aidl",
@@ -209,6 +208,7 @@
     name: "framework-internal-utils",
     static_libs: [
         "apex_aidl_interface-java",
+        "packagemanager_aidl-java",
         "framework-protos",
         "updatable-driver-protos",
         "ota_metadata_proto_java",
@@ -357,9 +357,6 @@
     srcs: [
         "core/java/android/net/annotations/PolicyDirection.java",
         "core/java/com/android/internal/util/HexDump.java",
-        "core/java/com/android/internal/util/IState.java",
-        "core/java/com/android/internal/util/State.java",
-        "core/java/com/android/internal/util/StateMachine.java",
         "core/java/com/android/internal/util/WakeupMessage.java",
         "services/core/java/com/android/server/vcn/util/PersistableBundleUtils.java",
         "telephony/java/android/telephony/Annotation.java",
@@ -411,7 +408,6 @@
         "core/java/com/android/internal/util/AsyncChannel.java",
         "core/java/com/android/internal/util/AsyncService.java",
         "core/java/com/android/internal/util/Protocol.java",
-        "core/java/com/android/internal/util/Preconditions.java",
         "telephony/java/android/telephony/Annotation.java",
         ":net-utils-framework-wifi-common-srcs",
     ],
diff --git a/ApiDocs.bp b/ApiDocs.bp
index 65a6547..1767678 100644
--- a/ApiDocs.bp
+++ b/ApiDocs.bp
@@ -77,22 +77,16 @@
         // Module sources
         ":art.module.public.api{.public.stubs.source}",
         ":conscrypt.module.public.api{.public.stubs.source}",
-        ":framework-connectivity-sources",
-        ":framework-mediaprovider-sources",
-        ":framework-permission-sources",
-        ":framework-sdkextensions-sources",
-        ":framework-statsd-sources",
-        ":framework-tethering-srcs",
-        ":framework-wifi-updatable-sources",
         ":i18n.module.public.api{.public.stubs.source}",
-        ":ike-srcs",
-        ":updatable-media-srcs",
 
         // No longer part of the stubs, but are included in the docs.
         ":android-test-base-sources",
         ":android-test-mock-sources",
         ":android-test-runner-sources",
     ],
+    arg_files: [
+        "core/res/AndroidManifest.xml",
+    ],
     libs: framework_docs_only_libs,
     create_doc_stubs: true,
     annotations_enabled: true,
@@ -106,6 +100,7 @@
     merge_annotations_dirs: [
         "metalava-manual",
     ],
+    write_sdk_values: true,
 }
 
 droidstubs {
@@ -114,6 +109,24 @@
     args: metalava_framework_docs_args,
 }
 
+// Defaults module for doc-stubs targets that use module source code as input.
+// This is the default/normal.
+stubs_defaults {
+    name: "framework-doc-stubs-sources-default",
+    defaults: ["framework-doc-stubs-default"],
+    srcs: [
+        ":framework-connectivity-sources",
+        ":framework-mediaprovider-sources",
+        ":framework-permission-sources",
+        ":framework-sdkextensions-sources",
+        ":framework-statsd-sources",
+        ":framework-tethering-srcs",
+        ":framework-wifi-updatable-sources",
+        ":ike-srcs",
+        ":updatable-media-srcs",
+    ],
+}
+
 droidstubs {
     name: "android-non-updatable-doc-stubs-system",
     defaults: ["android-non-updatable-doc-stubs-defaults"],
@@ -123,26 +136,46 @@
 
 droidstubs {
     name: "framework-doc-stubs",
-    defaults: ["framework-doc-stubs-default"],
-    arg_files: [
-        "core/res/AndroidManifest.xml",
-    ],
+    defaults: ["framework-doc-stubs-sources-default"],
     args: metalava_framework_docs_args,
-    write_sdk_values: true,
 }
 
 droidstubs {
     name: "framework-doc-system-stubs",
-    defaults: ["framework-doc-stubs-default"],
-    arg_files: [
-        "core/res/AndroidManifest.xml",
-    ],
+    defaults: ["framework-doc-stubs-sources-default"],
     args: metalava_framework_docs_args +
         " --show-annotation android.annotation.SystemApi\\(client=android.annotation.SystemApi.Client.PRIVILEGED_APPS\\) ",
-    write_sdk_values: true,
     api_levels_sdk_type: "system",
 }
 
+// Experimental target building doc stubs with module stub source code as input.
+// This is intended to eventually replace framework-doc-stubs, once all diffs
+// have been eliminated.
+droidstubs {
+    name: "framework-doc-stubs-module-stubs",
+    defaults: ["framework-doc-stubs-default"],
+    args: metalava_framework_docs_args,
+    srcs: [
+        ":android.net.ipsec.ike{.public.stubs.source}",
+        ":framework-connectivity{.public.stubs.source}",
+        ":framework-media{.public.stubs.source}",
+        ":framework-mediaprovider{.public.stubs.source}",
+        ":framework-permission{.public.stubs.source}",
+        ":framework-sdkextensions{.public.stubs.source}",
+        ":framework-statsd{.public.stubs.source}",
+        ":framework-tethering{.public.stubs.source}",
+        ":framework-wifi{.public.stubs.source}",
+    ],
+    aidl: {
+        local_include_dirs: [
+            "apex/media/aidl/stable",
+        ],
+        include_dirs: [
+            "packages/modules/Connectivity/framework/aidl-export",
+        ],
+    },
+}
+
 /////////////////////////////////////////////////////////////////////
 // API docs are created from the generated stub source files
 // using droiddoc
diff --git a/apct-tests/perftests/multiuser/Android.bp b/apct-tests/perftests/multiuser/Android.bp
index 91e2074..c967e51 100644
--- a/apct-tests/perftests/multiuser/Android.bp
+++ b/apct-tests/perftests/multiuser/Android.bp
@@ -31,5 +31,6 @@
     ],
     platform_apis: true,
     test_suites: ["device-tests"],
+    data: ["trace_configs/*"],
     certificate: "platform",
 }
diff --git a/apct-tests/perftests/multiuser/AndroidTest.xml b/apct-tests/perftests/multiuser/AndroidTest.xml
index 9117561..bec3cc9 100644
--- a/apct-tests/perftests/multiuser/AndroidTest.xml
+++ b/apct-tests/perftests/multiuser/AndroidTest.xml
@@ -16,14 +16,51 @@
 <configuration description="Runs MultiUserPerfTests metric instrumentation.">
     <option name="test-suite-tag" value="apct" />
     <option name="test-suite-tag" value="apct-metric-instrumentation" />
+
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
         <option name="cleanup-apks" value="true" />
         <option name="test-file-name" value="MultiUserPerfTests.apk" />
         <option name="test-file-name" value="MultiUserPerfDummyApp.apk" />
     </target_preparer>
 
+    <!-- Needed for pushing the trace config file -->
+    <target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer"/>
+    <target_preparer class="com.android.tradefed.targetprep.PushFilePreparer">
+        <option name="push-file" key="trace_config_multi_user.textproto" value="/data/misc/perfetto-traces/trace_config.textproto" />
+        <!--Install the content provider automatically when we push some file in sdcard folder.-->
+        <!--Needed to avoid the installation during the test suite.-->
+        <option name="push-file" key="trace_config_multi_user.textproto" value="/sdcard/sample.textproto" />
+    </target_preparer>
+
+    <!-- Needed for pulling the collected trace config on to the host -->
+    <metrics_collector class="com.android.tradefed.device.metric.FilePullerLogCollector">
+        <option name="pull-pattern-keys" value="perfetto_file_path" />
+    </metrics_collector>
+
+    <!-- Needed for storing the perfetto trace files in the sdcard/test_results-->
+    <option name="isolated-storage" value="false" />
+
     <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
         <option name="package" value="com.android.perftests.multiuser" />
         <option name="hidden-api-checks" value="false"/>
+
+        <!-- Listener related args for collecting the traces and waiting for the device to stabilize. -->
+        <option name="device-listeners" value="android.device.collectors.ProcLoadListener,android.device.collectors.PerfettoListener" />
+        <!-- Guarantee that user defined RunListeners will be running before any of the default listeners defined in this runner. -->
+        <option name="instrumentation-arg" key="newRunListenerMode" value="true" />
+
+        <!-- ProcLoadListener related arguments -->
+        <!-- Wait for device last minute threshold to reach 3 with 2 minute timeout before starting the test run -->
+        <option name="instrumentation-arg" key="procload-collector:per_run" value="true" />
+        <option name="instrumentation-arg" key="proc-loadavg-threshold" value="3" />
+        <option name="instrumentation-arg" key="proc-loadavg-timeout" value="120000" />
+        <option name="instrumentation-arg" key="proc-loadavg-interval" value="10000" />
+
+        <!-- PerfettoListener related arguments -->
+        <option name="instrumentation-arg" key="perfetto_config_text_proto" value="true" />
+        <option name="instrumentation-arg" key="perfetto_config_file" value="trace_config.textproto" />
+
+        <option name="instrumentation-arg" key="newRunListenerMode" value="true" />
+
     </test>
 </configuration>
diff --git a/apct-tests/perftests/multiuser/trace_configs/trace_config_multi_user.textproto b/apct-tests/perftests/multiuser/trace_configs/trace_config_multi_user.textproto
new file mode 100644
index 0000000..93b06e8
--- /dev/null
+++ b/apct-tests/perftests/multiuser/trace_configs/trace_config_multi_user.textproto
@@ -0,0 +1,154 @@
+# Copyright (C) 2021 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# proto-message: TraceConfig
+
+# Enable periodic flushing of the trace buffer into the output file.
+write_into_file: true
+
+# Writes the userspace buffer into the file every 1s.
+file_write_period_ms: 1000
+
+# See b/126487238 - we need to guarantee ordering of events.
+flush_period_ms: 10000
+
+# The trace buffers needs to be big enough to hold |file_write_period_ms| of
+# trace data. The trace buffer sizing depends on the number of trace categories
+# enabled and the device activity.
+
+# RSS events
+buffers {
+  size_kb: 32768
+  fill_policy: RING_BUFFER
+}
+
+# procfs polling
+buffers {
+  size_kb: 8192
+  fill_policy: RING_BUFFER
+}
+
+data_sources {
+  config {
+    name: "linux.ftrace"
+    target_buffer: 0
+    ftrace_config {
+      # These parameters affect only the kernel trace buffer size and how
+      # frequently it gets moved into the userspace buffer defined above.
+      buffer_size_kb: 16384
+      drain_period_ms: 250
+
+      # Store certain high-volume "sched" ftrace events in a denser format
+      # (falling back to the default format if not supported by the tracer).
+      compact_sched {
+        enabled: true
+      }
+
+      # Enables symbol name resolution against /proc/kallsyms
+      symbolize_ksyms: true
+
+      # We need to do process tracking to ensure kernel ftrace events targeted at short-lived
+      # threads are associated correctly
+      ftrace_events: "task/task_newtask"
+      ftrace_events: "task/task_rename"
+      ftrace_events: "sched/sched_process_exit"
+      ftrace_events: "sched/sched_process_free"
+
+      # Memory events
+      ftrace_events: "rss_stat"
+      ftrace_events: "ion_heap_shrink"
+      ftrace_events: "ion_heap_grow"
+      ftrace_events: "ion/ion_stat"
+      ftrace_events: "dmabuf_heap/dma_heap_stat"
+      ftrace_events: "oom_score_adj_update"
+      ftrace_events: "gpu_mem/gpu_mem_total"
+
+      # Old (kernel) LMK
+      ftrace_events: "lowmemorykiller/lowmemory_kill"
+
+      atrace_apps: "*"
+
+      atrace_categories: "am"
+      atrace_categories: "binder_driver"
+      atrace_categories: "bionic"
+      atrace_categories: "dalvik"
+      atrace_categories: "input"
+      atrace_categories: "pm"
+      atrace_categories: "res"
+      atrace_categories: "rro"
+      atrace_categories: "ss"
+      atrace_categories: "view"
+      atrace_categories: "wm"
+
+      atrace_categories: "freq"
+      atrace_categories: "sched"
+      atrace_categories: "sync"
+      atrace_categories: "workq"
+
+    }
+  }
+}
+
+data_sources: {
+  config {
+    name: "android.gpu.memory"
+    target_buffer: 0
+  }
+}
+
+data_sources {
+  config {
+    name: "linux.process_stats"
+    target_buffer: 1
+    process_stats_config {
+      proc_stats_poll_ms: 10000
+    }
+  }
+}
+
+data_sources {
+  config {
+    name: "linux.sys_stats"
+    target_buffer: 1
+    sys_stats_config {
+      meminfo_period_ms: 1000
+      meminfo_counters: MEMINFO_MEM_TOTAL
+      meminfo_counters: MEMINFO_MEM_FREE
+      meminfo_counters: MEMINFO_MEM_AVAILABLE
+      meminfo_counters: MEMINFO_BUFFERS
+      meminfo_counters: MEMINFO_CACHED
+      meminfo_counters: MEMINFO_SWAP_CACHED
+      meminfo_counters: MEMINFO_ACTIVE
+      meminfo_counters: MEMINFO_INACTIVE
+      meminfo_counters: MEMINFO_ACTIVE_ANON
+      meminfo_counters: MEMINFO_INACTIVE_ANON
+      meminfo_counters: MEMINFO_ACTIVE_FILE
+      meminfo_counters: MEMINFO_INACTIVE_FILE
+      meminfo_counters: MEMINFO_UNEVICTABLE
+      meminfo_counters: MEMINFO_SWAP_TOTAL
+      meminfo_counters: MEMINFO_SWAP_FREE
+      meminfo_counters: MEMINFO_DIRTY
+      meminfo_counters: MEMINFO_WRITEBACK
+      meminfo_counters: MEMINFO_ANON_PAGES
+      meminfo_counters: MEMINFO_MAPPED
+      meminfo_counters: MEMINFO_SHMEM
+    }
+  }
+}
+
+data_sources: {
+  config: {
+    name: "android.surfaceflinger.frametimeline"
+  }
+}
diff --git a/api/Android.bp b/api/Android.bp
index fbfbc7c..0acd759 100644
--- a/api/Android.bp
+++ b/api/Android.bp
@@ -24,9 +24,8 @@
     default_applicable_licenses: ["frameworks_base_license"],
 }
 
-python_binary_host {
-    name: "api_versions_trimmer",
-    srcs: ["api_versions_trimmer.py"],
+python_defaults {
+    name: "python3_version_defaults",
     version: {
         py2: {
             enabled: false,
@@ -38,6 +37,12 @@
     },
 }
 
+python_binary_host {
+    name: "api_versions_trimmer",
+    srcs: ["api_versions_trimmer.py"],
+    defaults: ["python3_version_defaults"],
+}
+
 python_test_host {
     name: "api_versions_trimmer_unittests",
     main: "api_versions_trimmer_unittests.py",
@@ -45,24 +50,35 @@
         "api_versions_trimmer_unittests.py",
         "api_versions_trimmer.py",
     ],
+    defaults: ["python3_version_defaults"],
     test_options: {
         unit_test: true,
     },
-    version: {
-        py2: {
-            enabled: false,
-        },
-        py3: {
-            enabled: true,
-            embedded_launcher: false,
-        },
+}
+
+python_binary_host {
+    name: "merge_annotation_zips",
+    srcs: ["merge_annotation_zips.py"],
+    defaults: ["python3_version_defaults"],
+}
+
+python_test_host {
+    name: "merge_annotation_zips_test",
+    main: "merge_annotation_zips_test.py",
+    srcs: [
+        "merge_annotation_zips.py",
+        "merge_annotation_zips_test.py",
+    ],
+    defaults: ["python3_version_defaults"],
+    test_options: {
+        unit_test: true,
     },
 }
 
 metalava_cmd = "$(location metalava)"
 // Silence reflection warnings. See b/168689341
 metalava_cmd += " -J--add-opens=java.base/java.util=ALL-UNNAMED "
-metalava_cmd += " --no-banner --format=v2 "
+metalava_cmd += " --quiet --no-banner --format=v2 "
 
 genrule {
     name: "current-api-xml",
@@ -118,13 +134,13 @@
         ":android-incompatibilities.api.public.latest",
         ":frameworks-base-api-current.txt",
     ],
-    out: ["stdout.txt"],
+    out: ["updated-baseline.txt"],
     tools: ["metalava"],
     cmd: metalava_cmd +
         "--check-compatibility:api:released $(location :android.api.public.latest) " +
         "--baseline:compatibility:released $(location :android-incompatibilities.api.public.latest) " +
-        "$(location :frameworks-base-api-current.txt) " +
-        "> $(genDir)/stdout.txt",
+        "--update-baseline:compatibility:released $(genDir)/updated-baseline.txt " +
+        "$(location :frameworks-base-api-current.txt)",
 }
 
 genrule {
@@ -231,14 +247,14 @@
         ":frameworks-base-api-current.txt",
         ":frameworks-base-api-system-current.txt",
     ],
-    out: ["stdout.txt"],
+    out: ["updated-baseline.txt"],
     tools: ["metalava"],
     cmd: metalava_cmd +
         "--check-compatibility:api:released $(location :android.api.system.latest) " +
         "--check-compatibility:base $(location :frameworks-base-api-current.txt) " +
         "--baseline:compatibility:released $(location :android-incompatibilities.api.system.latest) " +
-        "$(location :frameworks-base-api-system-current.txt) " +
-        "> $(genDir)/stdout.txt",
+        "--update-baseline:compatibility:released $(genDir)/updated-baseline.txt " +
+        "$(location :frameworks-base-api-system-current.txt)",
 }
 
 genrule {
@@ -320,7 +336,7 @@
         ":frameworks-base-api-current.txt",
         ":frameworks-base-api-module-lib-current.txt",
     ],
-    out: ["stdout.txt"],
+    out: ["updated-baseline.txt"],
     tools: ["metalava"],
     cmd: metalava_cmd +
         "--check-compatibility:api:released $(location :android.api.module-lib.latest) " +
@@ -329,8 +345,8 @@
         // MODULE_LIBS -> public.
         "--check-compatibility:base $(location :frameworks-base-api-current.txt) " +
         "--baseline:compatibility:released $(location :android-incompatibilities.api.module-lib.latest) " +
-        "$(location :frameworks-base-api-module-lib-current.txt) " +
-        "> $(genDir)/stdout.txt",
+        "--update-baseline:compatibility:released $(genDir)/updated-baseline.txt " +
+        "$(location :frameworks-base-api-module-lib-current.txt)",
 }
 
 genrule {
diff --git a/api/api_versions_trimmer_unittests.py b/api/api_versions_trimmer_unittests.py
index 4eb929e..d2e5b7d 100644
--- a/api/api_versions_trimmer_unittests.py
+++ b/api/api_versions_trimmer_unittests.py
@@ -304,4 +304,4 @@
 
 
 if __name__ == "__main__":
-  unittest.main()
+  unittest.main(verbosity=2)
diff --git a/api/merge_annotation_zips.py b/api/merge_annotation_zips.py
new file mode 100755
index 0000000..9c67d7b
--- /dev/null
+++ b/api/merge_annotation_zips.py
@@ -0,0 +1,71 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2021 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+"""Script to merge annotation XML files (created by e.g. metalava)."""
+
+from pathlib import Path
+import sys
+import xml.etree.ElementTree as ET
+import zipfile
+
+
+def validate_xml_assumptions(root):
+  """Verify the format of the annotations XML matches expectations"""
+  prevName = ""
+  assert root.tag == 'root'
+  for child in root:
+    assert child.tag == 'item', 'unexpected tag: %s' % child.tag
+    assert list(child.attrib.keys()) == ['name'], 'unexpected attribs: %s' % child.attrib.keys()
+    assert prevName < child.get('name'), 'items unexpectedly not strictly sorted (possibly duplicate entries)'
+    prevName = child.get('name')
+
+
+def merge_xml(a, b):
+  """Merge two annotation xml files"""
+  for xml in [a, b]:
+    validate_xml_assumptions(xml)
+  a.extend(b[:])
+  a[:] = sorted(a[:], key=lambda x: x.get('name'))
+  validate_xml_assumptions(a)
+
+
+def merge_zip_file(out_dir, zip_file):
+  """Merge the content of the zip_file into out_dir"""
+  for filename in zip_file.namelist():
+    path = Path(out_dir, filename)
+    if path.exists():
+      existing_xml = ET.parse(path)
+      with zip_file.open(filename) as other_file:
+        other_xml = ET.parse(other_file)
+      merge_xml(existing_xml.getroot(), other_xml.getroot())
+      existing_xml.write(path, encoding='UTF-8', xml_declaration=True)
+    else:
+      zip_file.extract(filename, out_dir)
+
+
+def main():
+  out_dir = Path(sys.argv[1])
+  zip_filenames = sys.argv[2:]
+
+  assert not out_dir.exists()
+  out_dir.mkdir()
+  for zip_filename in zip_filenames:
+    with zipfile.ZipFile(zip_filename) as zip_file:
+      merge_zip_file(out_dir, zip_file)
+
+
+if __name__ == "__main__":
+  main()
diff --git a/api/merge_annotation_zips_test.py b/api/merge_annotation_zips_test.py
new file mode 100644
index 0000000..26795c47
--- /dev/null
+++ b/api/merge_annotation_zips_test.py
@@ -0,0 +1,124 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2021 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import io
+from pathlib import Path
+import tempfile
+import unittest
+import zipfile
+
+import merge_annotation_zips
+
+
+zip_a = {
+  'android/provider/annotations.xml':
+  """<?xml version="1.0" encoding="UTF-8"?>
+<root>
+  <item name="android.provider.BlockedNumberContract boolean isBlocked(android.content.Context, java.lang.String)">
+    <annotation name="androidx.annotation.WorkerThread"/>
+  </item>
+  <item name="android.provider.SimPhonebookContract.SimRecords android.net.Uri getItemUri(int, int, int) 2">
+    <annotation name="androidx.annotation.IntRange">
+      <val name="from" val="1" />
+    </annotation>
+  </item>
+</root>""",
+  'android/os/annotations.xml':
+  """<?xml version="1.0" encoding="UTF-8"?>
+<root>
+  <item name="android.app.ActionBar void setCustomView(int) 0">
+    <annotation name="androidx.annotation.LayoutRes"/>
+  </item>
+</root>
+"""
+}
+
+zip_b = {
+  'android/provider/annotations.xml':
+  """<?xml version="1.0" encoding="UTF-8"?>
+<root>
+  <item name="android.provider.MediaStore QUERY_ARG_MATCH_FAVORITE">
+    <annotation name="androidx.annotation.IntDef">
+      <val name="value" val="{android.provider.MediaStore.MATCH_DEFAULT, android.provider.MediaStore.MATCH_INCLUDE, android.provider.MediaStore.MATCH_EXCLUDE, android.provider.MediaStore.MATCH_ONLY}" />
+      <val name="flag" val="true" />
+    </annotation>
+  </item>
+  <item name="android.provider.MediaStore QUERY_ARG_MATCH_PENDING">
+    <annotation name="androidx.annotation.IntDef">
+      <val name="value" val="{android.provider.MediaStore.MATCH_DEFAULT, android.provider.MediaStore.MATCH_INCLUDE, android.provider.MediaStore.MATCH_EXCLUDE, android.provider.MediaStore.MATCH_ONLY}" />
+      <val name="flag" val="true" />
+    </annotation>
+  </item>
+</root>"""
+}
+
+zip_c = {
+  'android/app/annotations.xml':
+  """<?xml version="1.0" encoding="UTF-8"?>
+<root>
+  <item name="android.app.ActionBar void setCustomView(int) 0">
+    <annotation name="androidx.annotation.LayoutRes"/>
+  </item>
+</root>"""
+}
+
+merged_provider = """<?xml version='1.0' encoding='UTF-8'?>
+<root>
+  <item name="android.provider.BlockedNumberContract boolean isBlocked(android.content.Context, java.lang.String)">
+    <annotation name="androidx.annotation.WorkerThread" />
+  </item>
+  <item name="android.provider.MediaStore QUERY_ARG_MATCH_FAVORITE">
+    <annotation name="androidx.annotation.IntDef">
+      <val name="value" val="{android.provider.MediaStore.MATCH_DEFAULT, android.provider.MediaStore.MATCH_INCLUDE, android.provider.MediaStore.MATCH_EXCLUDE, android.provider.MediaStore.MATCH_ONLY}" />
+      <val name="flag" val="true" />
+    </annotation>
+  </item>
+  <item name="android.provider.MediaStore QUERY_ARG_MATCH_PENDING">
+    <annotation name="androidx.annotation.IntDef">
+      <val name="value" val="{android.provider.MediaStore.MATCH_DEFAULT, android.provider.MediaStore.MATCH_INCLUDE, android.provider.MediaStore.MATCH_EXCLUDE, android.provider.MediaStore.MATCH_ONLY}" />
+      <val name="flag" val="true" />
+    </annotation>
+  </item>
+<item name="android.provider.SimPhonebookContract.SimRecords android.net.Uri getItemUri(int, int, int) 2">
+    <annotation name="androidx.annotation.IntRange">
+      <val name="from" val="1" />
+    </annotation>
+  </item>
+</root>"""
+
+
+
+class MergeAnnotationZipsTest(unittest.TestCase):
+
+  def test_merge_zips(self):
+    with tempfile.TemporaryDirectory() as out_dir:
+      for zip_content in [zip_a, zip_b, zip_c]:
+        f = io.BytesIO()
+        with zipfile.ZipFile(f, "w") as zip_file:
+          for filename, content in zip_content.items():
+            zip_file.writestr(filename, content)
+          merge_annotation_zips.merge_zip_file(out_dir, zip_file)
+
+      # Unchanged
+      self.assertEqual(zip_a['android/os/annotations.xml'], Path(out_dir, 'android/os/annotations.xml').read_text())
+      self.assertEqual(zip_c['android/app/annotations.xml'], Path(out_dir, 'android/app/annotations.xml').read_text())
+
+      # Merged
+      self.assertEqual(merged_provider, Path(out_dir, 'android/provider/annotations.xml').read_text())
+
+
+if __name__ == "__main__":
+  unittest.main(verbosity=2)
diff --git a/core/api/current.txt b/core/api/current.txt
index e4e7d4e..93ef8f8 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -29998,12 +29998,15 @@
     method public int readInt();
     method public void readIntArray(@NonNull int[]);
     method public void readList(@NonNull java.util.List, @Nullable ClassLoader);
+    method public <T> void readList(@NonNull java.util.List<? super T>, @Nullable ClassLoader, @NonNull Class<T>);
     method public long readLong();
     method public void readLongArray(@NonNull long[]);
     method public void readMap(@NonNull java.util.Map, @Nullable ClassLoader);
     method @Nullable public <T extends android.os.Parcelable> T readParcelable(@Nullable ClassLoader);
+    method @Nullable public <T extends android.os.Parcelable> T readParcelable(@Nullable ClassLoader, @NonNull Class<T>);
     method @Nullable public android.os.Parcelable[] readParcelableArray(@Nullable ClassLoader);
     method @Nullable public android.os.Parcelable.Creator<?> readParcelableCreator(@Nullable ClassLoader);
+    method @Nullable public <T> android.os.Parcelable.Creator<T> readParcelableCreator(@Nullable ClassLoader, @NonNull Class<T>);
     method @NonNull public <T extends android.os.Parcelable> java.util.List<T> readParcelableList(@NonNull java.util.List<T>, @Nullable ClassLoader);
     method @Nullable public android.os.PersistableBundle readPersistableBundle();
     method @Nullable public android.os.PersistableBundle readPersistableBundle(@Nullable ClassLoader);
@@ -30142,7 +30145,7 @@
 
   public interface Parcelable {
     method public int describeContents();
-    method public void writeToParcel(android.os.Parcel, int);
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
     field public static final int CONTENTS_FILE_DESCRIPTOR = 1; // 0x1
     field public static final int PARCELABLE_WRITE_RETURN_VALUE = 1; // 0x1
   }
diff --git a/core/java/Android.bp b/core/java/Android.bp
index a52e3b0..3b2d883 100644
--- a/core/java/Android.bp
+++ b/core/java/Android.bp
@@ -381,10 +381,7 @@
         "android/util/Rational.java",
         "com/android/internal/util/FastXmlSerializer.java",
         "com/android/internal/util/HexDump.java",
-        "com/android/internal/util/IState.java",
         "com/android/internal/util/MessageUtils.java",
-        "com/android/internal/util/State.java",
-        "com/android/internal/util/StateMachine.java",
         "com/android/internal/util/WakeupMessage.java",
     ],
     visibility: [
diff --git a/core/java/android/net/nsd/NsdManager.java b/core/java/android/net/nsd/NsdManager.java
index 5a25cfc..ae8d010 100644
--- a/core/java/android/net/nsd/NsdManager.java
+++ b/core/java/android/net/nsd/NsdManager.java
@@ -23,6 +23,9 @@
 import android.annotation.SdkConstant;
 import android.annotation.SdkConstant.SdkConstantType;
 import android.annotation.SystemService;
+import android.app.compat.CompatChanges;
+import android.compat.annotation.ChangeId;
+import android.compat.annotation.EnabledSince;
 import android.content.Context;
 import android.os.Handler;
 import android.os.HandlerThread;
@@ -126,6 +129,24 @@
     private static final boolean DBG = false;
 
     /**
+     * When enabled, apps targeting < Android 12 are considered legacy for
+     * the NSD native daemon.
+     * The platform will only keep the daemon running as long as there are
+     * any legacy apps connected.
+     *
+     * After Android 12, directly communicate with native daemon might not
+     * work since the native damon won't always stay alive.
+     * Use the NSD APIs from NsdManager as the replacement is recommended.
+     * An another alternative could be bundling your own mdns solutions instead of
+     * depending on the system mdns native daemon.
+     *
+     * @hide
+     */
+    @ChangeId
+    @EnabledSince(targetSdkVersion = android.os.Build.VERSION_CODES.S)
+    public static final long RUN_NATIVE_NSD_ONLY_IF_LEGACY_APPS = 191844585L;
+
+    /**
      * Broadcast intent action to indicate whether network service discovery is
      * enabled or disabled. An extra {@link #EXTRA_NSD_STATE} provides the state
      * information as int.
@@ -203,6 +224,9 @@
     public static final int DAEMON_CLEANUP                          = BASE + 21;
 
     /** @hide */
+    public static final int DAEMON_STARTUP                          = BASE + 22;
+
+    /** @hide */
     public static final int ENABLE                                  = BASE + 24;
     /** @hide */
     public static final int DISABLE                                 = BASE + 25;
@@ -232,6 +256,8 @@
         EVENT_NAMES.put(RESOLVE_SERVICE, "RESOLVE_SERVICE");
         EVENT_NAMES.put(RESOLVE_SERVICE_FAILED, "RESOLVE_SERVICE_FAILED");
         EVENT_NAMES.put(RESOLVE_SERVICE_SUCCEEDED, "RESOLVE_SERVICE_SUCCEEDED");
+        EVENT_NAMES.put(DAEMON_CLEANUP, "DAEMON_CLEANUP");
+        EVENT_NAMES.put(DAEMON_STARTUP, "DAEMON_STARTUP");
         EVENT_NAMES.put(ENABLE, "ENABLE");
         EVENT_NAMES.put(DISABLE, "DISABLE");
         EVENT_NAMES.put(NATIVE_DAEMON_EVENT, "NATIVE_DAEMON_EVENT");
@@ -494,6 +520,12 @@
         } catch (InterruptedException e) {
             fatal("Interrupted wait at init");
         }
+        if (CompatChanges.isChangeEnabled(RUN_NATIVE_NSD_ONLY_IF_LEGACY_APPS)) {
+            return;
+        }
+        // Only proactively start the daemon if the target SDK < S, otherwise the internal service
+        // would automatically start/stop the native daemon as needed.
+        mAsyncChannel.sendMessage(DAEMON_STARTUP);
     }
 
     private static void fatal(String msg) {
diff --git a/core/java/android/os/BaseBundle.java b/core/java/android/os/BaseBundle.java
index 6da02f5..7ce8d72 100644
--- a/core/java/android/os/BaseBundle.java
+++ b/core/java/android/os/BaseBundle.java
@@ -260,6 +260,9 @@
     /**
      * Returns the value for key {@code key}.
      *
+     * This call should always be made after {@link #unparcel()} or inside a lock after making sure
+     * {@code mMap} is not null.
+     *
      * @hide
      */
     final Object getValue(String key) {
@@ -270,15 +273,15 @@
     /**
      * Returns the value for a certain position in the array map.
      *
+     * This call should always be made after {@link #unparcel()} or inside a lock after making sure
+     * {@code mMap} is not null.
+     *
      * @hide
      */
     final Object getValueAt(int i) {
         Object object = mMap.valueAt(i);
         if (object instanceof Supplier<?>) {
-            Supplier<?> supplier = (Supplier<?>) object;
-            synchronized (this) {
-                object = supplier.get();
-            }
+            object = ((Supplier<?>) object).get();
             mMap.setValueAt(i, object);
         }
         return object;
@@ -428,7 +431,7 @@
      *
      * @hide
      */
-    public static boolean kindofEquals(BaseBundle a, BaseBundle b) {
+    public static boolean kindofEquals(@Nullable BaseBundle a, @Nullable BaseBundle b) {
         return (a == b) || (a != null && a.kindofEquals(b));
     }
 
@@ -1045,7 +1048,7 @@
      */
     char getChar(String key, char defaultValue) {
         unparcel();
-        Object o = getValue(key);
+        Object o = mMap.get(key);
         if (o == null) {
             return defaultValue;
         }
@@ -1448,7 +1451,7 @@
     @Nullable
     short[] getShortArray(@Nullable String key) {
         unparcel();
-        Object o = getValue(key);
+        Object o = mMap.get(key);
         if (o == null) {
             return null;
         }
@@ -1471,7 +1474,7 @@
     @Nullable
     char[] getCharArray(@Nullable String key) {
         unparcel();
-        Object o = getValue(key);
+        Object o = mMap.get(key);
         if (o == null) {
             return null;
         }
@@ -1540,7 +1543,7 @@
     @Nullable
     float[] getFloatArray(@Nullable String key) {
         unparcel();
-        Object o = getValue(key);
+        Object o = mMap.get(key);
         if (o == null) {
             return null;
         }
diff --git a/core/java/android/os/OWNERS b/core/java/android/os/OWNERS
index a870c04..c575c80e 100644
--- a/core/java/android/os/OWNERS
+++ b/core/java/android/os/OWNERS
@@ -69,3 +69,6 @@
 
 # UpdateEngine
 per-file *UpdateEngine* = file:/platform/system/update_engine:/OWNERS
+
+# VINTF
+per-file Vintf* = file:/platform/system/libvintf:/OWNERS
diff --git a/core/java/android/os/Parcel.java b/core/java/android/os/Parcel.java
index a2716d2..243dfb3 100644
--- a/core/java/android/os/Parcel.java
+++ b/core/java/android/os/Parcel.java
@@ -16,6 +16,8 @@
 
 package android.os;
 
+import static java.util.Objects.requireNonNull;
+
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.TestApi;
@@ -261,6 +263,10 @@
     private static final int VAL_SIZE = 26;
     private static final int VAL_SIZEF = 27;
     private static final int VAL_DOUBLEARRAY = 28;
+    private static final int VAL_CHAR = 29;
+    private static final int VAL_SHORTARRAY = 30;
+    private static final int VAL_CHARARRAY = 31;
+    private static final int VAL_FLOATARRAY = 32;
 
     // The initial int32 in a Binder call's reply Parcel header:
     // Keep these in sync with libbinder's binder/Status.h.
@@ -446,6 +452,23 @@
     }
 
     /**
+     * Retrieve a new Parcel object from the pool for use with a specific binder.
+     *
+     * Associate this parcel with a binder object. This marks the parcel as being prepared for a
+     * transaction on this specific binder object. Based on this, the format of the wire binder
+     * protocol may change. For future compatibility, it is recommended to use this for all
+     * Parcels.
+     *
+     * @hide
+     */
+    @NonNull
+    public static Parcel obtain(@NonNull IBinder binder) {
+        Parcel parcel = Parcel.obtain();
+        parcel.markForBinder(binder);
+        return parcel;
+    }
+
+    /**
      * Put a Parcel object back into the pool.  You must not touch
      * the object after this call.
      */
@@ -517,16 +540,9 @@
     }
 
     /**
-     * Associate this parcel with a binder object. This marks the parcel as being prepared for a
-     * transaction on this specific binder object. Based on this, the format of the wire binder
-     * protocol may change. This should be called before any data is written to the parcel. If this
-     * is called multiple times, this will only be marked for the last binder. For future
-     * compatibility, it is recommended to call this on all parcels which are being sent over
-     * binder.
-     *
      * @hide
      */
-    public void markForBinder(@NonNull IBinder binder) {
+    private void markForBinder(@NonNull IBinder binder) {
         nativeMarkForBinder(mNativePtr, binder);
     }
 
@@ -1307,6 +1323,46 @@
         }
     }
 
+    /** @hide */
+    public void writeShortArray(@Nullable short[] val) {
+        if (val != null) {
+            int n = val.length;
+            writeInt(n);
+            for (int i = 0; i < n; i++) {
+                writeInt(val[i]);
+            }
+        } else {
+            writeInt(-1);
+        }
+    }
+
+    /** @hide */
+    @Nullable
+    public short[] createShortArray() {
+        int n = readInt();
+        if (n >= 0 && n <= (dataAvail() >> 2)) {
+            short[] val = new short[n];
+            for (int i = 0; i < n; i++) {
+                val[i] = (short) readInt();
+            }
+            return val;
+        } else {
+            return null;
+        }
+    }
+
+    /** @hide */
+    public void readShortArray(@NonNull short[] val) {
+        int n = readInt();
+        if (n == val.length) {
+            for (int i = 0; i < n; i++) {
+                val[i] = (short) readInt();
+            }
+        } else {
+            throw new RuntimeException("bad array lengths");
+        }
+    }
+
     public final void writeCharArray(@Nullable char[] val) {
         if (val != null) {
             int N = val.length;
@@ -1972,6 +2028,14 @@
             return VAL_SIZE;
         } else if (v instanceof double[]) {
             return VAL_DOUBLEARRAY;
+        } else if (v instanceof Character) {
+            return VAL_CHAR;
+        } else if (v instanceof short[]) {
+            return VAL_SHORTARRAY;
+        } else if (v instanceof char[]) {
+            return VAL_CHARARRAY;
+        } else  if (v instanceof float[]) {
+            return VAL_FLOATARRAY;
         } else {
             Class<?> clazz = v.getClass();
             if (clazz.isArray() && clazz.getComponentType() == Object.class) {
@@ -2074,6 +2138,18 @@
             case VAL_DOUBLEARRAY:
                 writeDoubleArray((double[]) v);
                 break;
+            case VAL_CHAR:
+                writeInt((Character) v);
+                break;
+            case VAL_SHORTARRAY:
+                writeShortArray((short[]) v);
+                break;
+            case VAL_CHARARRAY:
+                writeCharArray((char[]) v);
+                break;
+            case VAL_FLOATARRAY:
+                writeFloatArray((float[]) v);
+                break;
             case VAL_OBJECTARRAY:
                 writeArray((Object[]) v);
                 break;
@@ -2754,7 +2830,20 @@
      */
     public final void readList(@NonNull List outVal, @Nullable ClassLoader loader) {
         int N = readInt();
-        readListInternal(outVal, N, loader);
+        readListInternal(outVal, N, loader, /* clazz */ null);
+    }
+
+    /**
+     * Same as {@link #readList(List, ClassLoader)} but accepts {@code clazz} parameter as
+     * the type required for each item. If the item to be deserialized is not an instance
+     * of that class or any of its children class
+     * a {@link BadParcelableException} will be thrown.
+     */
+    public <T> void readList(@NonNull List<? super T> outVal,
+            @Nullable ClassLoader loader, @NonNull Class<T> clazz) {
+        Objects.requireNonNull(clazz);
+        int n = readInt();
+        readListInternal(outVal, n, loader, clazz);
     }
 
     /**
@@ -2953,7 +3042,7 @@
             return null;
         }
         ArrayList l = new ArrayList(N);
-        readListInternal(l, N, loader);
+        readListInternal(l, N, loader, /* clazz */ null);
         return l;
     }
 
@@ -3353,20 +3442,29 @@
      */
     @Nullable
     public final Object readValue(@Nullable ClassLoader loader) {
+        return readValue(loader, /* clazz */ null);
+    }
+
+
+    /**
+     * @param clazz The type of the object expected or {@code null} for performing no checks.
+     */
+    @Nullable
+    private <T> T readValue(@Nullable ClassLoader loader, @Nullable Class<T> clazz) {
         int type = readInt();
-        final Object object;
+        final T object;
         if (isLengthPrefixed(type)) {
             int length = readInt();
             int start = dataPosition();
-            object = readValue(type, loader);
+            object = readValue(type, loader, clazz);
             int actual = dataPosition() - start;
             if (actual != length) {
-                Log.w(TAG,
+                Slog.wtfStack(TAG,
                         "Unparcelling of " + object + " of type " + Parcel.valueTypeToString(type)
                                 + "  consumed " + actual + " bytes, but " + length + " expected.");
             }
         } else {
-            object = readValue(type, loader);
+            object = readValue(type, loader, clazz);
         }
         return object;
     }
@@ -3403,7 +3501,7 @@
             setDataPosition(MathUtils.addOrThrow(dataPosition(), length));
             return new LazyValue(this, start, length, type, loader);
         } else {
-            return readValue(type, loader);
+            return readValue(type, loader, /* clazz */ null);
         }
     }
 
@@ -3412,12 +3510,19 @@
         private final int mLength;
         private final int mType;
         @Nullable private final ClassLoader mLoader;
-        @Nullable private Parcel mSource;
         @Nullable private Object mObject;
-        @Nullable private Parcel mValueParcel;
+        @Nullable private volatile Parcel mValueParcel;
+
+        /**
+         * This goes from non-null to null once. Always check the nullability of this object before
+         * performing any operations, either involving itself or mObject since the happens-before
+         * established by this volatile will guarantee visibility of either. We can assume this
+         * parcel won't change anymore.
+         */
+        @Nullable private volatile Parcel mSource;
 
         LazyValue(Parcel source, int position, int length, int type, @Nullable ClassLoader loader) {
-            mSource = source;
+            mSource = requireNonNull(source);
             mPosition = position;
             mLength = length;
             mType = type;
@@ -3426,38 +3531,41 @@
 
         @Override
         public Object get() {
-            if (mObject == null) {
-                int restore = mSource.dataPosition();
-                try {
-                    mSource.setDataPosition(mPosition);
-                    mObject = mSource.readValue(mLoader);
-                } finally {
-                    mSource.setDataPosition(restore);
-                }
-                mSource = null;
-                if (mValueParcel != null) {
-                    mValueParcel.recycle();
-                    mValueParcel = null;
+            Parcel source = mSource;
+            if (source != null) {
+                synchronized (source) {
+                    int restore = source.dataPosition();
+                    try {
+                        source.setDataPosition(mPosition);
+                        mObject = source.readValue(mLoader);
+                    } finally {
+                        source.setDataPosition(restore);
+                    }
+                    mSource = null;
                 }
             }
             return mObject;
         }
 
         public void writeToParcel(Parcel out) {
-            if (mObject == null) {
-                out.appendFrom(mSource, mPosition, mLength + 8);
+            Parcel source = mSource;
+            if (source != null) {
+                out.appendFrom(source, mPosition, mLength + 8);
             } else {
                 out.writeValue(mObject);
             }
         }
 
         public boolean hasFileDescriptors() {
-            return getValueParcel().hasFileDescriptors();
+            Parcel source = mSource;
+            return (source != null)
+                    ? getValueParcel(source).hasFileDescriptors()
+                    : Parcel.hasFileDescriptors(mObject);
         }
 
         @Override
         public String toString() {
-            return mObject == null
+            return (mSource != null)
                     ? "Supplier{" + valueTypeToString(mType) + "@" + mPosition + "+" + mLength + '}'
                     : "Supplier{" + mObject + "}";
         }
@@ -3476,155 +3584,224 @@
                 return false;
             }
             LazyValue value = (LazyValue) other;
-            // Check if they are either both serialized or both deserialized
-            if ((mObject == null) != (value.mObject == null)) {
+            // Check if they are either both serialized or both deserialized.
+            Parcel source = mSource;
+            Parcel otherSource = value.mSource;
+            if ((source == null) != (otherSource == null)) {
                 return false;
             }
-            // If both are deserialized, compare the live objects
-            if (mObject != null) {
-                return mObject.equals(value.mObject);
+            // If both are deserialized, compare the live objects.
+            if (source == null) {
+                // Note that here it's guaranteed that both mObject references contain valid values
+                // (possibly null) since mSource will have provided the memory barrier for those and
+                // once deserialized we never go back to serialized state.
+                return Objects.equals(mObject, value.mObject);
             }
-            // Better safely fail here since this could mean we get different objects
+            // Better safely fail here since this could mean we get different objects.
             if (!Objects.equals(mLoader, value.mLoader)) {
                 return false;
             }
-            // Otherwise compare metadata prior to comparing payload
+            // Otherwise compare metadata prior to comparing payload.
             if (mType != value.mType || mLength != value.mLength) {
                 return false;
             }
-            // Finally we compare the payload
-            return getValueParcel().compareData(value.getValueParcel()) == 0;
+            // Finally we compare the payload.
+            return getValueParcel(source).compareData(value.getValueParcel(otherSource)) == 0;
         }
 
         @Override
         public int hashCode() {
-            return Objects.hash(mObject, mLoader, mType, mLength);
+            // Accessing mSource first to provide memory barrier for mObject
+            return Objects.hash(mSource == null, mObject, mLoader, mType, mLength);
         }
 
         /** This extracts the parcel section responsible for the object and returns it. */
-        private Parcel getValueParcel() {
-            if (mValueParcel == null) {
-                mValueParcel = Parcel.obtain();
+        private Parcel getValueParcel(Parcel source) {
+            Parcel parcel = mValueParcel;
+            if (parcel == null) {
+                parcel = Parcel.obtain();
                 // mLength is the length of object representation, excluding the type and length.
                 // mPosition is the position of the entire value container, right before the type.
                 // So, we add 4 bytes for the type + 4 bytes for the length written.
-                mValueParcel.appendFrom(mSource, mPosition, mLength + 8);
+                parcel.appendFrom(source, mPosition, mLength + 8);
+                mValueParcel = parcel;
             }
-            return mValueParcel;
+            return parcel;
         }
     }
 
     /**
      * Reads a value from the parcel of type {@code type}. Does NOT read the int representing the
      * type first.
+     * @param clazz The type of the object expected or {@code null} for performing no checks.
      */
+    @SuppressWarnings("unchecked")
     @Nullable
-    private Object readValue(int type, @Nullable ClassLoader loader) {
+    private <T> T readValue(int type, @Nullable ClassLoader loader, @Nullable Class<T> clazz) {
+        final Object object;
         switch (type) {
-        case VAL_NULL:
-            return null;
+            case VAL_NULL:
+                object = null;
+                break;
 
-        case VAL_STRING:
-            return readString();
+            case VAL_STRING:
+                object = readString();
+                break;
 
-        case VAL_INTEGER:
-            return readInt();
+            case VAL_INTEGER:
+                object = readInt();
+                break;
 
-        case VAL_MAP:
-            return readHashMap(loader);
+            case VAL_MAP:
+                object = readHashMap(loader);
+                break;
 
-        case VAL_PARCELABLE:
-            return readParcelable(loader);
+            case VAL_PARCELABLE:
+                object = readParcelableInternal(loader, clazz);
+                break;
 
-        case VAL_SHORT:
-            return (short) readInt();
+            case VAL_SHORT:
+                object = (short) readInt();
+                break;
 
-        case VAL_LONG:
-            return readLong();
+            case VAL_LONG:
+                object = readLong();
+                break;
 
-        case VAL_FLOAT:
-            return readFloat();
+            case VAL_FLOAT:
+                object = readFloat();
+                break;
 
-        case VAL_DOUBLE:
-            return readDouble();
+            case VAL_DOUBLE:
+                object = readDouble();
+                break;
 
-        case VAL_BOOLEAN:
-            return readInt() == 1;
+            case VAL_BOOLEAN:
+                object = readInt() == 1;
+                break;
 
-        case VAL_CHARSEQUENCE:
-            return readCharSequence();
+            case VAL_CHARSEQUENCE:
+                object = readCharSequence();
+                break;
 
-        case VAL_LIST:
-            return readArrayList(loader);
+            case VAL_LIST:
+                object = readArrayList(loader);
+                break;
 
-        case VAL_BOOLEANARRAY:
-            return createBooleanArray();
+            case VAL_BOOLEANARRAY:
+                object = createBooleanArray();
+                break;
 
-        case VAL_BYTEARRAY:
-            return createByteArray();
+            case VAL_BYTEARRAY:
+                object = createByteArray();
+                break;
 
-        case VAL_STRINGARRAY:
-            return readStringArray();
+            case VAL_STRINGARRAY:
+                object = readStringArray();
+                break;
 
-        case VAL_CHARSEQUENCEARRAY:
-            return readCharSequenceArray();
+            case VAL_CHARSEQUENCEARRAY:
+                object = readCharSequenceArray();
+                break;
 
-        case VAL_IBINDER:
-            return readStrongBinder();
+            case VAL_IBINDER:
+                object = readStrongBinder();
+                break;
 
-        case VAL_OBJECTARRAY:
-            return readArray(loader);
+            case VAL_OBJECTARRAY:
+                object = readArray(loader);
+                break;
 
-        case VAL_INTARRAY:
-            return createIntArray();
+            case VAL_INTARRAY:
+                object = createIntArray();
+                break;
 
-        case VAL_LONGARRAY:
-            return createLongArray();
+            case VAL_LONGARRAY:
+                object = createLongArray();
+                break;
 
-        case VAL_BYTE:
-            return readByte();
+            case VAL_BYTE:
+                object = readByte();
+                break;
 
-        case VAL_SERIALIZABLE:
-            return readSerializable(loader);
+            case VAL_SERIALIZABLE:
+                object = readSerializable(loader);
+                break;
 
-        case VAL_PARCELABLEARRAY:
-            return readParcelableArray(loader);
+            case VAL_PARCELABLEARRAY:
+                object = readParcelableArray(loader);
+                break;
 
-        case VAL_SPARSEARRAY:
-            return readSparseArray(loader);
+            case VAL_SPARSEARRAY:
+                object = readSparseArray(loader);
+                break;
 
-        case VAL_SPARSEBOOLEANARRAY:
-            return readSparseBooleanArray();
+            case VAL_SPARSEBOOLEANARRAY:
+                object = readSparseBooleanArray();
+                break;
 
-        case VAL_BUNDLE:
-            return readBundle(loader); // loading will be deferred
+            case VAL_BUNDLE:
+                object = readBundle(loader); // loading will be deferred
+                break;
 
-        case VAL_PERSISTABLEBUNDLE:
-            return readPersistableBundle(loader);
+            case VAL_PERSISTABLEBUNDLE:
+                object = readPersistableBundle(loader);
+                break;
 
-        case VAL_SIZE:
-            return readSize();
+            case VAL_SIZE:
+                object = readSize();
+                break;
 
-        case VAL_SIZEF:
-            return readSizeF();
+            case VAL_SIZEF:
+                object = readSizeF();
+                break;
 
-        case VAL_DOUBLEARRAY:
-            return createDoubleArray();
+            case VAL_DOUBLEARRAY:
+                object = createDoubleArray();
+                break;
 
-        default:
-            int off = dataPosition() - 4;
-            throw new RuntimeException(
-                "Parcel " + this + ": Unmarshalling unknown type code " + type + " at offset " + off);
+            case VAL_CHAR:
+                object = (char) readInt();
+                break;
+
+            case VAL_SHORTARRAY:
+                object = createShortArray();
+                break;
+
+            case VAL_CHARARRAY:
+                object = createCharArray();
+                break;
+
+            case VAL_FLOATARRAY:
+                object = createFloatArray();
+                break;
+
+            default:
+                int off = dataPosition() - 4;
+                throw new RuntimeException(
+                    "Parcel " + this + ": Unmarshalling unknown type code " + type
+                            + " at offset " + off);
         }
+        if (clazz != null && !clazz.isInstance(object)) {
+            throw new BadParcelableException("Unparcelled object " + object
+                    + " is not an instance of required class " + clazz.getName()
+                    + " provided in the parameter");
+        }
+        return (T) object;
     }
 
     private boolean isLengthPrefixed(int type) {
+        // In general, we want custom types and containers of custom types to be length-prefixed,
+        // this allows clients (eg. Bundle) to skip their content during deserialization. The
+        // exception to this is Bundle, since Bundle is already length-prefixed and already copies
+        // the correspondent section of the parcel internally.
         switch (type) {
+            case VAL_MAP:
             case VAL_PARCELABLE:
-            case VAL_PARCELABLEARRAY:
             case VAL_LIST:
             case VAL_SPARSEARRAY:
-            case VAL_BUNDLE:
+            case VAL_PARCELABLEARRAY:
+            case VAL_OBJECTARRAY:
             case VAL_SERIALIZABLE:
                 return true;
             default:
@@ -3643,17 +3820,42 @@
      * @throws BadParcelableException Throws BadParcelableException if there
      * was an error trying to instantiate the Parcelable.
      */
-    @SuppressWarnings("unchecked")
     @Nullable
     public final <T extends Parcelable> T readParcelable(@Nullable ClassLoader loader) {
-        Parcelable.Creator<?> creator = readParcelableCreator(loader);
+        return readParcelableInternal(loader, /* clazz */ null);
+    }
+
+    /**
+     * Same as {@link #readParcelable(ClassLoader)} but accepts {@code clazz} parameter as the type
+     * required for each item. If the item to be deserialized is not an instance of that class or
+     * any of its children classes a {@link BadParcelableException} will be thrown.
+     */
+    @Nullable
+    public <T extends Parcelable> T readParcelable(@Nullable ClassLoader loader,
+            @NonNull Class<T> clazz) {
+        Objects.requireNonNull(clazz);
+        return readParcelableInternal(loader, clazz);
+    }
+
+    /**
+     *
+     * @param clazz The type of the parcelable expected or {@code null} for performing no checks.
+     */
+    @SuppressWarnings("unchecked")
+    @Nullable
+    private <T> T readParcelableInternal(@Nullable ClassLoader loader, @Nullable Class<T> clazz) {
+        if (clazz != null && !Parcelable.class.isAssignableFrom(clazz)) {
+            throw new BadParcelableException("About to unparcel a parcelable object "
+                    + " but class required " + clazz.getName() + " is not Parcelable");
+        }
+        Parcelable.Creator<?> creator = readParcelableCreatorInternal(loader, clazz);
         if (creator == null) {
             return null;
         }
         if (creator instanceof Parcelable.ClassLoaderCreator<?>) {
-          Parcelable.ClassLoaderCreator<?> classLoaderCreator =
-              (Parcelable.ClassLoaderCreator<?>) creator;
-          return (T) classLoaderCreator.createFromParcel(this, loader);
+            Parcelable.ClassLoaderCreator<?> classLoaderCreator =
+                    (Parcelable.ClassLoaderCreator<?>) creator;
+            return (T) classLoaderCreator.createFromParcel(this, loader);
         }
         return (T) creator.createFromParcel(this);
     }
@@ -3687,6 +3889,28 @@
      */
     @Nullable
     public final Parcelable.Creator<?> readParcelableCreator(@Nullable ClassLoader loader) {
+        return readParcelableCreatorInternal(loader, /* clazz */ null);
+    }
+
+    /**
+     * Same as {@link #readParcelableCreator(ClassLoader)} but accepts {@code clazz} parameter
+     * as the required type. If the item to be deserialized is not an instance of that class
+     * or any of its children classes a {@link BadParcelableException} will be thrown.
+     */
+    @Nullable
+    public <T> Parcelable.Creator<T> readParcelableCreator(
+            @Nullable ClassLoader loader, @NonNull Class<T> clazz) {
+        Objects.requireNonNull(clazz);
+        return readParcelableCreatorInternal(loader, clazz);
+    }
+
+    /**
+     * @param clazz The type of the parcelable expected or {@code null} for performing no checks.
+     */
+    @SuppressWarnings("unchecked")
+    @Nullable
+    private <T> Parcelable.Creator<T> readParcelableCreatorInternal(
+            @Nullable ClassLoader loader, @Nullable Class<T> clazz) {
         String name = readString();
         if (name == null) {
             return null;
@@ -3702,7 +3926,15 @@
             creator = map.get(name);
         }
         if (creator != null) {
-            return creator;
+            if (clazz != null) {
+                Class<?> parcelableClass = creator.getClass().getEnclosingClass();
+                if (!clazz.isAssignableFrom(parcelableClass)) {
+                    throw new BadParcelableException("Parcelable creator " + name + " is not "
+                            + "a subclass of required class " + clazz.getName()
+                            + " provided in the parameter");
+                }
+            }
+            return (Parcelable.Creator<T>) creator;
         }
 
         try {
@@ -3718,6 +3950,14 @@
                 throw new BadParcelableException("Parcelable protocol requires subclassing "
                         + "from Parcelable on class " + name);
             }
+            if (clazz != null) {
+                if (!clazz.isAssignableFrom(parcelableClass)) {
+                    throw new BadParcelableException("Parcelable creator " + name + " is not "
+                            + "a subclass of required class " + clazz.getName()
+                            + " provided in the parameter");
+                }
+            }
+
             Field f = parcelableClass.getField("CREATOR");
             if ((f.getModifiers() & Modifier.STATIC) == 0) {
                 throw new BadParcelableException("Parcelable protocol requires "
@@ -3755,7 +3995,7 @@
             map.put(name, creator);
         }
 
-        return creator;
+        return (Parcelable.Creator<T>) creator;
     }
 
     /**
@@ -4001,13 +4241,21 @@
         return result;
     }
 
-    private void readListInternal(@NonNull List outVal, int N,
+    private void readListInternal(@NonNull List outVal, int n,
             @Nullable ClassLoader loader) {
-        while (N > 0) {
-            Object value = readValue(loader);
+        readListInternal(outVal, n, loader, null);
+    }
+
+    /**
+     * @param clazz The type of the object expected or {@code null} for performing no checks.
+     */
+    private <T> void readListInternal(@NonNull List<? super T> outVal, int n,
+            @Nullable ClassLoader loader, @Nullable Class<T> clazz) {
+        while (n > 0) {
+            T value = readValue(loader, clazz);
             //Log.d(TAG, "Unmarshalling value=" + value);
             outVal.add(value);
-            N--;
+            n--;
         }
     }
 
@@ -4086,6 +4334,10 @@
             case VAL_SIZE: return "VAL_SIZE";
             case VAL_SIZEF: return "VAL_SIZEF";
             case VAL_DOUBLEARRAY: return "VAL_DOUBLEARRAY";
+            case VAL_CHAR: return "VAL_CHAR";
+            case VAL_SHORTARRAY: return "VAL_SHORTARRAY";
+            case VAL_CHARARRAY: return "VAL_CHARARRAY";
+            case VAL_FLOATARRAY: return "VAL_FLOATARRAY";
             case VAL_OBJECTARRAY: return "VAL_OBJECTARRAY";
             case VAL_SERIALIZABLE: return "VAL_SERIALIZABLE";
             default: return "UNKNOWN(" + type + ")";
diff --git a/core/java/android/os/Parcelable.java b/core/java/android/os/Parcelable.java
index a537c98..a396211 100644
--- a/core/java/android/os/Parcelable.java
+++ b/core/java/android/os/Parcelable.java
@@ -16,6 +16,7 @@
 
 package android.os;
 
+import android.annotation.NonNull;
 import android.annotation.IntDef;
 import android.annotation.SystemApi;
 
@@ -202,7 +203,7 @@
      * @param flags Additional flags about how the object should be written.
      * May be 0 or {@link #PARCELABLE_WRITE_RETURN_VALUE}.
      */
-    public void writeToParcel(Parcel dest, @WriteFlags int flags);
+    public void writeToParcel(@NonNull Parcel dest, @WriteFlags int flags);
 
     /**
      * Interface that must be implemented and provided as a public CREATOR
diff --git a/core/java/android/os/ServiceManagerNative.java b/core/java/android/os/ServiceManagerNative.java
index 755c35f..3739040 100644
--- a/core/java/android/os/ServiceManagerNative.java
+++ b/core/java/android/os/ServiceManagerNative.java
@@ -98,6 +98,10 @@
         return mServiceManager.updatableViaApex(name);
     }
 
+    public ConnectionInfo getConnectionInfo(String name) throws RemoteException {
+        return mServiceManager.getConnectionInfo(name);
+    }
+
     public void registerClientCallback(String name, IBinder service, IClientCallback cb)
             throws RemoteException {
         throw new RemoteException();
diff --git a/core/java/android/os/VintfObject.java b/core/java/android/os/VintfObject.java
index bf0b655..1f11197 100644
--- a/core/java/android/os/VintfObject.java
+++ b/core/java/android/os/VintfObject.java
@@ -97,8 +97,11 @@
      * ["android.hidl.manager@1.0", "android.hardware.camera.device@1.0",
      *  "android.hardware.camera.device@3.2"]. There are no duplicates.
      *
-     * For AIDL HALs, the version is stripped away
-     * (e.g. "android.hardware.light").
+     * For AIDL HALs, the version is a single number
+     * (e.g. "android.hardware.light@1"). Historically, this API strips the
+     * version number for AIDL HALs (e.g. "android.hardware.light"). Users
+     * of this API must be able to handle both for backwards compatibility.
+     *
      * @hide
      */
     @TestApi
diff --git a/core/java/android/permission/OWNERS b/core/java/android/permission/OWNERS
index 19a3a8b..b5466b6 100644
--- a/core/java/android/permission/OWNERS
+++ b/core/java/android/permission/OWNERS
@@ -1,11 +1,12 @@
 # Bug component: 137825
 
-eugenesusla@google.com
 evanseverson@google.com
 evanxinchen@google.com
 ewol@google.com
 guojing@google.com
 jaysullivan@google.com
+olekarg@google.com
+pyuli@google.com
 ntmyren@google.com
 svetoslavganov@android.com
 svetoslavganov@google.com
diff --git a/core/java/android/util/ArrayMap.java b/core/java/android/util/ArrayMap.java
index 4cf0a36..418d92c 100644
--- a/core/java/android/util/ArrayMap.java
+++ b/core/java/android/util/ArrayMap.java
@@ -645,7 +645,7 @@
             e.fillInStackTrace();
             Log.w(TAG, "New hash " + hash
                     + " is before end of array hash " + mHashes[index-1]
-                    + " at index " + index + " key " + key, e);
+                    + " at index " + index + (DEBUG ? " key " + key : ""), e);
             put(key, value);
             return;
         }
diff --git a/core/java/android/widget/QuickContactBadge.java b/core/java/android/widget/QuickContactBadge.java
index ea39f6d..fcaeeff 100644
--- a/core/java/android/widget/QuickContactBadge.java
+++ b/core/java/android/widget/QuickContactBadge.java
@@ -396,8 +396,9 @@
                 // Prompt user to add this person to contacts
                 final Intent intent = new Intent(Intents.SHOW_OR_CREATE_CONTACT, createUri);
                 if (extras != null) {
-                    extras.remove(EXTRA_URI_CONTENT);
-                    intent.putExtras(extras);
+                    Bundle bundle = new Bundle(extras);
+                    bundle.remove(EXTRA_URI_CONTENT);
+                    intent.putExtras(bundle);
                 }
                 getContext().startActivity(intent);
             }
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 14d3147..ac19121 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -3026,6 +3026,11 @@
          and one pSIM) -->
     <integer name="config_num_physical_slots">1</integer>
 
+    <!-- When a radio power off request is received, we will delay completing the request until
+         either IMS moves to the deregistered state or the timeout defined by this configuration
+         elapses. If 0, this feature is disabled and we do not delay radio power off requests.-->
+    <integer name="config_delay_for_ims_dereg_millis">0</integer>
+
     <!--Thresholds for LTE dbm in status bar-->
     <integer-array translatable="false" name="config_lteDbmThresholds">
         <item>-140</item>    <!-- SIGNAL_STRENGTH_NONE_OR_UNKNOWN -->
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 2be5152..aebad6a 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -476,6 +476,7 @@
   <java-symbol type="string" name="config_deviceSpecificDevicePolicyManagerService" />
   <java-symbol type="string" name="config_deviceSpecificAudioService" />
   <java-symbol type="integer" name="config_num_physical_slots" />
+  <java-symbol type="integer" name="config_delay_for_ims_dereg_millis" />
   <java-symbol type="array" name="config_integrityRuleProviderPackages" />
   <java-symbol type="bool" name="config_useAssistantVolume" />
   <java-symbol type="string" name="config_bandwidthEstimateSource" />
diff --git a/core/tests/coretests/src/android/os/BundleTest.java b/core/tests/coretests/src/android/os/BundleTest.java
index 4cc70ba..9d2cab3 100644
--- a/core/tests/coretests/src/android/os/BundleTest.java
+++ b/core/tests/coretests/src/android/os/BundleTest.java
@@ -16,16 +16,24 @@
 
 package android.os;
 
+import static com.google.common.truth.Truth.assertThat;
+
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertThrows;
 import static org.junit.Assert.assertTrue;
 
+import android.util.Log;
+
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
+import org.junit.After;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
+import java.util.Objects;
+
 /**
  * Unit tests for bundle that requires accessing hidden APS.  Tests that can be written only with
  * public APIs should go in the CTS counterpart.
@@ -35,6 +43,14 @@
 @SmallTest
 @RunWith(AndroidJUnit4.class)
 public class BundleTest {
+    private Log.TerribleFailureHandler mWtfHandler;
+
+    @After
+    public void tearDown() throws Exception {
+        if (mWtfHandler != null) {
+            Log.setWtfHandler(mWtfHandler);
+        }
+    }
 
     /**
      * Take a bundle, write it to a parcel and return the parcel.
@@ -217,4 +233,193 @@
         // return true
         assertTrue(BaseBundle.kindofEquals(bundle1, bundle2));
     }
+
+    @Test
+    public void kindofEquals_lazyValues() {
+        Parcelable p1 = new CustomParcelable(13, "Tiramisu");
+        Parcelable p2 = new CustomParcelable(13, "Tiramisu");
+
+        // 2 maps with live objects
+        Bundle a = new Bundle();
+        a.putParcelable("key1", p1);
+        Bundle b = new Bundle();
+        b.putParcelable("key1", p2);
+        assertTrue(Bundle.kindofEquals(a, b));
+
+        // 2 identical parcels
+        a.readFromParcel(getParcelledBundle(a));
+        a.setClassLoader(getClass().getClassLoader());
+        b.readFromParcel(getParcelledBundle(b));
+        b.setClassLoader(getClass().getClassLoader());
+        assertTrue(Bundle.kindofEquals(a, b));
+
+        // 2 lazy values with identical parcels inside
+        a.isEmpty();
+        b.isEmpty();
+        assertTrue(Bundle.kindofEquals(a, b));
+
+        // 1 lazy value vs 1 live object
+        a.getParcelable("key1");
+        assertFalse(Bundle.kindofEquals(a, b));
+
+        // 2 live objects
+        b.getParcelable("key1");
+        assertTrue(Bundle.kindofEquals(a, b));
+    }
+
+    @Test
+    public void kindofEquals_lazyValuesWithIdenticalParcels_returnsTrue() {
+        Parcelable p1 = new CustomParcelable(13, "Tiramisu");
+        Parcelable p2 = new CustomParcelable(13, "Tiramisu");
+        Bundle a = new Bundle();
+        a.putParcelable("key", p1);
+        a.readFromParcel(getParcelledBundle(a));
+        a.setClassLoader(getClass().getClassLoader());
+        Bundle b = new Bundle();
+        b.putParcelable("key", p2);
+        b.readFromParcel(getParcelledBundle(b));
+        b.setClassLoader(getClass().getClassLoader());
+        // 2 lazy values with identical parcels inside
+        a.isEmpty();
+        b.isEmpty();
+
+        assertTrue(Bundle.kindofEquals(a, b));
+    }
+
+    @Test
+    public void kindofEquals_lazyValuesAndDifferentClassLoaders_returnsFalse() {
+        Parcelable p1 = new CustomParcelable(13, "Tiramisu");
+        Parcelable p2 = new CustomParcelable(13, "Tiramisu");
+        Bundle a = new Bundle();
+        a.putParcelable("key", p1);
+        a.readFromParcel(getParcelledBundle(a));
+        a.setClassLoader(getClass().getClassLoader());
+        Bundle b = new Bundle();
+        b.putParcelable("key", p2);
+        b.readFromParcel(getParcelledBundle(b));
+        b.setClassLoader(Bundle.class.getClassLoader()); // BCP
+        // 2 lazy values with identical parcels inside
+        a.isEmpty();
+        b.isEmpty();
+
+        assertFalse(Bundle.kindofEquals(a, b));
+    }
+
+    @Test
+    public void kindofEquals_lazyValuesOfDifferentTypes_returnsFalse() {
+        Parcelable p = new CustomParcelable(13, "Tiramisu");
+        Parcelable[] ps = {p};
+        Bundle a = new Bundle();
+        a.putParcelable("key", p);
+        a.readFromParcel(getParcelledBundle(a));
+        a.setClassLoader(getClass().getClassLoader());
+        Bundle b = new Bundle();
+        b.putParcelableArray("key", ps);
+        b.readFromParcel(getParcelledBundle(b));
+        b.setClassLoader(getClass().getClassLoader());
+        a.isEmpty();
+        b.isEmpty();
+
+        assertFalse(Bundle.kindofEquals(a, b));
+    }
+
+    @Test
+    public void kindofEquals_lazyValuesWithDifferentLengths_returnsFalse() {
+        Parcelable p1 = new CustomParcelable(13, "Tiramisu");
+        Parcelable p2 = new CustomParcelable(13, "Tiramisuuuuuuuu");
+        Bundle a = new Bundle();
+        a.putParcelable("key", p1);
+        a.readFromParcel(getParcelledBundle(a));
+        a.setClassLoader(getClass().getClassLoader());
+        Bundle b = new Bundle();
+        b.putParcelable("key", p2);
+        b.readFromParcel(getParcelledBundle(b));
+        b.setClassLoader(getClass().getClassLoader());
+        a.isEmpty();
+        b.isEmpty();
+
+        assertFalse(Bundle.kindofEquals(a, b));
+    }
+
+    @Test
+    public void readWriteLengthMismatch_logsWtf() throws Exception {
+        mWtfHandler = Log.setWtfHandler((tag, e, system) -> {
+            throw new RuntimeException(e);
+        });
+        Parcelable parcelable = new CustomParcelable(13, "Tiramisu").setHasLengthMismatch(true);
+        Bundle bundle = new Bundle();
+        bundle.putParcelable("p", parcelable);
+        bundle.readFromParcel(getParcelledBundle(bundle));
+        bundle.setClassLoader(getClass().getClassLoader());
+        RuntimeException e = assertThrows(RuntimeException.class, () -> bundle.getParcelable("p"));
+        assertThat(e.getCause()).isInstanceOf(Log.TerribleFailure.class);
+    }
+
+    private static class CustomParcelable implements Parcelable {
+        public final int integer;
+        public final String string;
+        public boolean hasLengthMismatch;
+
+        CustomParcelable(int integer, String string) {
+            this.integer = integer;
+            this.string = string;
+        }
+
+        protected CustomParcelable(Parcel in) {
+            integer = in.readInt();
+            string = in.readString();
+            hasLengthMismatch = in.readBoolean();
+        }
+
+        public CustomParcelable setHasLengthMismatch(boolean hasLengthMismatch) {
+            this.hasLengthMismatch = hasLengthMismatch;
+            return this;
+        }
+
+        @Override
+        public int describeContents() {
+            return 0;
+        }
+
+        @Override
+        public void writeToParcel(Parcel out, int flags) {
+            out.writeInt(integer);
+            out.writeString(string);
+            out.writeBoolean(hasLengthMismatch);
+            if (hasLengthMismatch) {
+                out.writeString("extra-write");
+            }
+        }
+
+        @Override
+        public boolean equals(Object other) {
+            if (this == other) {
+                return true;
+            }
+            if (!(other instanceof CustomParcelable)) {
+                return false;
+            }
+            CustomParcelable
+                    that = (CustomParcelable) other;
+            return integer == that.integer
+                    && hasLengthMismatch == that.hasLengthMismatch
+                    && string.equals(that.string);
+        }
+
+        @Override
+        public int hashCode() {
+            return Objects.hash(integer, string, hasLengthMismatch);
+        }
+
+        public static final Creator<CustomParcelable> CREATOR = new Creator<CustomParcelable>() {
+            @Override
+            public CustomParcelable createFromParcel(Parcel in) {
+                return new CustomParcelable(in);
+            }
+            @Override
+            public CustomParcelable[] newArray(int size) {
+                return new CustomParcelable[size];
+            }
+        };
+    }
 }
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothEventManager.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothEventManager.java
index a086918..70baf1d 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothEventManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothEventManager.java
@@ -18,6 +18,7 @@
 
 import android.bluetooth.BluetoothA2dp;
 import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothCsipSetCoordinator;
 import android.bluetooth.BluetoothDevice;
 import android.bluetooth.BluetoothHeadset;
 import android.bluetooth.BluetoothHearingAid;
@@ -125,6 +126,9 @@
         addHandler(BluetoothDevice.ACTION_ACL_CONNECTED, new AclStateChangedHandler());
         addHandler(BluetoothDevice.ACTION_ACL_DISCONNECTED, new AclStateChangedHandler());
 
+        addHandler(BluetoothCsipSetCoordinator.ACTION_CSIS_SET_MEMBER_AVAILABLE,
+                new SetMemberAvailableHandler());
+
         registerAdapterIntentReceiver();
     }
 
@@ -338,6 +342,12 @@
             }
             int bondState = intent.getIntExtra(BluetoothDevice.EXTRA_BOND_STATE,
                     BluetoothDevice.ERROR);
+
+            if (mDeviceManager.onBondStateChangedIfProcess(device, bondState)) {
+                Log.d(TAG, "Should not update UI for the set member");
+                return;
+            }
+
             CachedBluetoothDevice cachedDevice = mDeviceManager.findDevice(device);
             if (cachedDevice == null) {
                 Log.w(TAG, "Got bonding state changed for " + device +
@@ -351,8 +361,10 @@
             cachedDevice.onBondingStateChanged(bondState);
 
             if (bondState == BluetoothDevice.BOND_NONE) {
-                /* Check if we need to remove other Hearing Aid devices */
-                if (cachedDevice.getHiSyncId() != BluetoothHearingAid.HI_SYNC_ID_INVALID) {
+                // Check if we need to remove other Coordinated set member devices / Hearing Aid
+                // devices
+                if (cachedDevice.getGroupId() != BluetoothCsipSetCoordinator.GROUP_ID_INVALID
+                        || cachedDevice.getHiSyncId() != BluetoothHearingAid.HI_SYNC_ID_INVALID) {
                     mDeviceManager.onDeviceUnpaired(cachedDevice);
                 }
                 int reason = intent.getIntExtra(BluetoothDevice.EXTRA_REASON,
@@ -499,4 +511,29 @@
             dispatchAudioModeChanged();
         }
     }
+
+    private class SetMemberAvailableHandler implements Handler {
+        @Override
+        public void onReceive(Context context, Intent intent, BluetoothDevice device) {
+            final String action = intent.getAction();
+            if (device == null) {
+                Log.e(TAG, "SetMemberAvailableHandler: device is null");
+                return;
+            }
+
+            if (action == null) {
+                Log.e(TAG, "SetMemberAvailableHandler: action is null");
+                return;
+            }
+
+            final int groupId = intent.getIntExtra(BluetoothCsipSetCoordinator.EXTRA_CSIS_GROUP_ID,
+                    BluetoothCsipSetCoordinator.GROUP_ID_INVALID);
+            if (groupId == BluetoothCsipSetCoordinator.GROUP_ID_INVALID) {
+                Log.e(TAG, "SetMemberAvailableHandler: Invalid group id");
+                return;
+            }
+
+            mDeviceManager.onSetMemberAppear(device, groupId);
+        }
+    }
 }
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
index 6a590c2..78fc139 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
@@ -18,6 +18,7 @@
 
 import android.bluetooth.BluetoothAdapter;
 import android.bluetooth.BluetoothClass;
+import android.bluetooth.BluetoothCsipSetCoordinator;
 import android.bluetooth.BluetoothDevice;
 import android.bluetooth.BluetoothHearingAid;
 import android.bluetooth.BluetoothProfile;
@@ -41,7 +42,9 @@
 
 import java.util.ArrayList;
 import java.util.Collection;
+import java.util.HashSet;
 import java.util.List;
+import java.util.Set;
 import java.util.concurrent.CopyOnWriteArrayList;
 
 /**
@@ -66,6 +69,7 @@
     private final Object mProfileLock = new Object();
     BluetoothDevice mDevice;
     private long mHiSyncId;
+    private int mGroupId;
     // Need this since there is no method for getting RSSI
     short mRssi;
     // mProfiles and mRemovedProfiles does not do swap() between main and sub device. It is
@@ -100,6 +104,8 @@
     private boolean mIsA2dpProfileConnectedFail = false;
     private boolean mIsHeadsetProfileConnectedFail = false;
     private boolean mIsHearingAidProfileConnectedFail = false;
+    // Group member devices for the coordinated set
+    private Set<CachedBluetoothDevice> mMemberDevices = new HashSet<CachedBluetoothDevice>();
     // Group second device for Hearing Aid
     private CachedBluetoothDevice mSubDevice;
 
@@ -133,6 +139,7 @@
         mDevice = device;
         fillData();
         mHiSyncId = BluetoothHearingAid.HI_SYNC_ID_INVALID;
+        mGroupId = BluetoothCsipSetCoordinator.GROUP_ID_INVALID;
     }
 
     /**
@@ -317,6 +324,24 @@
         return mIsCoordinatedSetMember;
     }
 
+    /**
+    * Get the coordinated set group id.
+    *
+    * @return the group id.
+    */
+    public int getGroupId() {
+        return mGroupId;
+    }
+
+    /**
+    * Set the coordinated set group id.
+    *
+    * @param id the group id from the CSIP.
+    */
+    public void setGroupId(int id) {
+        mGroupId = id;
+    }
+
     void onBondingDockConnect() {
         // Attempt to connect if UUIDs are available. Otherwise,
         // we will connect when the ACTION_UUID intent arrives.
@@ -1191,4 +1216,52 @@
         mSubDevice.mJustDiscovered = tmpJustDiscovered;
         fetchActiveDevices();
     }
+
+    /**
+     * @return a set of member devices that are in the same coordinated set with this device.
+     */
+    public Set<CachedBluetoothDevice> getMemberDevice() {
+        return mMemberDevices;
+    }
+
+    /**
+     * Store the member devices that are in the same coordinated set.
+     */
+    public void setMemberDevice(CachedBluetoothDevice memberDevice) {
+        mMemberDevices.add(memberDevice);
+    }
+
+    /**
+     * Remove a device from the member device sets.
+     */
+    public void removeMemberDevice(CachedBluetoothDevice memberDevice) {
+        mMemberDevices.remove(memberDevice);
+    }
+
+    /**
+     * In order to show the preference for the whole group, we always set the main device as the
+     * first connected device in the coordinated set, and then switch the content of the main
+     * device and member devices.
+     *
+     * @param prevMainDevice the previous Main device, it will be added into the member device set.
+     * @param newMainDevie the new Main device, it will be removed from the member device set.
+     */
+    public void switchMemberDeviceContent(CachedBluetoothDevice prevMainDevice,
+            CachedBluetoothDevice newMainDevie) {
+        // Backup from main device
+        final BluetoothDevice tmpDevice = mDevice;
+        final short tmpRssi = mRssi;
+        final boolean tmpJustDiscovered = mJustDiscovered;
+        // Set main device from sub device
+        mDevice = newMainDevie.mDevice;
+        mRssi = newMainDevie.mRssi;
+        mJustDiscovered = newMainDevie.mJustDiscovered;
+        setMemberDevice(prevMainDevice);
+        mMemberDevices.remove(newMainDevie);
+        // Set sub device from backup
+        newMainDevie.mDevice = tmpDevice;
+        newMainDevie.mRssi = tmpRssi;
+        newMainDevie.mJustDiscovered = tmpJustDiscovered;
+        fetchActiveDevices();
+    }
 }
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManager.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManager.java
index cca9cfa..0256615 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManager.java
@@ -18,6 +18,7 @@
 
 import android.bluetooth.BluetoothAdapter;
 import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothProfile;
 import android.content.Context;
 import android.util.Log;
 
@@ -26,6 +27,7 @@
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.List;
+import java.util.Set;
 
 /**
  * CachedBluetoothDeviceManager manages the set of remote Bluetooth devices.
@@ -41,11 +43,15 @@
     final List<CachedBluetoothDevice> mCachedDevices = new ArrayList<CachedBluetoothDevice>();
     @VisibleForTesting
     HearingAidDeviceManager mHearingAidDeviceManager;
+    @VisibleForTesting
+    CsipDeviceManager mCsipDeviceManager;
+    BluetoothDevice mOngoingSetMemberPair;
 
     CachedBluetoothDeviceManager(Context context, LocalBluetoothManager localBtManager) {
         mContext = context;
         mBtManager = localBtManager;
         mHearingAidDeviceManager = new HearingAidDeviceManager(localBtManager, mCachedDevices);
+        mCsipDeviceManager = new CsipDeviceManager(localBtManager, mCachedDevices);
     }
 
     public synchronized Collection<CachedBluetoothDevice> getCachedDevicesCopy() {
@@ -79,7 +85,16 @@
             if (cachedDevice.getDevice().equals(device)) {
                 return cachedDevice;
             }
-            // Check sub devices if it exists
+            // Check the member devices for the coordinated set if it exists
+            final Set<CachedBluetoothDevice> memberDevices = cachedDevice.getMemberDevice();
+            if (memberDevices != null) {
+                for (CachedBluetoothDevice memberDevice : memberDevices) {
+                    if (memberDevice.getDevice().equals(device)) {
+                        return memberDevice;
+                    }
+                }
+            }
+            // Check sub devices for hearing aid if it exists
             CachedBluetoothDevice subDevice = cachedDevice.getSubDevice();
             if (subDevice != null && subDevice.getDevice().equals(device)) {
                 return subDevice;
@@ -102,8 +117,10 @@
             newDevice = findDevice(device);
             if (newDevice == null) {
                 newDevice = new CachedBluetoothDevice(mContext, profileManager, device);
+                mCsipDeviceManager.initCsipDeviceIfNeeded(newDevice);
                 mHearingAidDeviceManager.initHearingAidDeviceIfNeeded(newDevice);
-                if (!mHearingAidDeviceManager.setSubDeviceIfNeeded(newDevice)) {
+                if (!mCsipDeviceManager.setMemberDeviceIfNeeded(newDevice)
+                        && !mHearingAidDeviceManager.setSubDeviceIfNeeded(newDevice)) {
                     mCachedDevices.add(newDevice);
                     mBtManager.getEventManager().dispatchDeviceAdded(newDevice);
                 }
@@ -114,13 +131,23 @@
     }
 
     /**
-     * Returns device summary of the pair of the hearing aid passed as the parameter.
+     * Returns device summary of the pair of the hearing aid / CSIP passed as the parameter.
      *
      * @param CachedBluetoothDevice device
-     * @return Device summary, or if the pair does not exist or if it is not a hearing aid,
-     * then {@code null}.
+     * @return Device summary, or if the pair does not exist or if it is not a hearing aid or
+     * a CSIP set member, then {@code null}.
      */
     public synchronized String getSubDeviceSummary(CachedBluetoothDevice device) {
+        final Set<CachedBluetoothDevice> memberDevices = device.getMemberDevice();
+        if (memberDevices != null) {
+            for (CachedBluetoothDevice memberDevice : memberDevices) {
+                if (!memberDevice.isConnected()) {
+                    return null;
+                }
+            }
+
+            return device.getConnectionSummary();
+        }
         CachedBluetoothDevice subDevice = device.getSubDevice();
         if (subDevice != null && subDevice.isConnected()) {
             return subDevice.getConnectionSummary();
@@ -132,12 +159,22 @@
      * Search for existing sub device {@link CachedBluetoothDevice}.
      *
      * @param device the address of the Bluetooth device
-     * @return true for found sub device or false.
+     * @return true for found sub / member device or false.
      */
     public synchronized boolean isSubDevice(BluetoothDevice device) {
         for (CachedBluetoothDevice cachedDevice : mCachedDevices) {
             if (!cachedDevice.getDevice().equals(device)) {
-                // Check sub devices if it exists
+                // Check the member devices of the coordinated set if it exists
+                Set<CachedBluetoothDevice> memberDevices = cachedDevice.getMemberDevice();
+                if (memberDevices != null) {
+                    for (CachedBluetoothDevice memberDevice : memberDevices) {
+                        if (memberDevice.getDevice().equals(device)) {
+                            return true;
+                        }
+                    }
+                    continue;
+                }
+                // Check sub devices of hearing aid if it exists
                 CachedBluetoothDevice subDevice = cachedDevice.getSubDevice();
                 if (subDevice != null && subDevice.getDevice().equals(device)) {
                     return true;
@@ -157,6 +194,14 @@
     }
 
     /**
+     * Updates the Csip devices; specifically the GroupId's. This routine is called when the
+     * CSIS is connected and the GroupId's are now available.
+     */
+    public synchronized void updateCsipDevices() {
+        mCsipDeviceManager.updateCsipDevices();
+    }
+
+    /**
      * Attempts to get the name of a remote device, otherwise returns the address.
      *
      * @param device The remote device.
@@ -185,6 +230,16 @@
     private void clearNonBondedSubDevices() {
         for (int i = mCachedDevices.size() - 1; i >= 0; i--) {
             CachedBluetoothDevice cachedDevice = mCachedDevices.get(i);
+            final Set<CachedBluetoothDevice> memberDevices = cachedDevice.getMemberDevice();
+            if (memberDevices != null) {
+                for (CachedBluetoothDevice memberDevice : memberDevices) {
+                    // Member device exists and it is not bonded
+                    if (memberDevice.getDevice().getBondState() == BluetoothDevice.BOND_NONE) {
+                        cachedDevice.removeMemberDevice(memberDevice);
+                    }
+                }
+                return;
+            }
             CachedBluetoothDevice subDevice = cachedDevice.getSubDevice();
             if (subDevice != null
                     && subDevice.getDevice().getBondState() == BluetoothDevice.BOND_NONE) {
@@ -201,6 +256,13 @@
         for (int i = mCachedDevices.size() - 1; i >= 0; i--) {
             CachedBluetoothDevice cachedDevice = mCachedDevices.get(i);
             cachedDevice.setJustDiscovered(false);
+            final Set<CachedBluetoothDevice> memberDevices = cachedDevice.getMemberDevice();
+            if (memberDevices != null) {
+                for (CachedBluetoothDevice memberDevice : memberDevices) {
+                    memberDevice.setJustDiscovered(false);
+                }
+                return;
+            }
             final CachedBluetoothDevice subDevice = cachedDevice.getSubDevice();
             if (subDevice != null) {
                 subDevice.setJustDiscovered(false);
@@ -214,10 +276,19 @@
         if (bluetoothState == BluetoothAdapter.STATE_TURNING_OFF) {
             for (int i = mCachedDevices.size() - 1; i >= 0; i--) {
                 CachedBluetoothDevice cachedDevice = mCachedDevices.get(i);
-                CachedBluetoothDevice subDevice = cachedDevice.getSubDevice();
-                if (subDevice != null) {
-                    if (subDevice.getBondState() != BluetoothDevice.BOND_BONDED) {
-                        cachedDevice.setSubDevice(null);
+                final Set<CachedBluetoothDevice> memberDevices = cachedDevice.getMemberDevice();
+                if (memberDevices != null) {
+                    for (CachedBluetoothDevice memberDevice : memberDevices) {
+                        if (memberDevice.getBondState() != BluetoothDevice.BOND_BONDED) {
+                            cachedDevice.removeMemberDevice(memberDevice);
+                        }
+                    }
+                } else {
+                    CachedBluetoothDevice subDevice = cachedDevice.getSubDevice();
+                    if (subDevice != null) {
+                        if (subDevice.getBondState() != BluetoothDevice.BOND_BONDED) {
+                            cachedDevice.setSubDevice(null);
+                        }
                     }
                 }
                 if (cachedDevice.getBondState() != BluetoothDevice.BOND_BONDED) {
@@ -229,13 +300,32 @@
     }
 
     public synchronized boolean onProfileConnectionStateChangedIfProcessed(CachedBluetoothDevice
-            cachedDevice, int state) {
-        return mHearingAidDeviceManager.onProfileConnectionStateChangedIfProcessed(cachedDevice,
+            cachedDevice, int state, int profileId) {
+        if (profileId == BluetoothProfile.HEARING_AID) {
+            return mHearingAidDeviceManager.onProfileConnectionStateChangedIfProcessed(cachedDevice,
                 state);
+        }
+        if (profileId == BluetoothProfile.CSIP_SET_COORDINATOR) {
+            return mCsipDeviceManager.onProfileConnectionStateChangedIfProcessed(cachedDevice,
+                state);
+        }
+        return false;
     }
 
     public synchronized void onDeviceUnpaired(CachedBluetoothDevice device) {
-        CachedBluetoothDevice mainDevice = mHearingAidDeviceManager.findMainDevice(device);
+        CachedBluetoothDevice mainDevice = mCsipDeviceManager.findMainDevice(device);
+        final Set<CachedBluetoothDevice> memberDevices = device.getMemberDevice();
+        if (memberDevices != null) {
+            // Main device is unpaired, to unpair the member device
+            for (CachedBluetoothDevice memberDevice : memberDevices) {
+                memberDevice.unpair();
+                device.removeMemberDevice(memberDevice);
+            }
+        } else if (mainDevice != null) {
+            // the member device unpaired, to unpair main device
+            mainDevice.unpair();
+        }
+        mainDevice = mHearingAidDeviceManager.findMainDevice(device);
         CachedBluetoothDevice subDevice = device.getSubDevice();
         if (subDevice != null) {
             // Main device is unpaired, to unpair sub device
@@ -248,6 +338,74 @@
         }
     }
 
+    /**
+     * Called when we found a set member of a group. The function will check the {@code groupId} if
+     * it exists and if there is a ongoing pair, the device would be ignored.
+     *
+     * @param device The found device
+     * @param groupId The group id of the found device
+     */
+    public synchronized void onSetMemberAppear(BluetoothDevice device, int groupId) {
+        Log.d(TAG, "onSetMemberAppear, groupId: " + groupId + " device: " + device.toString());
+
+        if (mOngoingSetMemberPair != null) {
+            Log.d(TAG, "Ongoing set memberPairing in process, drop it!");
+            return;
+        }
+
+        if (mCsipDeviceManager.onSetMemberAppear(device, groupId)) {
+            mOngoingSetMemberPair = device;
+        }
+    }
+
+    /**
+     * Called when the bond state change. If the bond state change is related with the
+     * ongoing set member pair, the cachedBluetoothDevice will be created but the UI
+     * would not be updated. For the other case, return {@code false} to go through the normal
+     * flow.
+     *
+     * @param device The device
+     * @param bondState The new bond state
+     *
+     * @return {@code true}, if the bond state change for the device is handled inside this
+     * function, and would not like to update the UI. If not, return {@code false}.
+     */
+    public synchronized boolean onBondStateChangedIfProcess(BluetoothDevice device, int bondState) {
+        if (mOngoingSetMemberPair == null || !mOngoingSetMemberPair.equals(device)) {
+            return false;
+        }
+
+        if (bondState == BluetoothDevice.BOND_BONDING) {
+            return true;
+        }
+
+        mOngoingSetMemberPair = null;
+        if (bondState != BluetoothDevice.BOND_NONE) {
+            if (findDevice(device) == null) {
+                final LocalBluetoothProfileManager profileManager = mBtManager.getProfileManager();
+                CachedBluetoothDevice newDevice =
+                        new CachedBluetoothDevice(mContext, profileManager, device);
+                mCachedDevices.add(newDevice);
+                findDevice(device).connect();
+            }
+        }
+
+        return true;
+    }
+
+    /**
+     * Check if the device is the one which is initial paired locally by CSIP. The setting
+     * would depned on it to accept the pairing request automatically
+     *
+     * @param device The device
+     *
+     * @return {@code true}, if the device is ongoing pair by CSIP. Otherwise, return
+     * {@code false}.
+     */
+    public boolean isOngoingPairByCsip(BluetoothDevice device) {
+        return !(mOngoingSetMemberPair == null) && mOngoingSetMemberPair.equals(device);
+    }
+
     private void log(String msg) {
         if (DEBUG) {
             Log.d(TAG, msg);
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CsipDeviceManager.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CsipDeviceManager.java
new file mode 100644
index 0000000..347e14b
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CsipDeviceManager.java
@@ -0,0 +1,270 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.bluetooth;
+
+import android.bluetooth.BluetoothCsipSetCoordinator;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothProfile;
+import android.bluetooth.BluetoothUuid;
+import android.os.ParcelUuid;
+import android.util.Log;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * CsipDeviceManager manages the set of remote CSIP Bluetooth devices.
+ */
+public class CsipDeviceManager {
+    private static final String TAG = "CsipDeviceManager";
+    private static final boolean DEBUG = BluetoothUtils.D;
+
+    private final LocalBluetoothManager mBtManager;
+    private final List<CachedBluetoothDevice> mCachedDevices;
+
+    CsipDeviceManager(LocalBluetoothManager localBtManager,
+            List<CachedBluetoothDevice> cachedDevices) {
+        mBtManager = localBtManager;
+        mCachedDevices = cachedDevices;
+    };
+
+    void initCsipDeviceIfNeeded(CachedBluetoothDevice newDevice) {
+        // Current it only supports the base uuid for CSIP and group this set in UI.
+        final int groupId = getBaseGroupId(newDevice.getDevice());
+        if (isValidGroupId(groupId)) {
+            log("initCsipDeviceIfNeeded: " + newDevice + " (group: " + groupId + ")");
+            // Once groupId is valid, assign groupId
+            newDevice.setGroupId(groupId);
+        }
+    }
+
+    private int getBaseGroupId(BluetoothDevice device) {
+        final LocalBluetoothProfileManager profileManager = mBtManager.getProfileManager();
+        final CsipSetCoordinatorProfile profileProxy = profileManager
+                .getCsipSetCoordinatorProfile();
+        if (profileProxy != null) {
+            final Map<Integer, ParcelUuid> groupIdMap = profileProxy
+                    .getGroupUuidMapByDevice(device);
+            if (groupIdMap == null) {
+                return BluetoothCsipSetCoordinator.GROUP_ID_INVALID;
+            }
+
+            for (Map.Entry<Integer, ParcelUuid> entry: groupIdMap.entrySet()) {
+                if (entry.getValue().equals(BluetoothUuid.BASE_UUID)) {
+                    return entry.getKey();
+                }
+            }
+        }
+        return BluetoothCsipSetCoordinator.GROUP_ID_INVALID;
+    }
+
+    boolean setMemberDeviceIfNeeded(CachedBluetoothDevice newDevice) {
+        final int groupId = newDevice.getGroupId();
+        if (isValidGroupId(groupId)) {
+            final CachedBluetoothDevice CsipDevice = getCachedDevice(groupId);
+            log("setMemberDeviceIfNeeded, main: " + CsipDevice + ", member: " + newDevice);
+            // Just add one of the coordinated set from a pair in the list that is shown in the UI.
+            // Once there is other devices with the same groupId, to add new device as member
+            // devices.
+            if (CsipDevice != null) {
+                CsipDevice.setMemberDevice(newDevice);
+                newDevice.setName(CsipDevice.getName());
+                return true;
+            }
+        }
+        return false;
+    }
+
+    private boolean isValidGroupId(int groupId) {
+        return groupId != BluetoothCsipSetCoordinator.GROUP_ID_INVALID;
+    }
+
+    private CachedBluetoothDevice getCachedDevice(int groupId) {
+        for (int i = mCachedDevices.size() - 1; i >= 0; i--) {
+            CachedBluetoothDevice cachedDevice = mCachedDevices.get(i);
+            if (cachedDevice.getGroupId() == groupId) {
+                return cachedDevice;
+            }
+        }
+        return null;
+    }
+
+    // To collect all set member devices and call #onGroupIdChanged to group device by GroupId
+    void updateCsipDevices() {
+        final Set<Integer> newGroupIdSet = new HashSet<Integer>();
+        for (CachedBluetoothDevice cachedDevice : mCachedDevices) {
+            // Do nothing if GroupId has been assigned
+            if (!isValidGroupId(cachedDevice.getGroupId())) {
+                final int newGroupId = getBaseGroupId(cachedDevice.getDevice());
+                // Do nothing if there is no GroupId on Bluetooth device
+                if (isValidGroupId(newGroupId)) {
+                    cachedDevice.setGroupId(newGroupId);
+                    newGroupIdSet.add(newGroupId);
+                }
+            }
+        }
+        for (int groupId : newGroupIdSet) {
+            onGroupIdChanged(groupId);
+        }
+    }
+
+    // Group devices by groupId
+    @VisibleForTesting
+    void onGroupIdChanged(int groupId) {
+        int firstMatchedIndex = -1;
+        CachedBluetoothDevice mainDevice = null;
+
+        for (int i = mCachedDevices.size() - 1; i >= 0; i--) {
+            final CachedBluetoothDevice cachedDevice = mCachedDevices.get(i);
+            if (cachedDevice.getGroupId() != groupId) {
+                continue;
+            }
+
+            if (firstMatchedIndex == -1) {
+                // Found the first one
+                firstMatchedIndex = i;
+                mainDevice = cachedDevice;
+                continue;
+            }
+
+            log("onGroupIdChanged: removed from UI device =" + cachedDevice
+                    + ", with groupId=" + groupId + " firstMatchedIndex=" + firstMatchedIndex);
+
+            mainDevice.setMemberDevice(cachedDevice);
+            mCachedDevices.remove(i);
+            mBtManager.getEventManager().dispatchDeviceRemoved(cachedDevice);
+            break;
+        }
+    }
+
+    // @return {@code true}, the event is processed inside the method. It is for updating
+    // le audio device on group relationship when receiving connected or disconnected.
+    // @return {@code false}, it is not le audio device or to process it same as other profiles
+    boolean onProfileConnectionStateChangedIfProcessed(CachedBluetoothDevice cachedDevice,
+            int state) {
+        log("onProfileConnectionStateChangedIfProcessed: " + cachedDevice + ", state: " + state);
+        switch (state) {
+            case BluetoothProfile.STATE_CONNECTED:
+                onGroupIdChanged(cachedDevice.getGroupId());
+                CachedBluetoothDevice mainDevice = findMainDevice(cachedDevice);
+                if (mainDevice != null) {
+                    if (mainDevice.isConnected()) {
+                        // When main device exists and in connected state, receiving member device
+                        // connection. To refresh main device UI
+                        mainDevice.refresh();
+                        return true;
+                    } else {
+                        // When both LE Audio devices are disconnected, receiving member device
+                        // connection. To switch content and dispatch to notify UI change
+                        mBtManager.getEventManager().dispatchDeviceRemoved(mainDevice);
+                        mainDevice.switchMemberDeviceContent(mainDevice, cachedDevice);
+                        mainDevice.refresh();
+                        // It is necessary to do remove and add for updating the mapping on
+                        // preference and device
+                        mBtManager.getEventManager().dispatchDeviceAdded(mainDevice);
+                        return true;
+                    }
+                }
+                break;
+            case BluetoothProfile.STATE_DISCONNECTED:
+                mainDevice = findMainDevice(cachedDevice);
+                if (mainDevice != null) {
+                    // When main device exists, receiving sub device disconnection
+                    // To update main device UI
+                    mainDevice.refresh();
+                    return true;
+                }
+                final Set<CachedBluetoothDevice> memberSet = cachedDevice.getMemberDevice();
+                if (memberSet == null) {
+                    break;
+                }
+
+                for (CachedBluetoothDevice device: memberSet) {
+                    if (device.isConnected()) {
+                        // Main device is disconnected and sub device is connected
+                        // To copy data from sub device to main device
+                        mBtManager.getEventManager().dispatchDeviceRemoved(cachedDevice);
+                        cachedDevice.switchMemberDeviceContent(device, cachedDevice);
+                        cachedDevice.refresh();
+                        // It is necessary to do remove and add for updating the mapping on
+                        // preference and device
+                        mBtManager.getEventManager().dispatchDeviceAdded(cachedDevice);
+                        return true;
+                    }
+                }
+                break;
+            default:
+                // Do not handle this state.
+        }
+        return false;
+    }
+
+    CachedBluetoothDevice findMainDevice(CachedBluetoothDevice device) {
+        if (device == null || mCachedDevices == null) {
+            return null;
+        }
+
+        for (CachedBluetoothDevice cachedDevice : mCachedDevices) {
+            if (isValidGroupId(cachedDevice.getGroupId())) {
+                Set<CachedBluetoothDevice> memberSet = cachedDevice.getMemberDevice();
+                if (memberSet == null) {
+                    continue;
+                }
+
+                for (CachedBluetoothDevice memberDevice: memberSet) {
+                    if (memberDevice != null && memberDevice.equals(device)) {
+                        return cachedDevice;
+                    }
+                }
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Called when we found a set member of a group. The function will check bond state, and
+     * the {@code groupId} if it exists, and then create the bond.
+     *
+     * @param device The found device
+     * @param groupId The group id of the found device
+     *
+     * @return {@code true}, if the we create bond with the device. Otherwise, return
+     * {@code false}.
+     */
+    public boolean onSetMemberAppear(BluetoothDevice device, int groupId) {
+        if (device.getBondState() != BluetoothDevice.BOND_NONE) {
+            return false;
+        }
+
+        if (getCachedDevice(groupId) != null) {
+            device.createBond(BluetoothDevice.TRANSPORT_LE);
+            return true;
+        }
+
+        return false;
+    }
+
+    private void log(String msg) {
+        if (DEBUG) {
+            Log.d(TAG, msg);
+        }
+    }
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CsipSetCoordinatorProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CsipSetCoordinatorProfile.java
index 754914f..6da249c 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CsipSetCoordinatorProfile.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CsipSetCoordinatorProfile.java
@@ -26,6 +26,7 @@
 import android.bluetooth.BluetoothDevice;
 import android.bluetooth.BluetoothProfile;
 import android.content.Context;
+import android.os.ParcelUuid;
 import android.util.Log;
 
 import androidx.annotation.RequiresApi;
@@ -34,6 +35,7 @@
 
 import java.util.ArrayList;
 import java.util.List;
+import java.util.Map;
 
 /**
  * CSIP Set Coordinator handles Bluetooth CSIP Set Coordinator role profile.
@@ -80,6 +82,7 @@
                 device.refresh();
             }
 
+            mDeviceManager.updateCsipDevices();
             mProfileManager.callServiceConnectedListeners();
             mIsProfileReady = true;
         }
@@ -212,6 +215,18 @@
     }
 
     /**
+     * Get the device's groups and correspondsing uuids map.
+     * @param device the bluetooth device
+     * @return Map of groups ids and related UUIDs
+     */
+    public Map<Integer, ParcelUuid> getGroupUuidMapByDevice(BluetoothDevice device) {
+        if (mService == null || device == null) {
+            return null;
+        }
+        return mService.getGroupUuidMapByDevice(device);
+    }
+
+    /**
      * Return the profile name as a string.
      */
     public String toString() {
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothProfileManager.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothProfileManager.java
index 25fd430..24113c5 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothProfileManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothProfileManager.java
@@ -318,11 +318,35 @@
                     }
                 }
             }
+
+            if (getCsipSetCoordinatorProfile() != null
+                    && mProfile instanceof CsipSetCoordinatorProfile
+                    && newState == BluetoothProfile.STATE_CONNECTED) {
+                // Check if the GroupID has being initialized
+                if (cachedDevice.getGroupId() == BluetoothCsipSetCoordinator.GROUP_ID_INVALID) {
+                    final Map<Integer, ParcelUuid> groupIdMap = getCsipSetCoordinatorProfile()
+                            .getGroupUuidMapByDevice(cachedDevice.getDevice());
+                    if (groupIdMap != null) {
+                        for (Map.Entry<Integer, ParcelUuid> entry: groupIdMap.entrySet()) {
+                            if (entry.getValue().equals(BluetoothUuid.BASE_UUID)) {
+                                cachedDevice.setGroupId(entry.getKey());
+                                break;
+                            }
+                        }
+                    }
+                }
+            }
+
             cachedDevice.onProfileStateChanged(mProfile, newState);
             // Dispatch profile changed after device update
-            if (!(cachedDevice.getHiSyncId() != BluetoothHearingAid.HI_SYNC_ID_INVALID
-                    && mDeviceManager.onProfileConnectionStateChangedIfProcessed(cachedDevice,
-                    newState))) {
+            boolean needDispatchProfileConnectionState = true;
+            if (cachedDevice.getHiSyncId() != BluetoothHearingAid.HI_SYNC_ID_INVALID
+                    || cachedDevice.getGroupId() != BluetoothCsipSetCoordinator.GROUP_ID_INVALID) {
+                needDispatchProfileConnectionState = !mDeviceManager
+                        .onProfileConnectionStateChangedIfProcessed(cachedDevice, newState,
+                        mProfile.getProfileId());
+            }
+            if (needDispatchProfileConnectionState) {
                 cachedDevice.refresh();
                 mEventManager.dispatchProfileConnectionStateChanged(cachedDevice, newState,
                         mProfile.getProfileId());
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/LocalBluetoothProfileManagerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/LocalBluetoothProfileManagerTest.java
index fd5b053..4f8fa2f 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/LocalBluetoothProfileManagerTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/LocalBluetoothProfileManagerTest.java
@@ -206,7 +206,7 @@
         mContext.sendBroadcast(mIntent);
 
         verify(mDeviceManager).onProfileConnectionStateChangedIfProcessed(mCachedBluetoothDevice,
-                BluetoothProfile.STATE_CONNECTED);
+                BluetoothProfile.STATE_CONNECTED, BluetoothProfile.HEARING_AID);
     }
 
     /**
diff --git a/services/core/java/com/android/server/NsdService.java b/services/core/java/com/android/server/NsdService.java
index a9f3a1b..462ed5c 100644
--- a/services/core/java/com/android/server/NsdService.java
+++ b/services/core/java/com/android/server/NsdService.java
@@ -82,6 +82,8 @@
 
     private static final int INVALID_ID = 0;
     private int mUniqueId = 1;
+    // The count of the connected legacy clients.
+    private int mLegacyClientCount = 0;
 
     private class NsdStateMachine extends StateMachine {
 
@@ -107,7 +109,9 @@
             sendMessageDelayed(NsdManager.DAEMON_CLEANUP, mCleanupDelayMs);
         }
         private void maybeScheduleStop() {
-            if (!isAnyRequestActive()) {
+            // The native daemon should stay alive and can't be cleanup
+            // if any legacy client connected.
+            if (!isAnyRequestActive() && mLegacyClientCount == 0) {
                 scheduleStop();
             }
         }
@@ -175,11 +179,11 @@
                         if (cInfo != null) {
                             cInfo.expungeAllRequests();
                             mClients.remove(msg.replyTo);
+                            if (cInfo.isLegacy()) {
+                                mLegacyClientCount -= 1;
+                            }
                         }
-                        //Last client
-                        if (mClients.size() == 0) {
-                            scheduleStop();
-                        }
+                        maybeScheduleStop();
                         break;
                     case AsyncChannel.CMD_CHANNEL_FULL_CONNECTION:
                         AsyncChannel ac = new AsyncChannel();
@@ -208,6 +212,17 @@
                     case NsdManager.DAEMON_CLEANUP:
                         mDaemon.maybeStop();
                         break;
+                    // This event should be only sent by the legacy (target SDK < S) clients.
+                    // Mark the sending client as legacy.
+                    case NsdManager.DAEMON_STARTUP:
+                        cInfo = mClients.get(msg.replyTo);
+                        if (cInfo != null) {
+                            cancelStop();
+                            cInfo.setLegacy();
+                            mLegacyClientCount += 1;
+                            maybeStartDaemon();
+                        }
+                        break;
                     case NsdManager.NATIVE_DAEMON_EVENT:
                     default:
                         Slog.e(TAG, "Unhandled " + msg);
@@ -863,6 +878,9 @@
         /* A map from client id to the type of the request we had received */
         private final SparseIntArray mClientRequests = new SparseIntArray();
 
+        // The target SDK of this client < Build.VERSION_CODES.S
+        private boolean mIsLegacy = false;
+
         private ClientInfo(AsyncChannel c, Messenger m) {
             mChannel = c;
             mMessenger = m;
@@ -875,6 +893,7 @@
             sb.append("mChannel ").append(mChannel).append("\n");
             sb.append("mMessenger ").append(mMessenger).append("\n");
             sb.append("mResolvedService ").append(mResolvedService).append("\n");
+            sb.append("mIsLegacy ").append(mIsLegacy).append("\n");
             for(int i = 0; i< mClientIds.size(); i++) {
                 int clientID = mClientIds.keyAt(i);
                 sb.append("clientId ").append(clientID).
@@ -884,6 +903,14 @@
             return sb.toString();
         }
 
+        private boolean isLegacy() {
+            return mIsLegacy;
+        }
+
+        private void setLegacy() {
+            mIsLegacy = true;
+        }
+
         // Remove any pending requests from the global map when we get rid of a client,
         // and send cancellations to the daemon.
         private void expungeAllRequests() {
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecMessageValidator.java b/services/core/java/com/android/server/hdmi/HdmiCecMessageValidator.java
index c24973d..8926e20 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecMessageValidator.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecMessageValidator.java
@@ -242,7 +242,7 @@
         mValidationInfo.append(opcode, new ValidationInfo(validator, addrType));
     }
 
-    int isValid(HdmiCecMessage message) {
+    int isValid(HdmiCecMessage message, boolean isMessageReceived) {
         int opcode = message.getOpcode();
         ValidationInfo info = mValidationInfo.get(opcode);
         if (info == null) {
@@ -256,6 +256,22 @@
             HdmiLogger.warning("Unexpected source: " + message);
             return ERROR_SOURCE;
         }
+
+        if (isMessageReceived) {
+            // Check if the source's logical address and local device's logical
+            // address are the same.
+            for (HdmiCecLocalDevice device : mService.getAllLocalDevices()) {
+                synchronized (device.mLock) {
+                    if (message.getSource() == device.getDeviceInfo().getLogicalAddress()
+                            && message.getSource() != Constants.ADDR_UNREGISTERED) {
+                        HdmiLogger.warning(
+                                "Unexpected source: message sent from device itself, " + message);
+                        return ERROR_SOURCE;
+                    }
+                }
+            }
+        }
+
         // Check the destination field.
         if (message.getDestination() == Constants.ADDR_BROADCAST) {
             if ((info.addressType & DEST_BROADCAST) == 0) {
diff --git a/services/core/java/com/android/server/hdmi/HdmiControlService.java b/services/core/java/com/android/server/hdmi/HdmiControlService.java
index cca8be8..b049d01 100644
--- a/services/core/java/com/android/server/hdmi/HdmiControlService.java
+++ b/services/core/java/com/android/server/hdmi/HdmiControlService.java
@@ -556,6 +556,12 @@
         // on boot, if device is interactive, set HDMI CEC state as powered on as well
         if (mPowerManager.isInteractive() && isPowerStandbyOrTransient()) {
             mPowerStatus = HdmiControlManager.POWER_STATUS_ON;
+            // Start all actions that were queued because the device was in standby
+            if (mAddressAllocated) {
+                for (HdmiCecLocalDevice localDevice : getAllLocalDevices()) {
+                    localDevice.startQueuedActions();
+                }
+            }
         }
     }
 
@@ -1122,7 +1128,7 @@
     @ServiceThreadOnly
     void sendCecCommand(HdmiCecMessage command, @Nullable SendMessageCallback callback) {
         assertRunOnServiceThread();
-        if (mMessageValidator.isValid(command) == HdmiCecMessageValidator.OK) {
+        if (mMessageValidator.isValid(command, false) == HdmiCecMessageValidator.OK) {
             mCecController.sendCommand(command, callback);
         } else {
             HdmiLogger.error("Invalid message type:" + command);
@@ -1153,7 +1159,7 @@
     @ServiceThreadOnly
     boolean handleCecCommand(HdmiCecMessage message) {
         assertRunOnServiceThread();
-        int errorCode = mMessageValidator.isValid(message);
+        int errorCode = mMessageValidator.isValid(message, true);
         if (errorCode != HdmiCecMessageValidator.OK) {
             // We'll not response on the messages with the invalid source or destination
             // or with parameter length shorter than specified in the standard.
@@ -3353,8 +3359,8 @@
         invokeInputChangeListener(info);
     }
 
-   void setMhlInputChangeEnabled(boolean enabled) {
-       mMhlController.setOption(OPTION_MHL_INPUT_SWITCHING, toInt(enabled));
+    void setMhlInputChangeEnabled(boolean enabled) {
+        mMhlController.setOption(OPTION_MHL_INPUT_SWITCHING, toInt(enabled));
 
         synchronized (mLock) {
             mMhlInputChangeEnabled = enabled;
diff --git a/services/net/java/android/net/ConnectivityModuleConnector.java b/services/net/java/android/net/ConnectivityModuleConnector.java
index 62f2c35..c6b15c1 100644
--- a/services/net/java/android/net/ConnectivityModuleConnector.java
+++ b/services/net/java/android/net/ConnectivityModuleConnector.java
@@ -278,7 +278,10 @@
             // This code path is only run by the system server: only the system server binds
             // to the NetworkStack as a service. Other processes get the NetworkStack from
             // the ServiceManager.
-            maybeCrashWithTerribleFailure("Lost network stack", mPackageName);
+            maybeCrashWithTerribleFailure(
+                "Lost network stack. This is not the root cause of any issue, it is a side "
+                + "effect of a crash that happened earlier. Earlier logs should point to the "
+                + "actual issue.", mPackageName);
         }
     }
 
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTest.java
index 3e5cbea..c45d084 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTest.java
@@ -125,7 +125,7 @@
         mMessageValidator =
                 new HdmiCecMessageValidator(mHdmiControlService) {
                     @Override
-                    int isValid(HdmiCecMessage message) {
+                    int isValid(HdmiCecMessage message, boolean isMessageReceived) {
                         return HdmiCecMessageValidator.OK;
                     }
                 };
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecMessageValidatorTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecMessageValidatorTest.java
index ae7f422..ae99dab 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecMessageValidatorTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecMessageValidatorTest.java
@@ -629,7 +629,7 @@
     }
 
     private IntegerSubject assertMessageValidity(String message) {
-        return assertThat(mHdmiCecMessageValidator.isValid(buildMessage(message)));
+        return assertThat(mHdmiCecMessageValidator.isValid(buildMessage(message), false));
     }
 
     /**
diff --git a/telephony/java/android/telephony/SignalStrengthUpdateRequest.java b/telephony/java/android/telephony/SignalStrengthUpdateRequest.java
index fe7e5976..41e24dd 100644
--- a/telephony/java/android/telephony/SignalStrengthUpdateRequest.java
+++ b/telephony/java/android/telephony/SignalStrengthUpdateRequest.java
@@ -26,8 +26,10 @@
 import java.util.Collection;
 import java.util.Collections;
 import java.util.Comparator;
+import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
+import java.util.Map;
 import java.util.Objects;
 import java.util.Set;
 
@@ -101,9 +103,11 @@
             }
 
             mSignalThresholdInfos = new ArrayList<>(signalThresholdInfos);
-            // Sort the collection with RAN ascending order, make the ordering not matter for equals
+            // Sort the collection with RAN and then SignalMeasurementType ascending order, make the
+            // ordering not matter for equals
             mSignalThresholdInfos.sort(
-                    Comparator.comparingInt(SignalThresholdInfo::getRadioAccessNetworkType));
+                    Comparator.comparingInt(SignalThresholdInfo::getRadioAccessNetworkType)
+                            .thenComparing(SignalThresholdInfo::getSignalMeasurementType));
             return this;
         }
 
@@ -144,7 +148,7 @@
          * @return the SignalStrengthUpdateRequest object
          *
          * @throws IllegalArgumentException if the SignalThresholdInfo collection is empty size, the
-         * radio access network type in the collection is not unique
+         * signal measurement type for the same RAN in the collection is not unique
          */
         public @NonNull SignalStrengthUpdateRequest build() {
             return new SignalStrengthUpdateRequest(mSignalThresholdInfos,
@@ -258,14 +262,23 @@
     }
 
     /**
-     * Throw IAE when the RAN in the collection is not unique.
+     * Throw IAE if SignalThresholdInfo collection is null or empty,
+     * or the SignalMeasurementType for the same RAN in the collection is not unique.
      */
     private static void validate(Collection<SignalThresholdInfo> infos) {
-        Set<Integer> uniqueRan = new HashSet<>(infos.size());
+        if (infos == null || infos.isEmpty()) {
+            throw new IllegalArgumentException("SignalThresholdInfo collection is null or empty");
+        }
+
+        // Map from RAN to set of SignalMeasurementTypes
+        Map<Integer, Set<Integer>> ranToTypes = new HashMap<>(infos.size());
         for (SignalThresholdInfo info : infos) {
             final int ran = info.getRadioAccessNetworkType();
-            if (!uniqueRan.add(ran)) {
-                throw new IllegalArgumentException("RAN: " + ran + " is not unique");
+            final int type = info.getSignalMeasurementType();
+            ranToTypes.putIfAbsent(ran, new HashSet<>());
+            if (!ranToTypes.get(ran).add(type)) {
+                throw new IllegalArgumentException(
+                        "SignalMeasurementType " + type + " for RAN " + ran + " is not unique");
             }
         }
     }
diff --git a/tests/StagedInstallTest/Android.bp b/tests/StagedInstallTest/Android.bp
index 840a588..086ef95 100644
--- a/tests/StagedInstallTest/Android.bp
+++ b/tests/StagedInstallTest/Android.bp
@@ -50,7 +50,7 @@
         "cts-install-lib-host",
     ],
     data: [
-        ":com.android.apex.cts.shim.v2_prebuilt",
+        ":StagedInstallTestApexV2",
         ":TestAppAv1",
     ],
     test_suites: ["general-tests"],