Merge "libbinder: split out PackageManagerNative aidl"
diff --git a/Android.bp b/Android.bp
index 9e63894..1fbdc15 100644
--- a/Android.bp
+++ b/Android.bp
@@ -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",
@@ -408,11 +405,9 @@
     srcs: [
         "core/java/android/content/pm/BaseParceledListSlice.java",
         "core/java/android/content/pm/ParceledListSlice.java",
-        "core/java/android/os/HandlerExecutor.java",
         "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/cmds/idmap2/include/idmap2/FileUtils.h b/cmds/idmap2/include/idmap2/FileUtils.h
index c4e0e1f..8fcaeb1 100644
--- a/cmds/idmap2/include/idmap2/FileUtils.h
+++ b/cmds/idmap2/include/idmap2/FileUtils.h
@@ -17,6 +17,8 @@
 #ifndef IDMAP2_INCLUDE_IDMAP2_FILEUTILS_H_
 #define IDMAP2_INCLUDE_IDMAP2_FILEUTILS_H_
 
+#include <sys/types.h>
+
 #include <string>
 
 namespace android::idmap2::utils {
diff --git a/core/api/current.txt b/core/api/current.txt
index 2c2c679..93ef8f8 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -8657,6 +8657,15 @@
     field public static final int TELEPHONY = 4194304; // 0x400000
   }
 
+  public final class BluetoothCsipSetCoordinator implements java.lang.AutoCloseable android.bluetooth.BluetoothProfile {
+    method public void close();
+    method protected void finalize();
+    method @NonNull public java.util.List<android.bluetooth.BluetoothDevice> getConnectedDevices();
+    method public int getConnectionState(@Nullable android.bluetooth.BluetoothDevice);
+    method @NonNull public java.util.List<android.bluetooth.BluetoothDevice> getDevicesMatchingConnectionStates(@NonNull int[]);
+    field @RequiresPermission(android.Manifest.permission.BLUETOOTH) public static final String ACTION_CSIS_CONNECTION_STATE_CHANGED = "android.bluetooth.action.CSIS_CONNECTION_STATE_CHANGED";
+  }
+
   public final class BluetoothDevice implements android.os.Parcelable {
     method public android.bluetooth.BluetoothGatt connectGatt(android.content.Context, boolean, android.bluetooth.BluetoothGattCallback);
     method public android.bluetooth.BluetoothGatt connectGatt(android.content.Context, boolean, android.bluetooth.BluetoothGattCallback, int);
@@ -8703,6 +8712,7 @@
     field public static final String EXTRA_BOND_STATE = "android.bluetooth.device.extra.BOND_STATE";
     field public static final String EXTRA_CLASS = "android.bluetooth.device.extra.CLASS";
     field public static final String EXTRA_DEVICE = "android.bluetooth.device.extra.DEVICE";
+    field public static final String EXTRA_IS_COORDINATED_SET_MEMBER = "android.bluetooth.extra.IS_COORDINATED_SET_MEMBER";
     field public static final String EXTRA_NAME = "android.bluetooth.device.extra.NAME";
     field public static final String EXTRA_PAIRING_KEY = "android.bluetooth.device.extra.PAIRING_KEY";
     field public static final String EXTRA_PAIRING_VARIANT = "android.bluetooth.device.extra.PAIRING_VARIANT";
@@ -29988,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);
@@ -30132,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/api/system-current.txt b/core/api/system-current.txt
index 906a595..e601583 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -1507,8 +1507,24 @@
     method public void onOobData(int, @NonNull android.bluetooth.OobData);
   }
 
+  public final class BluetoothCsipSetCoordinator implements java.lang.AutoCloseable android.bluetooth.BluetoothProfile {
+    method @NonNull @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public java.util.List<java.lang.Integer> getAllGroupIds(@Nullable android.os.ParcelUuid);
+    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public int getConnectionPolicy(@Nullable android.bluetooth.BluetoothDevice);
+    method @NonNull @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public java.util.Map getGroupUuidMapByDevice(@Nullable android.bluetooth.BluetoothDevice);
+    method @Nullable @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public java.util.UUID groupLock(int, @Nullable java.util.concurrent.Executor, @Nullable android.bluetooth.BluetoothCsipSetCoordinator.ClientLockCallback);
+    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public boolean groupUnlock(@NonNull java.util.UUID);
+    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public boolean setConnectionPolicy(@Nullable android.bluetooth.BluetoothDevice, int);
+    field @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public static final String ACTION_CSIS_DEVICE_AVAILABLE = "android.bluetooth.action.CSIS_DEVICE_AVAILABLE";
+    field @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public static final String ACTION_CSIS_SET_MEMBER_AVAILABLE = "android.bluetooth.action.CSIS_SET_MEMBER_AVAILABLE";
+  }
+
+  public static interface BluetoothCsipSetCoordinator.ClientLockCallback {
+    method public void onGroupLockSet(int, int, boolean);
+  }
+
   public final class BluetoothDevice implements android.os.Parcelable {
     method @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public boolean cancelBondProcess();
+    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADMIN) public boolean createBond(int);
     method @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public boolean createBondOutOfBand(int, @Nullable android.bluetooth.OobData, @Nullable android.bluetooth.OobData);
     method @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public boolean fetchUuidsWithSdp(int);
     method @Nullable @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public byte[] getMetadata(int);
@@ -1560,7 +1576,10 @@
     method @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADMIN) public boolean connect(android.bluetooth.BluetoothDevice);
     method @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADMIN) public boolean disconnect(android.bluetooth.BluetoothDevice);
     method @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public int getConnectionPolicy(@NonNull android.bluetooth.BluetoothDevice);
+    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public boolean isInbandRingingEnabled();
     method @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public boolean setConnectionPolicy(@NonNull android.bluetooth.BluetoothDevice, int);
+    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADMIN) public boolean startScoUsingVirtualVoiceCall();
+    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADMIN) public boolean stopScoUsingVirtualVoiceCall();
   }
 
   public final class BluetoothHearingAid implements android.bluetooth.BluetoothProfile {
@@ -7241,6 +7260,7 @@
   public class SystemConfigManager {
     method @NonNull @RequiresPermission(android.Manifest.permission.READ_CARRIER_APP_INFO) public java.util.Set<java.lang.String> getDisabledUntilUsedPreinstalledCarrierApps();
     method @NonNull @RequiresPermission(android.Manifest.permission.READ_CARRIER_APP_INFO) public java.util.Map<java.lang.String,java.util.List<java.lang.String>> getDisabledUntilUsedPreinstalledCarrierAssociatedApps();
+    method @NonNull public java.util.List<java.lang.String> getEnabledComponentOverrides(@NonNull String);
     method @NonNull @RequiresPermission(android.Manifest.permission.GET_RUNTIME_PERMISSIONS) public int[] getSystemPermissionUids(@NonNull String);
   }
 
diff --git a/core/java/Android.bp b/core/java/Android.bp
index 675cea8..949ba6a 100644
--- a/core/java/Android.bp
+++ b/core/java/Android.bp
@@ -386,10 +386,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/annotation/SuppressLint.java b/core/java/android/annotation/SuppressLint.java
new file mode 100644
index 0000000..2d3456b
--- /dev/null
+++ b/core/java/android/annotation/SuppressLint.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.annotation;
+
+import static java.lang.annotation.ElementType.CONSTRUCTOR;
+import static java.lang.annotation.ElementType.FIELD;
+import static java.lang.annotation.ElementType.LOCAL_VARIABLE;
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.ElementType.PARAMETER;
+import static java.lang.annotation.ElementType.TYPE;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/** Indicates that Lint should ignore the specified warnings for the annotated element. */
+@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE})
+@Retention(RetentionPolicy.CLASS)
+public @interface SuppressLint {
+    /**
+     * The set of warnings (identified by the lint issue id) that should be
+     * ignored by lint. It is not an error to specify an unrecognized name.
+     */
+    String[] value();
+}
diff --git a/core/java/android/annotation/TargetApi.java b/core/java/android/annotation/TargetApi.java
new file mode 100644
index 0000000..975318e
--- /dev/null
+++ b/core/java/android/annotation/TargetApi.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.annotation;
+
+import static java.lang.annotation.ElementType.CONSTRUCTOR;
+import static java.lang.annotation.ElementType.FIELD;
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.ElementType.TYPE;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/** Indicates that Lint should treat this type as targeting a given API level, no matter what the
+    project target is. */
+@Target({TYPE, METHOD, CONSTRUCTOR, FIELD})
+@Retention(RetentionPolicy.CLASS)
+public @interface TargetApi {
+    /**
+     * This sets the target api level for the type..
+     */
+    int value();
+}
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 41170a4..749e8f5 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -6299,7 +6299,13 @@
             final File cacheDir = context.getCacheDir();
             if (cacheDir != null) {
                 // Provide a usable directory for temporary files
-                System.setProperty("java.io.tmpdir", cacheDir.getAbsolutePath());
+                String tmpdir = cacheDir.getAbsolutePath();
+                System.setProperty("java.io.tmpdir", tmpdir);
+                try {
+                    android.system.Os.setenv("TMPDIR", tmpdir, true);
+                } catch (ErrnoException ex) {
+                    Log.w(TAG, "Unable to initialize $TMPDIR", ex);
+                }
             } else {
                 Log.v(TAG, "Unable to initialize \"java.io.tmpdir\" property "
                         + "due to missing cache directory");
diff --git a/core/java/android/bluetooth/BluetoothAdapter.java b/core/java/android/bluetooth/BluetoothAdapter.java
index d487025..313dd3e 100644
--- a/core/java/android/bluetooth/BluetoothAdapter.java
+++ b/core/java/android/bluetooth/BluetoothAdapter.java
@@ -2819,6 +2819,10 @@
         } else if (profile == BluetoothProfile.VOLUME_CONTROL) {
             BluetoothVolumeControl vcs = new BluetoothVolumeControl(context, listener, this);
             return true;
+        } else if (profile == BluetoothProfile.CSIP_SET_COORDINATOR) {
+            BluetoothCsipSetCoordinator csipSetCoordinator =
+                    new BluetoothCsipSetCoordinator(context, listener);
+            return true;
         } else {
             return false;
         }
@@ -2908,6 +2912,11 @@
                 BluetoothVolumeControl vcs = (BluetoothVolumeControl) proxy;
                 vcs.close();
                 break;
+            case BluetoothProfile.CSIP_SET_COORDINATOR:
+                BluetoothCsipSetCoordinator csipSetCoordinator =
+                        (BluetoothCsipSetCoordinator) proxy;
+                csipSetCoordinator.close();
+                break;
         }
     }
 
diff --git a/core/java/android/bluetooth/BluetoothCsipSetCoordinator.java b/core/java/android/bluetooth/BluetoothCsipSetCoordinator.java
new file mode 100644
index 0000000..cb542e5
--- /dev/null
+++ b/core/java/android/bluetooth/BluetoothCsipSetCoordinator.java
@@ -0,0 +1,550 @@
+/*
+ * Copyright 2021 HIMSA II K/S - www.himsa.com.
+ * Represented by EHIMA - www.ehima.com
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.bluetooth;
+
+import android.Manifest;
+import android.annotation.CallbackExecutor;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
+import android.annotation.SdkConstant;
+import android.annotation.SdkConstant.SdkConstantType;
+import android.annotation.SystemApi;
+import android.content.Context;
+import android.os.Binder;
+import android.os.IBinder;
+import android.os.ParcelUuid;
+import android.os.RemoteException;
+import android.util.CloseGuard;
+import android.util.Log;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.UUID;
+import java.util.concurrent.Executor;
+
+/**
+ * This class provides the public APIs to control the Bluetooth CSIP set coordinator.
+ *
+ * <p>BluetoothCsipSetCoordinator is a proxy object for controlling the Bluetooth VC
+ * Service via IPC. Use {@link BluetoothAdapter#getProfileProxy} to get
+ * the BluetoothCsipSetCoordinator proxy object.
+ *
+ */
+public final class BluetoothCsipSetCoordinator implements BluetoothProfile, AutoCloseable {
+    private static final String TAG = "BluetoothCsipSetCoordinator";
+    private static final boolean DBG = false;
+    private static final boolean VDBG = false;
+
+    private CloseGuard mCloseGuard;
+
+    /**
+     * @hide
+     */
+    @SystemApi
+    public interface ClientLockCallback {
+        /**
+         * @hide
+         */
+        @SystemApi void onGroupLockSet(int groupId, int opStatus, boolean isLocked);
+    }
+
+    private static class BluetoothCsipSetCoordinatorLockCallbackDelegate
+            extends IBluetoothCsipSetCoordinatorLockCallback.Stub {
+        private final ClientLockCallback mCallback;
+        private final Executor mExecutor;
+
+        BluetoothCsipSetCoordinatorLockCallbackDelegate(
+                Executor executor, ClientLockCallback callback) {
+            mExecutor = executor;
+            mCallback = callback;
+        }
+
+        @Override
+        public void onGroupLockSet(int groupId, int opStatus, boolean isLocked) {
+            mExecutor.execute(() -> mCallback.onGroupLockSet(groupId, opStatus, isLocked));
+        }
+    };
+
+    /**
+     * Intent used to broadcast the change in connection state of the CSIS
+     * Client.
+     *
+     * <p>This intent will have 3 extras:
+     * <ul>
+     * <li> {@link #EXTRA_STATE} - The current state of the profile. </li>
+     * <li> {@link #EXTRA_PREVIOUS_STATE}- The previous state of the profile.</li>
+     * <li> {@link BluetoothDevice#EXTRA_DEVICE} - The remote device. </li>
+     * </ul>
+     *
+     * <p>{@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} can be any of
+     * {@link #STATE_DISCONNECTED}, {@link #STATE_CONNECTING},
+     * {@link #STATE_CONNECTED}, {@link #STATE_DISCONNECTING}.
+     */
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH)
+    @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+    public static final String ACTION_CSIS_CONNECTION_STATE_CHANGED =
+            "android.bluetooth.action.CSIS_CONNECTION_STATE_CHANGED";
+
+    /**
+     * Intent used to expose broadcast receiving device.
+     *
+     * <p>This intent will have 2 extras:
+     * <ul>
+     * <li> {@link BluetoothDevice#EXTRA_DEVICE} - The remote Broadcast receiver device. </li>
+     * <li> {@link #EXTRA_CSIS_GROUP_ID} - Group identifier. </li>
+     * <li> {@link #EXTRA_CSIS_GROUP_SIZE} - Group size. </li>
+     * <li> {@link #EXTRA_CSIS_GROUP_TYPE_UUID} - Group type UUID. </li>
+     * </ul>
+     *
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED)
+    @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+    public static final String ACTION_CSIS_DEVICE_AVAILABLE =
+            "android.bluetooth.action.CSIS_DEVICE_AVAILABLE";
+
+    /**
+     * Used as an extra field in {@link #ACTION_CSIS_DEVICE_AVAILABLE} intent.
+     * Contains the group id.
+     *
+     * @hide
+     */
+    public static final String EXTRA_CSIS_GROUP_ID = "android.bluetooth.extra.CSIS_GROUP_ID";
+
+    /**
+     * Group size as int extra field in {@link #ACTION_CSIS_DEVICE_AVAILABLE} intent.
+     *
+     * @hide
+     */
+    public static final String EXTRA_CSIS_GROUP_SIZE = "android.bluetooth.extra.CSIS_GROUP_SIZE";
+
+    /**
+     * Group type uuid extra field in {@link #ACTION_CSIS_DEVICE_AVAILABLE} intent.
+     *
+     * @hide
+     */
+    public static final String EXTRA_CSIS_GROUP_TYPE_UUID =
+            "android.bluetooth.extra.CSIS_GROUP_TYPE_UUID";
+
+    /**
+     * Intent used to broadcast information about identified set member
+     * ready to connect.
+     *
+     * <p>This intent will have one extra:
+     * <ul>
+     * <li> {@link BluetoothDevice#EXTRA_DEVICE} - The remote device. It can
+     * be null if no device is active. </li>
+     * <li>  {@link #EXTRA_CSIS_GROUP_ID} - Group identifier. </li>
+     * </ul>
+     *
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED)
+    @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+    public static final String ACTION_CSIS_SET_MEMBER_AVAILABLE =
+            "android.bluetooth.action.CSIS_SET_MEMBER_AVAILABLE";
+
+    /**
+     * This represents an invalid group ID.
+     *
+     * @hide
+     */
+    public static final int GROUP_ID_INVALID = IBluetoothCsipSetCoordinator.CSIS_GROUP_ID_INVALID;
+
+    /**
+     * Indicating that group was locked with success.
+     *
+     * @hide
+     */
+    public static final int GROUP_LOCK_SUCCESS = 0;
+
+    /**
+     * Indicating that group locked failed due to invalid group ID.
+     *
+     * @hide
+     */
+    public static final int GROUP_LOCK_FAILED_INVALID_GROUP = 1;
+
+    /**
+     * Indicating that group locked failed due to empty group.
+     *
+     * @hide
+     */
+    public static final int GROUP_LOCK_FAILED_GROUP_EMPTY = 2;
+
+    /**
+     * Indicating that group locked failed due to group members being disconnected.
+     *
+     * @hide
+     */
+    public static final int GROUP_LOCK_FAILED_GROUP_NOT_CONNECTED = 3;
+
+    /**
+     * Indicating that group locked failed due to group member being already locked.
+     *
+     * @hide
+     */
+    public static final int GROUP_LOCK_FAILED_LOCKED_BY_OTHER = 4;
+
+    /**
+     * Indicating that group locked failed due to other reason.
+     *
+     * @hide
+     */
+    public static final int GROUP_LOCK_FAILED_OTHER_REASON = 5;
+
+    /**
+     * Indicating that group member in locked state was lost.
+     *
+     * @hide
+     */
+    public static final int LOCKED_GROUP_MEMBER_LOST = 6;
+
+    private BluetoothAdapter mAdapter;
+    private final BluetoothProfileConnector<IBluetoothCsipSetCoordinator> mProfileConnector =
+            new BluetoothProfileConnector(this, BluetoothProfile.CSIP_SET_COORDINATOR, TAG,
+                    IBluetoothCsipSetCoordinator.class.getName()) {
+                @Override
+                public IBluetoothCsipSetCoordinator getServiceInterface(IBinder service) {
+                    return IBluetoothCsipSetCoordinator.Stub.asInterface(
+                            Binder.allowBlocking(service));
+                }
+            };
+
+    /**
+     * Create a BluetoothCsipSetCoordinator proxy object for interacting with the local
+     * Bluetooth CSIS service.
+     */
+    /*package*/ BluetoothCsipSetCoordinator(Context context, ServiceListener listener) {
+        mAdapter = BluetoothAdapter.getDefaultAdapter();
+        mProfileConnector.connect(context, listener);
+        mCloseGuard = new CloseGuard();
+        mCloseGuard.open("close");
+    }
+
+    /**
+     * @hide
+     */
+    protected void finalize() {
+        if (mCloseGuard != null) {
+            mCloseGuard.warnIfOpen();
+        }
+        close();
+    }
+
+    /**
+     * @hide
+     */
+    public void close() {
+        mProfileConnector.disconnect();
+    }
+
+    private IBluetoothCsipSetCoordinator getService() {
+        return mProfileConnector.getService();
+    }
+
+    /**
+     * Lock the set.
+     * @param groupId group ID to lock,
+     * @param executor callback executor,
+     * @param cb callback to report lock and unlock events - stays valid until the app unlocks
+     *           using the returned lock identifier or the lock timeouts on the remote side,
+     *           as per CSIS specification,
+     * @return unique lock identifier used for unlocking or null if lock has failed.
+     *
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
+    public
+    @Nullable UUID groupLock(int groupId, @Nullable @CallbackExecutor Executor executor,
+            @Nullable ClientLockCallback cb) {
+        if (VDBG) {
+            log("groupLockSet()");
+        }
+        final IBluetoothCsipSetCoordinator service = getService();
+        try {
+            if (service != null && isEnabled()) {
+                IBluetoothCsipSetCoordinatorLockCallback delegate = null;
+                if ((executor != null) && (cb != null)) {
+                    delegate = new BluetoothCsipSetCoordinatorLockCallbackDelegate(executor, cb);
+                }
+                return service.groupLock(groupId, delegate).getUuid();
+            }
+            if (service == null) {
+                Log.w(TAG, "Proxy not attached to service");
+            }
+            return null;
+        } catch (RemoteException e) {
+            Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
+            return null;
+        }
+    }
+
+    /**
+     * Unlock the set.
+     * @param lockUuid unique lock identifier
+     * @return true if unlocked, false on error
+     *
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
+    public boolean groupUnlock(@NonNull UUID lockUuid) {
+        if (VDBG) {
+            log("groupLockSet()");
+        }
+        if (lockUuid == null) {
+            return false;
+        }
+
+        final IBluetoothCsipSetCoordinator service = getService();
+        try {
+            if (service != null && isEnabled()) {
+                service.groupUnlock(new ParcelUuid(lockUuid));
+                return true;
+            }
+            if (service == null) {
+                Log.w(TAG, "Proxy not attached to service");
+            }
+            return false;
+        } catch (RemoteException e) {
+            Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
+            return false;
+        }
+    }
+
+    /**
+     * Get device's groups.
+     * @param device the active device
+     * @return Map of groups ids and related UUIDs
+     *
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
+    public @NonNull Map getGroupUuidMapByDevice(@Nullable BluetoothDevice device) {
+        if (VDBG) {
+            log("getGroupUuidMapByDevice()");
+        }
+        final IBluetoothCsipSetCoordinator service = getService();
+        try {
+            if (service != null && isEnabled()) {
+                return service.getGroupUuidMapByDevice(device);
+            }
+            if (service == null) {
+                Log.w(TAG, "Proxy not attached to service");
+            }
+            return new HashMap<>();
+        } catch (RemoteException e) {
+            Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
+            return new HashMap<>();
+        }
+    }
+
+    /**
+     * Get group id for the given UUID
+     * @param uuid
+     * @return list of group IDs
+     *
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
+    public @NonNull List<Integer> getAllGroupIds(@Nullable ParcelUuid uuid) {
+        if (VDBG) {
+            log("getAllGroupIds()");
+        }
+        final IBluetoothCsipSetCoordinator service = getService();
+        try {
+            if (service != null && isEnabled()) {
+                return service.getAllGroupIds(uuid);
+            }
+            if (service == null) {
+                Log.w(TAG, "Proxy not attached to service");
+            }
+            return new ArrayList<Integer>();
+        } catch (RemoteException e) {
+            Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
+            return new ArrayList<Integer>();
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public @NonNull List<BluetoothDevice> getConnectedDevices() {
+        if (VDBG) {
+            log("getConnectedDevices()");
+        }
+        final IBluetoothCsipSetCoordinator service = getService();
+        if (service != null && isEnabled()) {
+            try {
+                return service.getConnectedDevices();
+            } catch (RemoteException e) {
+                Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
+                return new ArrayList<BluetoothDevice>();
+            }
+        }
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+        }
+        return new ArrayList<BluetoothDevice>();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public
+    @NonNull List<BluetoothDevice> getDevicesMatchingConnectionStates(
+            @NonNull int[] states) {
+        if (VDBG) {
+            log("getDevicesMatchingStates(states=" + Arrays.toString(states) + ")");
+        }
+        final IBluetoothCsipSetCoordinator service = getService();
+        if (service != null && isEnabled()) {
+            try {
+                return service.getDevicesMatchingConnectionStates(states);
+            } catch (RemoteException e) {
+                Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
+                return new ArrayList<BluetoothDevice>();
+            }
+        }
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+        }
+        return new ArrayList<BluetoothDevice>();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public
+    @BluetoothProfile.BtProfileState int getConnectionState(
+            @Nullable BluetoothDevice device) {
+        if (VDBG) {
+            log("getState(" + device + ")");
+        }
+        final IBluetoothCsipSetCoordinator service = getService();
+        if (service != null && isEnabled() && isValidDevice(device)) {
+            try {
+                return service.getConnectionState(device);
+            } catch (RemoteException e) {
+                Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
+                return BluetoothProfile.STATE_DISCONNECTED;
+            }
+        }
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+        }
+        return BluetoothProfile.STATE_DISCONNECTED;
+    }
+
+    /**
+     * Set connection policy of the profile
+     *
+     * <p> The device should already be paired.
+     * Connection policy can be one of {@link #CONNECTION_POLICY_ALLOWED},
+     * {@link #CONNECTION_POLICY_FORBIDDEN}, {@link #CONNECTION_POLICY_UNKNOWN}
+     *
+     * @param device Paired bluetooth device
+     * @param connectionPolicy is the connection policy to set to for this profile
+     * @return true if connectionPolicy is set, false on error
+     *
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
+    public boolean setConnectionPolicy(
+            @Nullable BluetoothDevice device, @ConnectionPolicy int connectionPolicy) {
+        if (DBG) {
+            log("setConnectionPolicy(" + device + ", " + connectionPolicy + ")");
+        }
+        final IBluetoothCsipSetCoordinator service = getService();
+        try {
+            if (service != null && isEnabled() && isValidDevice(device)) {
+                if (connectionPolicy != BluetoothProfile.CONNECTION_POLICY_FORBIDDEN
+                        && connectionPolicy != BluetoothProfile.CONNECTION_POLICY_ALLOWED) {
+                    return false;
+                }
+                return service.setConnectionPolicy(device, connectionPolicy);
+            }
+            if (service == null) {
+                Log.w(TAG, "Proxy not attached to service");
+            }
+            return false;
+        } catch (RemoteException e) {
+            Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
+            return false;
+        }
+    }
+
+    /**
+     * Get the connection policy of the profile.
+     *
+     * <p> The connection policy can be any of:
+     * {@link #CONNECTION_POLICY_ALLOWED}, {@link #CONNECTION_POLICY_FORBIDDEN},
+     * {@link #CONNECTION_POLICY_UNKNOWN}
+     *
+     * @param device Bluetooth device
+     * @return connection policy of the device
+     *
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
+    public @ConnectionPolicy int getConnectionPolicy(@Nullable BluetoothDevice device) {
+        if (VDBG) {
+            log("getConnectionPolicy(" + device + ")");
+        }
+        final IBluetoothCsipSetCoordinator service = getService();
+        try {
+            if (service != null && isEnabled() && isValidDevice(device)) {
+                return service.getConnectionPolicy(device);
+            }
+            if (service == null) {
+                Log.w(TAG, "Proxy not attached to service");
+            }
+            return BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
+        } catch (RemoteException e) {
+            Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
+            return BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
+        }
+    }
+
+    private boolean isEnabled() {
+        return mAdapter.getState() == BluetoothAdapter.STATE_ON;
+    }
+
+    private static boolean isValidDevice(@Nullable BluetoothDevice device) {
+        return device != null && BluetoothAdapter.checkBluetoothAddress(device.getAddress());
+    }
+
+    private static void log(String msg) {
+        Log.d(TAG, msg);
+    }
+}
diff --git a/core/java/android/bluetooth/BluetoothDevice.java b/core/java/android/bluetooth/BluetoothDevice.java
index 38fb90d..1054b4c 100644
--- a/core/java/android/bluetooth/BluetoothDevice.java
+++ b/core/java/android/bluetooth/BluetoothDevice.java
@@ -106,7 +106,7 @@
      * <p>Sent when a remote device is found during discovery.
      * <p>Always contains the extra fields {@link #EXTRA_DEVICE} and {@link
      * #EXTRA_CLASS}. Can contain the extra fields {@link #EXTRA_NAME} and/or
-     * {@link #EXTRA_RSSI} if they are available.
+     * {@link #EXTRA_RSSI} and/or {@link #EXTRA_IS_COORDINATED_SET_MEMBER} if they are available.
      * <p>Requires {@link android.Manifest.permission#BLUETOOTH} and
      * {@link android.Manifest.permission#ACCESS_COARSE_LOCATION} to receive.
      */
@@ -257,6 +257,15 @@
     public static final String EXTRA_RSSI = "android.bluetooth.device.extra.RSSI";
 
     /**
+    * Used as an bool extra field in {@link #ACTION_FOUND} intents.
+    * It contains the information if device is discovered as member of a coordinated set or not.
+    * Pairing with device that belongs to a set would trigger pairing with the rest of set members.
+    * See Bluetooth CSIP specification for more details.
+    */
+    public static final String EXTRA_IS_COORDINATED_SET_MEMBER =
+            "android.bluetooth.extra.IS_COORDINATED_SET_MEMBER";
+
+    /**
      * Used as a Parcelable {@link BluetoothClass} extra field in {@link
      * #ACTION_FOUND} and {@link #ACTION_CLASS_CHANGED} intents.
      */
@@ -1326,7 +1335,7 @@
      * @throws IllegalArgumentException if an invalid transport was specified
      * @hide
      */
-    @UnsupportedAppUsage
+    @SystemApi
     @RequiresPermission(Manifest.permission.BLUETOOTH_ADMIN)
     public boolean createBond(int transport) {
         return createBondInternal(transport, null, null);
diff --git a/core/java/android/bluetooth/BluetoothHeadset.java b/core/java/android/bluetooth/BluetoothHeadset.java
index 632572d..18aa23a 100644
--- a/core/java/android/bluetooth/BluetoothHeadset.java
+++ b/core/java/android/bluetooth/BluetoothHeadset.java
@@ -1018,8 +1018,8 @@
      *  - binder is dead or Bluetooth is disabled or other error
      * @hide
      */
+    @SystemApi
     @RequiresPermission(Manifest.permission.BLUETOOTH_ADMIN)
-    @UnsupportedAppUsage
     public boolean startScoUsingVirtualVoiceCall() {
         if (DBG) log("startScoUsingVirtualVoiceCall()");
         final IBluetoothHeadset service = mService;
@@ -1048,8 +1048,8 @@
      *  - binder is dead or Bluetooth is disabled or other error
      * @hide
      */
+    @SystemApi
     @RequiresPermission(Manifest.permission.BLUETOOTH_ADMIN)
-    @UnsupportedAppUsage
     public boolean stopScoUsingVirtualVoiceCall() {
         if (DBG) log("stopScoUsingVirtualVoiceCall()");
         final IBluetoothHeadset service = mService;
@@ -1227,7 +1227,8 @@
      * @return true if in-band ringing is enabled, false if in-band ringing is disabled
      * @hide
      */
-    @RequiresPermission(android.Manifest.permission.BLUETOOTH)
+    @SystemApi
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED)
     public boolean isInbandRingingEnabled() {
         if (DBG) {
             log("isInbandRingingEnabled()");
diff --git a/core/java/android/bluetooth/OWNERS b/core/java/android/bluetooth/OWNERS
index 2239100..fbee577 100644
--- a/core/java/android/bluetooth/OWNERS
+++ b/core/java/android/bluetooth/OWNERS
@@ -1,5 +1,6 @@
 # Bug component: 27441
 
-zachoverflow@google.com
-siyuanh@google.com
 rahulsabnis@google.com
+sattiraju@google.com
+siyuanh@google.com
+zachoverflow@google.com
diff --git a/core/java/android/net/NetworkTemplate.java b/core/java/android/net/NetworkTemplate.java
index 68917a8..08f75df 100644
--- a/core/java/android/net/NetworkTemplate.java
+++ b/core/java/android/net/NetworkTemplate.java
@@ -47,7 +47,6 @@
 import android.util.BackupUtils;
 import android.util.Log;
 
-import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.ArrayUtils;
 import com.android.net.module.util.NetworkIdentityUtils;
 
@@ -151,24 +150,6 @@
         }
     }
 
-    private static boolean sForceAllNetworkTypes = false;
-
-    /**
-     * Results in matching against all mobile network types.
-     *
-     * <p>See {@link #matchesMobile} and {@link matchesMobileWildcard}.
-     */
-    @VisibleForTesting
-    public static void forceAllNetworkTypes() {
-        sForceAllNetworkTypes = true;
-    }
-
-    /** Resets the affect of {@link #forceAllNetworkTypes}. */
-    @VisibleForTesting
-    public static void resetForceAllNetworkTypes() {
-        sForceAllNetworkTypes = false;
-    }
-
     /**
      * Template to match {@link ConnectivityManager#TYPE_MOBILE} networks with
      * the given IMSI.
@@ -611,7 +592,7 @@
             // Only metered mobile network would be matched regardless of metered filter.
             // This is used to exclude non-metered APNs, e.g. IMS. See ag/908650.
             // TODO: Respect metered filter and remove mMetered condition.
-            return (sForceAllNetworkTypes || (ident.mType == TYPE_MOBILE && ident.mMetered))
+            return (ident.mType == TYPE_MOBILE && ident.mMetered)
                     && !ArrayUtils.isEmpty(mMatchSubscriberIds)
                     && ArrayUtils.contains(mMatchSubscriberIds, ident.mSubscriberId)
                     && matchesCollapsedRatType(ident);
@@ -726,7 +707,7 @@
         if (ident.mType == TYPE_WIMAX) {
             return true;
         } else {
-            return (sForceAllNetworkTypes || (ident.mType == TYPE_MOBILE && ident.mMetered))
+            return (ident.mType == TYPE_MOBILE && ident.mMetered)
                     && matchesCollapsedRatType(ident);
         }
     }
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/ISystemConfig.aidl b/core/java/android/os/ISystemConfig.aidl
index 4d160da..d83d94a 100644
--- a/core/java/android/os/ISystemConfig.aidl
+++ b/core/java/android/os/ISystemConfig.aidl
@@ -40,4 +40,9 @@
      * @see SystemConfigManager#getSystemPermissionUids
      */
     int[] getSystemPermissionUids(String permissionName);
+
+    /**
+     * @see SystemConfigManager#getEnabledComponentOverrides
+     */
+    List<String> getEnabledComponentOverrides(String packageName);
 }
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/SystemConfigManager.java b/core/java/android/os/SystemConfigManager.java
index 9bfa8ad..a6316df 100644
--- a/core/java/android/os/SystemConfigManager.java
+++ b/core/java/android/os/SystemConfigManager.java
@@ -17,6 +17,7 @@
 
 import android.Manifest;
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.annotation.RequiresPermission;
 import android.annotation.SystemApi;
 import android.annotation.SystemService;
@@ -129,4 +130,21 @@
             throw e.rethrowFromSystemServer();
         }
     }
+
+    /**
+     * Get enabled component for a specific package
+     *
+     * @param packageName The target package.
+     * @return The enabled component
+     * {@hide}
+     */
+    @SystemApi
+    @NonNull
+    public List<String> getEnabledComponentOverrides(@NonNull String packageName) {
+        try {
+            return mInterface.getEnabledComponentOverrides(packageName);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
 }
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/java/com/android/internal/os/OWNERS b/core/java/com/android/internal/os/OWNERS
index ea3b3a7..7766b77 100644
--- a/core/java/com/android/internal/os/OWNERS
+++ b/core/java/com/android/internal/os/OWNERS
@@ -10,4 +10,6 @@
 per-file *ChargeCalculator* = file:/BATTERY_STATS_OWNERS
 per-file *PowerCalculator* = file:/BATTERY_STATS_OWNERS
 per-file *PowerEstimator* = file:/BATTERY_STATS_OWNERS
+per-file *Kernel* = file:/BATTERY_STATS_OWNERS
+per-file *MultiState* = file:/BATTERY_STATS_OWNERS
 
diff --git a/core/jni/com_android_internal_os_Zygote.cpp b/core/jni/com_android_internal_os_Zygote.cpp
index 515c08d..288327e 100644
--- a/core/jni/com_android_internal_os_Zygote.cpp
+++ b/core/jni/com_android_internal_os_Zygote.cpp
@@ -1129,14 +1129,14 @@
 }
 
 // Relabel directory
-static void relabelDir(const char* path, security_context_t context, fail_fn_t fail_fn) {
+static void relabelDir(const char* path, const char* context, fail_fn_t fail_fn) {
   if (setfilecon(path, context) != 0) {
     fail_fn(CREATE_ERROR("Failed to setfilecon %s %s", path, strerror(errno)));
   }
 }
 
 // Relabel all directories under a path non-recursively.
-static void relabelAllDirs(const char* path, security_context_t context, fail_fn_t fail_fn) {
+static void relabelAllDirs(const char* path, const char* context, fail_fn_t fail_fn) {
   DIR* dir = opendir(path);
   if (dir == nullptr) {
     fail_fn(CREATE_ERROR("Failed to opendir %s", path));
@@ -1211,7 +1211,7 @@
   snprintf(internalDePath, PATH_MAX, "/data/user_de");
   snprintf(externalPrivateMountPath, PATH_MAX, "/mnt/expand");
 
-  security_context_t dataDataContext = nullptr;
+  char* dataDataContext = nullptr;
   if (getfilecon(internalDePath, &dataDataContext) < 0) {
     fail_fn(CREATE_ERROR("Unable to getfilecon on %s %s", internalDePath,
         strerror(errno)));
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 0135e45c..35a3cde 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -196,6 +196,9 @@
         android:name="android.bluetooth.hearingaid.profile.action.PLAYING_STATE_CHANGED" />
     <protected-broadcast
         android:name="android.bluetooth.hearingaid.profile.action.ACTIVE_DEVICE_CHANGED" />
+    <protected-broadcast android:name="android.bluetooth.action.CSIS_CONNECTION_STATE_CHANGED" />
+    <protected-broadcast android:name="android.bluetooth.action.CSIS_DEVICE_AVAILABLE" />
+    <protected-broadcast android:name="android.bluetooth.action.CSIS_SET_MEMBER_AVAILABLE" />
     <protected-broadcast
         android:name="android.bluetooth.volume-control.profile.action.CONNECTION_STATE_CHANGED" />
     <protected-broadcast
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/app/activity/ActivityThreadTest.java b/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java
index 000e870..2d63351 100644
--- a/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java
+++ b/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java
@@ -88,6 +88,11 @@
                     false /* launchActivity */);
 
     @Test
+    public void testTemporaryDirectory() throws Exception {
+        assertEquals(System.getProperty("java.io.tmpdir"), System.getenv("TMPDIR"));
+    }
+
+    @Test
     public void testDoubleRelaunch() throws Exception {
         final Activity activity = mActivityTestRule.launchActivity(new Intent());
         final IApplicationThread appThread = activity.getActivityThread().getApplicationThread();
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/core/tests/hosttests/Android.mk b/core/tests/hosttests/Android.mk
deleted file mode 100644
index f26d401..0000000
--- a/core/tests/hosttests/Android.mk
+++ /dev/null
@@ -1,20 +0,0 @@
-# Copyright (C) 2010 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.
-
-LOCAL_PATH := $(call my-dir)
-
-include $(CLEAR_VARS)
-
-# Build the test APKs using their own makefiles
-include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/core/tests/hosttests/test-apps/Android.mk b/core/tests/hosttests/test-apps/Android.mk
deleted file mode 100644
index e25764f..0000000
--- a/core/tests/hosttests/test-apps/Android.mk
+++ /dev/null
@@ -1,21 +0,0 @@
-# Copyright (C) 2010 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.
-
-LOCAL_PATH := $(call my-dir)
-
-include $(CLEAR_VARS)
-
-# Build the test APKs using their own makefiles
-include $(call all-makefiles-under,$(LOCAL_PATH))
-
diff --git a/core/tests/hosttests/test-apps/DownloadManagerTestApp/Android.bp b/core/tests/hosttests/test-apps/DownloadManagerTestApp/Android.bp
new file mode 100644
index 0000000..d439124
--- /dev/null
+++ b/core/tests/hosttests/test-apps/DownloadManagerTestApp/Android.bp
@@ -0,0 +1,38 @@
+// Copyright (C) 2010 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 {
+    default_applicable_licenses: ["frameworks_base_license"],
+}
+
+android_test {
+    name: "DownloadManagerTestApp",
+
+    srcs: ["src/**/*.java"],
+
+    static_libs: [
+        "android-common",
+        "mockwebserver",
+        "junit",
+    ],
+    libs: [
+        "android.test.runner",
+        "android.test.base",
+    ],
+
+    platform_apis: true,
+
+    // Need to run as system app to get access to Settings. This test won't work for user builds.
+    certificate: "platform",
+}
diff --git a/core/tests/hosttests/test-apps/DownloadManagerTestApp/Android.mk b/core/tests/hosttests/test-apps/DownloadManagerTestApp/Android.mk
deleted file mode 100644
index d9e6151..0000000
--- a/core/tests/hosttests/test-apps/DownloadManagerTestApp/Android.mk
+++ /dev/null
@@ -1,37 +0,0 @@
-# Copyright (C) 2010 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.
-
-LOCAL_PATH:= $(call my-dir)
-
-include $(CLEAR_VARS)
-
-LOCAL_MODULE_TAGS := tests
-
-LOCAL_SRC_FILES := $(call all-java-files-under, src)
-
-LOCAL_STATIC_JAVA_LIBRARIES := android-common mockwebserver junit
-LOCAL_JAVA_LIBRARIES := android.test.runner android.test.base
-
-LOCAL_PACKAGE_NAME := DownloadManagerTestApp
-LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
-LOCAL_LICENSE_CONDITIONS := notice
-LOCAL_NOTICE_FILE  := $(LOCAL_PATH)/../../../../../NOTICE
-LOCAL_PRIVATE_PLATFORM_APIS := true
-
-ifneq ($(TARGET_BUILD_VARIANT),user)
-# Need to run as system app to get access to Settings. This test won't work for user builds.
-LOCAL_CERTIFICATE := platform
-endif
-
-include $(BUILD_PACKAGE)
diff --git a/core/tests/hosttests/test-apps/MultiDexLegacyAndException/Android.bp b/core/tests/hosttests/test-apps/MultiDexLegacyAndException/Android.bp
new file mode 100644
index 0000000..d0645b0
--- /dev/null
+++ b/core/tests/hosttests/test-apps/MultiDexLegacyAndException/Android.bp
@@ -0,0 +1,47 @@
+// Copyright (C) 2014 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 {
+    default_applicable_licenses: ["frameworks_base_license"],
+}
+
+// The application with a minimal main dex
+android_test_helper_app {
+    name: "MultiDexLegacyAndException",
+
+    static_libs: [
+        "android-support-multidex",
+        "android-support-multidex-instrumentation",
+        "androidx.test.rules",
+    ],
+
+    srcs: ["src/**/*.java"],
+
+    sdk_version: "16",
+
+    javacflags: ["-nowarn"],
+
+    main_dex_rules: [":mainDexClassesRules"],
+    dxflags: [
+        // --debug triggers the old --minimal-main-dex behavior
+        "--debug",
+    ],
+    optimize: {
+        // disable optimization to force D8 instead of R8, as R8 doesn't support
+        // --main-dex-rules.
+        enabled: false,
+    },
+
+    min_sdk_version: "16",
+}
diff --git a/core/tests/hosttests/test-apps/MultiDexLegacyAndException/Android.mk b/core/tests/hosttests/test-apps/MultiDexLegacyAndException/Android.mk
deleted file mode 100644
index 2d8556f..0000000
--- a/core/tests/hosttests/test-apps/MultiDexLegacyAndException/Android.mk
+++ /dev/null
@@ -1,51 +0,0 @@
-# Copyright (C) 2014 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.
-
-LOCAL_PATH:= $(call my-dir)
-
-
-## The application with a minimal main dex
-include $(CLEAR_VARS)
-
-LOCAL_STATIC_JAVA_LIBRARIES := android-support-multidex android-support-multidex-instrumentation androidx.test.rules
-LOCAL_MODULE_TAGS := tests
-
-LOCAL_SRC_FILES := $(call all-java-files-under, src)
-
-LOCAL_SDK_VERSION := 8
-
-LOCAL_PACKAGE_NAME := MultiDexLegacyAndException
-LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
-LOCAL_LICENSE_CONDITIONS := notice
-LOCAL_NOTICE_FILE  := $(LOCAL_PATH)/../../../../../NOTICE
-
-LOCAL_DEX_PREOPT := false
-
-LOCAL_JAVACFLAGS := -nowarn
-
-mainDexList:= \
-    $(call intermediates-dir-for,APPS,$(LOCAL_PACKAGE_NAME),$(LOCAL_IS_HOST_MODULE),common)/maindex.list
-
-LOCAL_DX_FLAGS := --multi-dex --main-dex-list=$(mainDexList) --minimal-main-dex
-
-LOCAL_MIN_SDK_VERSION := 8
-
-include $(BUILD_PACKAGE)
-
-$(mainDexList): $(full_classes_pre_proguard_jar) $(MAINDEXCLASSES) $(PROGUARD_DEPS)
-	$(hide) mkdir -p $(dir $@)
-	PROGUARD_HOME=$(PROGUARD_HOME) $(MAINDEXCLASSES) $< 1>$@
-	echo "com/android/multidexlegacyandexception/Test.class" >> $@
-
-$(built_dex_intermediate): $(mainDexList)
diff --git a/core/tests/hosttests/test-apps/MultiDexLegacyTestApp/Android.bp b/core/tests/hosttests/test-apps/MultiDexLegacyTestApp/Android.bp
new file mode 100644
index 0000000..c0c8aba
--- /dev/null
+++ b/core/tests/hosttests/test-apps/MultiDexLegacyTestApp/Android.bp
@@ -0,0 +1,70 @@
+// Copyright (C) 2014 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 {
+    default_applicable_licenses: ["frameworks_base_license"],
+}
+
+// The application with a minimal main dex
+android_test_helper_app {
+    name: "MultiDexLegacyTestApp",
+
+    static_libs: ["android-support-multidex"],
+
+    srcs: ["src/**/*.java"],
+
+    sdk_version: "16",
+
+    main_dex_rules: [
+        ":mainDexClassesRules",
+        "mainDexClasses.rules",
+    ],
+    dxflags: [
+        // --debug triggers the old --minimal-main-dex behavior
+        "--debug",
+    ],
+    optimize: {
+        // disable optimization to force D8 instead of R8, as R8 doesn't support
+        // --main-dex-rules.
+        enabled: false,
+    },
+
+    min_sdk_version: "16",
+}
+
+android_test_helper_app {
+    name: "MultiDexLegacyTestApp2",
+
+    static_libs: ["android-support-multidex"],
+
+    srcs: ["src/**/*.java"],
+
+    sdk_version: "16",
+
+    main_dex_rules: [
+        ":mainDexClassesRules",
+        "mainDexClasses.rules",
+    ],
+    dxflags: [
+        // --release disables the old --minimal-main-dex behavior
+        "--release",
+    ],
+    optimize: {
+        // disable optimization to force D8 instead of R8, as R8 doesn't support
+        // --main-dex-rules.
+        enabled: false,
+    },
+
+    min_sdk_version: "16",
+}
diff --git a/core/tests/hosttests/test-apps/MultiDexLegacyTestApp/Android.mk b/core/tests/hosttests/test-apps/MultiDexLegacyTestApp/Android.mk
deleted file mode 100644
index d7af2d9..0000000
--- a/core/tests/hosttests/test-apps/MultiDexLegacyTestApp/Android.mk
+++ /dev/null
@@ -1,87 +0,0 @@
-# Copyright (C) 2014 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.
-
-LOCAL_PATH:= $(call my-dir)
-
-
-## The application with a minimal main dex
-include $(CLEAR_VARS)
-
-LOCAL_STATIC_JAVA_LIBRARIES := android-support-multidex
-LOCAL_MODULE_TAGS := tests
-
-LOCAL_SRC_FILES := $(call all-java-files-under, src)
-
-LOCAL_SDK_VERSION := 8
-
-LOCAL_PACKAGE_NAME := MultiDexLegacyTestApp
-LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
-LOCAL_LICENSE_CONDITIONS := notice
-LOCAL_NOTICE_FILE  := $(LOCAL_PATH)/../../../../../NOTICE
-
-LOCAL_DEX_PREOPT := false
-
-LOCAL_EMMA_INSTRUMENT := false
-
-mainDexList:= \
-	$(call intermediates-dir-for,APPS,$(LOCAL_PACKAGE_NAME),$(LOCAL_IS_HOST_MODULE),common)/maindex.list
-
-LOCAL_DX_FLAGS := --multi-dex --main-dex-list=$(mainDexList) --minimal-main-dex
-
-LOCAL_MIN_SDK_VERSION := 8
-
-include $(BUILD_PACKAGE)
-
-$(mainDexList): $(full_classes_pre_proguard_jar) $(MAINDEXCLASSES) $(PROGUARD_DEPS)
-	$(hide) mkdir -p $(dir $@)
-	PROGUARD_HOME=$(PROGUARD_HOME) $(MAINDEXCLASSES) $< 1>$@
-	echo "com/android/multidexlegacytestapp/Test.class" >> $@
-
-$(built_dex_intermediate): $(mainDexList)
-
-## The application with a full main dex
-include $(CLEAR_VARS)
-
-LOCAL_STATIC_JAVA_LIBRARIES := android-support-multidex
-
-LOCAL_MODULE_TAGS := tests
-
-LOCAL_SRC_FILES := $(call all-java-files-under, src)
-
-LOCAL_SDK_VERSION := 8
-
-LOCAL_PACKAGE_NAME := MultiDexLegacyTestApp2
-LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
-LOCAL_LICENSE_CONDITIONS := notice
-LOCAL_NOTICE_FILE  := $(LOCAL_PATH)/../../../../../NOTICE
-
-LOCAL_DEX_PREOPT := false
-
-LOCAL_EMMA_INSTRUMENT := false
-
-mainDexList2:= \
-	$(call intermediates-dir-for,APPS,$(LOCAL_PACKAGE_NAME),$(LOCAL_IS_HOST_MODULE),common)/maindex.list
-
-LOCAL_DX_FLAGS := --multi-dex --main-dex-list=$(mainDexList2)
-
-LOCAL_MIN_SDK_VERSION := 8
-
-include $(BUILD_PACKAGE)
-
-$(mainDexList2): $(full_classes_pre_proguard_jar) $(MAINDEXCLASSES) $(PROGUARD_DEPS)
-	$(hide) mkdir -p $(dir $@)
-	PROGUARD_HOME=$(PROGUARD_HOME) $(MAINDEXCLASSES) $< 1>$@
-	echo "com/android/multidexlegacytestapp/Test.class" >> $@
-
-$(built_dex_intermediate): $(mainDexList2)
diff --git a/core/tests/hosttests/test-apps/MultiDexLegacyTestApp/mainDexClasses.rules b/core/tests/hosttests/test-apps/MultiDexLegacyTestApp/mainDexClasses.rules
new file mode 100644
index 0000000..91e6ddb
--- /dev/null
+++ b/core/tests/hosttests/test-apps/MultiDexLegacyTestApp/mainDexClasses.rules
@@ -0,0 +1 @@
+-keep class com.android.multidexlegacytestapp.Test
diff --git a/core/tests/hosttests/test-apps/MultiDexLegacyTestAppTests/Android.bp b/core/tests/hosttests/test-apps/MultiDexLegacyTestAppTests/Android.bp
new file mode 100644
index 0000000..fe29416
--- /dev/null
+++ b/core/tests/hosttests/test-apps/MultiDexLegacyTestAppTests/Android.bp
@@ -0,0 +1,34 @@
+// Copyright (C) 2014 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 {
+    default_applicable_licenses: ["frameworks_base_license"],
+}
+
+// The application with a minimal main dex
+android_test {
+    name: "MultiDexLegacyTestAppTests",
+
+    static_libs: ["android-support-multidex-instrumentation"],
+
+    srcs: ["src/**/*.java"],
+
+    sdk_version: "16",
+
+    javacflags: ["-nowarn"],
+
+    min_sdk_version: "16",
+
+    instrumentation_for: "MultiDexLegacyTestApp",
+}
diff --git a/core/tests/hosttests/test-apps/MultiDexLegacyTestAppTests/Android.mk b/core/tests/hosttests/test-apps/MultiDexLegacyTestAppTests/Android.mk
deleted file mode 100644
index 236c740..0000000
--- a/core/tests/hosttests/test-apps/MultiDexLegacyTestAppTests/Android.mk
+++ /dev/null
@@ -1,43 +0,0 @@
-# Copyright (C) 2014 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.
-
-LOCAL_PATH:= $(call my-dir)
-
-
-## The application with a minimal main dex
-include $(CLEAR_VARS)
-
-LOCAL_STATIC_JAVA_LIBRARIES := android-support-multidex-instrumentation
-LOCAL_MODULE_TAGS := tests
-
-LOCAL_SRC_FILES := $(call all-java-files-under, src)
-
-LOCAL_SDK_VERSION := 8
-
-LOCAL_PACKAGE_NAME := MultiDexLegacyTestAppTests
-LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
-LOCAL_LICENSE_CONDITIONS := notice
-LOCAL_NOTICE_FILE  := $(LOCAL_PATH)/../../../../../NOTICE
-
-LOCAL_DEX_PREOPT := false
-
-LOCAL_JAVACFLAGS := -nowarn
-
-LOCAL_MIN_SDK_VERSION := 8
-
-LOCAL_INSTRUMENTATION_FOR := MultiDexLegacyTestApp
-
-LOCAL_JACK_FLAGS := -D jack.dex.output.policy=minimal-multidex -D jack.dex.output.multidex.legacy=true
-
-include $(BUILD_PACKAGE)
diff --git a/core/tests/hosttests/test-apps/MultiDexLegacyTestAppTests2/Android.bp b/core/tests/hosttests/test-apps/MultiDexLegacyTestAppTests2/Android.bp
new file mode 100644
index 0000000..c558153
--- /dev/null
+++ b/core/tests/hosttests/test-apps/MultiDexLegacyTestAppTests2/Android.bp
@@ -0,0 +1,57 @@
+// Copyright (C) 2014 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 {
+    default_applicable_licenses: ["frameworks_base_license"],
+}
+
+// The tests with only one dex
+android_test {
+    name: "MultiDexLegacyTestAppTests2",
+
+    static_libs: [
+        "android-support-multidex-instrumentation",
+        "androidx.test.rules",
+    ],
+
+    srcs: ["src/**/*.java"],
+
+    sdk_version: "16",
+
+    javacflags: ["-nowarn"],
+
+    min_sdk_version: "16",
+
+    instrumentation_for: "MultiDexLegacyTestApp",
+}
+
+// The tests with a minimal main dex
+android_test {
+    name: "MultiDexLegacyTestAppTests2-multidex",
+
+    static_libs: [
+        "android-support-multidex-instrumentation",
+        "androidx.test.rules",
+    ],
+
+    srcs: ["src/**/*.java"],
+
+    sdk_version: "16",
+
+    javacflags: ["-nowarn"],
+
+    min_sdk_version: "16",
+
+    instrumentation_for: "MultiDexLegacyTestApp",
+}
diff --git a/core/tests/hosttests/test-apps/MultiDexLegacyTestAppTests2/Android.mk b/core/tests/hosttests/test-apps/MultiDexLegacyTestAppTests2/Android.mk
deleted file mode 100644
index 6f6ccfe..0000000
--- a/core/tests/hosttests/test-apps/MultiDexLegacyTestAppTests2/Android.mk
+++ /dev/null
@@ -1,69 +0,0 @@
-# Copyright (C) 2014 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.
-
-LOCAL_PATH:= $(call my-dir)
-
-
-## The tests with only one dex
-include $(CLEAR_VARS)
-
-LOCAL_STATIC_JAVA_LIBRARIES := android-support-multidex-instrumentation androidx.test.rules
-LOCAL_MODULE_TAGS := tests
-
-LOCAL_SRC_FILES := $(call all-java-files-under, src)
-
-LOCAL_SDK_VERSION := current
-
-LOCAL_PACKAGE_NAME := MultiDexLegacyTestAppTests2
-LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
-LOCAL_LICENSE_CONDITIONS := notice
-LOCAL_NOTICE_FILE  := $(LOCAL_PATH)/../../../../../NOTICE
-
-LOCAL_DEX_PREOPT := false
-
-LOCAL_JAVACFLAGS := -nowarn
-
-LOCAL_MIN_SDK_VERSION := 8
-
-LOCAL_INSTRUMENTATION_FOR := MultiDexLegacyTestApp
-
-include $(BUILD_PACKAGE)
-
-
-## The tests with a minimal main dex
-include $(CLEAR_VARS)
-
-LOCAL_STATIC_JAVA_LIBRARIES := android-support-multidex-instrumentation androidx.test.rules
-LOCAL_MODULE_TAGS := tests
-
-LOCAL_SRC_FILES := $(call all-java-files-under, src)
-
-LOCAL_SDK_VERSION := 8
-
-LOCAL_PACKAGE_NAME := MultiDexLegacyTestAppTests2-multidex
-LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
-LOCAL_LICENSE_CONDITIONS := notice
-LOCAL_NOTICE_FILE  := $(LOCAL_PATH)/../../../../../NOTICE
-
-LOCAL_DEX_PREOPT := false
-
-LOCAL_JAVACFLAGS := -nowarn
-
-LOCAL_MIN_SDK_VERSION := 8
-
-LOCAL_INSTRUMENTATION_FOR := MultiDexLegacyTestApp
-
-LOCAL_JACK_FLAGS := -D jack.dex.output.policy=minimal-multidex -D jack.dex.output.multidex.legacy=true
-
-include $(BUILD_PACKAGE)
diff --git a/core/tests/hosttests/test-apps/MultiDexLegacyTestAppWithCorruptedDex/Android.bp b/core/tests/hosttests/test-apps/MultiDexLegacyTestAppWithCorruptedDex/Android.bp
new file mode 100644
index 0000000..15bc4ef
--- /dev/null
+++ b/core/tests/hosttests/test-apps/MultiDexLegacyTestAppWithCorruptedDex/Android.bp
@@ -0,0 +1,48 @@
+// Copyright (C) 2014 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 {
+    default_applicable_licenses: ["frameworks_base_license"],
+}
+
+android_test_helper_app {
+    name: "MultiDexLegacyTestApp_without_corrupted",
+
+    srcs: ["src/**/*.java"],
+
+    sdk_version: "16",
+
+    static_libs: ["android-support-multidex"],
+}
+
+java_genrule {
+    name: "MultiDexLegacyTestApp_genrule",
+    srcs: [
+        ":MultiDexLegacyTestApp_without_corrupted",
+    ],
+    tools: [
+        "soong_zip",
+        "merge_zips",
+    ],
+    out: ["MultiDexLegacyTestApp_with_corrupted.apk"],
+    cmd: "touch $(genDir)/classes2.dex &&" +
+        " $(location soong_zip) -o $(genDir)/corrupted.zip -j -f $(genDir)/classes2.dex &&" +
+        " $(location merge_zips) $(out) $(location :MultiDexLegacyTestApp_without_corrupted) $(genDir)/corrupted.zip",
+}
+
+android_test_import {
+    name: "MultiDexLegacyTestApp_corrupted",
+    apk: ":MultiDexLegacyTestApp_genrule",
+    default_dev_cert: true,
+}
diff --git a/core/tests/hosttests/test-apps/MultiDexLegacyTestAppWithCorruptedDex/Android.mk b/core/tests/hosttests/test-apps/MultiDexLegacyTestAppWithCorruptedDex/Android.mk
deleted file mode 100644
index 33a46ea..0000000
--- a/core/tests/hosttests/test-apps/MultiDexLegacyTestAppWithCorruptedDex/Android.mk
+++ /dev/null
@@ -1,41 +0,0 @@
-# Copyright (C) 2014 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.
-
-LOCAL_PATH:= $(call my-dir)
-
-include $(CLEAR_VARS)
-
-LOCAL_MODULE_TAGS := tests
-
-LOCAL_SRC_FILES := $(call all-java-files-under, src)
-
-LOCAL_SDK_VERSION := 18
-
-LOCAL_PACKAGE_NAME := MultiDexLegacyTestApp_corrupted
-LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
-LOCAL_LICENSE_CONDITIONS := notice
-LOCAL_NOTICE_FILE  := $(LOCAL_PATH)/../../../../../NOTICE
-
-LOCAL_STATIC_JAVA_LIBRARIES := android-support-multidex
-
-LOCAL_DEX_PREOPT := false
-
-include $(BUILD_PACKAGE)
-
-corrupted_classes2_dex := $(dir $(built_dex))/classes2.dex
-
-$(corrupted_classes2_dex): $(built_dex)
-	$(hide) touch $@
-
-$(LOCAL_BUILT_MODULE): $(corrupted_classes2_dex)
diff --git a/core/tests/hosttests/test-apps/MultiDexLegacyTestServices/Android.bp b/core/tests/hosttests/test-apps/MultiDexLegacyTestServices/Android.bp
new file mode 100644
index 0000000..de0657f
--- /dev/null
+++ b/core/tests/hosttests/test-apps/MultiDexLegacyTestServices/Android.bp
@@ -0,0 +1,38 @@
+// Copyright (C) 2014 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 {
+    default_applicable_licenses: ["frameworks_base_license"],
+}
+
+android_test {
+    name: "MultiDexLegacyTestServices",
+
+    srcs: ["src/**/*.java"],
+
+    sdk_version: "16",
+
+    static_libs: ["android-support-multidex"],
+
+    main_dex_rules: [":mainDexClassesRules"],
+    dxflags: [
+        // --debug triggers the old --minimal-main-dex behavior
+        "--debug",
+    ],
+    optimize: {
+        // disable optimization to force D8 instead of R8, as R8 doesn't support
+        // --main-dex-rules.
+        enabled: false,
+    },
+}
diff --git a/core/tests/hosttests/test-apps/MultiDexLegacyTestServices/Android.mk b/core/tests/hosttests/test-apps/MultiDexLegacyTestServices/Android.mk
deleted file mode 100644
index efc0688..0000000
--- a/core/tests/hosttests/test-apps/MultiDexLegacyTestServices/Android.mk
+++ /dev/null
@@ -1,47 +0,0 @@
-# Copyright (C) 2014 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.
-
-LOCAL_PATH:= $(call my-dir)
-
-include $(CLEAR_VARS)
-
-LOCAL_MODULE_TAGS := tests
-
-LOCAL_SRC_FILES := $(call all-java-files-under, src)
-
-LOCAL_SDK_VERSION := 9
-
-LOCAL_PACKAGE_NAME := MultiDexLegacyTestServices
-LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
-LOCAL_LICENSE_CONDITIONS := notice
-LOCAL_NOTICE_FILE  := $(LOCAL_PATH)/../../../../../NOTICE
-
-LOCAL_STATIC_JAVA_LIBRARIES := android-support-multidex
-
-mainDexList:= \
-	$(call intermediates-dir-for,APPS,$(LOCAL_PACKAGE_NAME),$(LOCAL_IS_HOST_MODULE),common)/maindex.list
-
-LOCAL_DX_FLAGS := --multi-dex --main-dex-list=$(mainDexList) --minimal-main-dex
-
-LOCAL_DEX_PREOPT := false
-
-LOCAL_EMMA_INSTRUMENT := false
-
-include $(BUILD_PACKAGE)
-
-$(mainDexList): $(full_classes_pre_proguard_jar) $(MAINDEXCLASSES) $(PROGUARD_DEPS)
-	$(hide) mkdir -p $(dir $@)
-	PROGUARD_HOME=$(PROGUARD_HOME) $(MAINDEXCLASSES) $< 1>$@
-
-$(built_dex_intermediate): $(mainDexList)
diff --git a/core/tests/hosttests/test-apps/MultiDexLegacyTestServicesTests/Android.bp b/core/tests/hosttests/test-apps/MultiDexLegacyTestServicesTests/Android.bp
index 56f10fe..b62b25c 100644
--- a/core/tests/hosttests/test-apps/MultiDexLegacyTestServicesTests/Android.bp
+++ b/core/tests/hosttests/test-apps/MultiDexLegacyTestServicesTests/Android.bp
@@ -24,7 +24,7 @@
 android_test {
     name: "MultiDexLegacyTestServicesTests",
     srcs: ["src/**/*.java"],
-    sdk_version: "9",
+    sdk_version: "16",
     dex_preopt: {
         enabled: false,
     },
diff --git a/core/tests/hosttests/test-apps/MultiDexLegacyTestServicesTests2/Android.bp b/core/tests/hosttests/test-apps/MultiDexLegacyTestServicesTests2/Android.bp
new file mode 100644
index 0000000..75c753c
--- /dev/null
+++ b/core/tests/hosttests/test-apps/MultiDexLegacyTestServicesTests2/Android.bp
@@ -0,0 +1,28 @@
+// Copyright (C) 2014 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 {
+    default_applicable_licenses: ["frameworks_base_license"],
+}
+
+android_test {
+    name: "MultiDexLegacyTestServicesTests2",
+
+    srcs: ["src/**/*.java"],
+
+    libs: ["android-support-multidex"],
+    static_libs: ["androidx.test.rules"],
+
+    sdk_version: "16",
+}
diff --git a/core/tests/hosttests/test-apps/MultiDexLegacyTestServicesTests2/Android.mk b/core/tests/hosttests/test-apps/MultiDexLegacyTestServicesTests2/Android.mk
deleted file mode 100644
index 3920fd6..0000000
--- a/core/tests/hosttests/test-apps/MultiDexLegacyTestServicesTests2/Android.mk
+++ /dev/null
@@ -1,35 +0,0 @@
-# Copyright (C) 2014 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.
-
-LOCAL_PATH:= $(call my-dir)
-
-include $(CLEAR_VARS)
-
-LOCAL_MODULE_TAGS := tests
-
-LOCAL_SRC_FILES := $(call all-java-files-under, src)
-
-LOCAL_PACKAGE_NAME := MultiDexLegacyTestServicesTests2
-LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
-LOCAL_LICENSE_CONDITIONS := notice
-LOCAL_NOTICE_FILE  := $(LOCAL_PATH)/../../../../../NOTICE
-
-LOCAL_JAVA_LIBRARIES := android-support-multidex
-LOCAL_STATIC_JAVA_LIBRARIES := androidx.test.rules
-
-LOCAL_SDK_VERSION := 9
-
-LOCAL_DEX_PREOPT := false
-
-include $(BUILD_PACKAGE)
diff --git a/core/tests/hosttests/test-apps/MultiDexLegacyVersionedTestApp_v1/Android.bp b/core/tests/hosttests/test-apps/MultiDexLegacyVersionedTestApp_v1/Android.bp
new file mode 100644
index 0000000..23c62dc
--- /dev/null
+++ b/core/tests/hosttests/test-apps/MultiDexLegacyVersionedTestApp_v1/Android.bp
@@ -0,0 +1,41 @@
+// Copyright (C) 2014 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 {
+    default_applicable_licenses: ["frameworks_base_license"],
+}
+
+android_test_helper_app {
+    name: "MultiDexLegacyVersionedTestApp_v1",
+
+    srcs: ["src/**/*.java"],
+
+    sdk_version: "16",
+
+    static_libs: ["android-support-multidex"],
+
+    main_dex_rules: [
+        ":mainDexClassesRules",
+        "mainDexClasses.rules",
+    ],
+    dxflags: [
+        // --debug triggers the old --minimal-main-dex behavior
+        "--debug",
+    ],
+    optimize: {
+        // disable optimization to force D8 instead of R8, as R8 doesn't support
+        // --main-dex-rules.
+        enabled: false,
+    },
+}
diff --git a/core/tests/hosttests/test-apps/MultiDexLegacyVersionedTestApp_v1/Android.mk b/core/tests/hosttests/test-apps/MultiDexLegacyVersionedTestApp_v1/Android.mk
deleted file mode 100644
index 2323ad9..0000000
--- a/core/tests/hosttests/test-apps/MultiDexLegacyVersionedTestApp_v1/Android.mk
+++ /dev/null
@@ -1,48 +0,0 @@
-# Copyright (C) 2014 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.
-
-LOCAL_PATH:= $(call my-dir)
-
-include $(CLEAR_VARS)
-
-LOCAL_MODULE_TAGS := tests
-
-LOCAL_SRC_FILES := $(call all-java-files-under, src)
-
-LOCAL_SDK_VERSION := 9
-
-LOCAL_PACKAGE_NAME := MultiDexLegacyVersionedTestApp_v1
-LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
-LOCAL_LICENSE_CONDITIONS := notice
-LOCAL_NOTICE_FILE  := $(LOCAL_PATH)/../../../../../NOTICE
-
-LOCAL_STATIC_JAVA_LIBRARIES := android-support-multidex
-
-LOCAL_DEX_PREOPT := false
-
-LOCAL_EMMA_INSTRUMENT := false
-
-mainDexList:= \
-	$(call intermediates-dir-for,APPS,$(LOCAL_PACKAGE_NAME),$(LOCAL_IS_HOST_MODULE),common)/maindex.list
-
-LOCAL_DX_FLAGS := --multi-dex --main-dex-list=$(mainDexList) --minimal-main-dex
-
-include $(BUILD_PACKAGE)
-
-$(mainDexList): $(full_classes_pre_proguard_jar) $(MAINDEXCLASSES) $(PROGUARD_DEPS)
-	$(hide) mkdir -p $(dir $@)
-	PROGUARD_HOME=$(PROGUARD_HOME) $(MAINDEXCLASSES) $< 1>$@
-	echo "com/android/framework/multidexlegacyversionedtestapp/MultiDexUpdateTest.class" >> $@
-
-$(built_dex_intermediate): $(mainDexList)
diff --git a/core/tests/hosttests/test-apps/MultiDexLegacyVersionedTestApp_v1/mainDexClasses.rules b/core/tests/hosttests/test-apps/MultiDexLegacyVersionedTestApp_v1/mainDexClasses.rules
new file mode 100644
index 0000000..1cdf3af
--- /dev/null
+++ b/core/tests/hosttests/test-apps/MultiDexLegacyVersionedTestApp_v1/mainDexClasses.rules
@@ -0,0 +1 @@
+-keep class com.android.framework.multidexlegacyversionedtestapp.MultiDexUpdateTest
diff --git a/core/tests/hosttests/test-apps/MultiDexLegacyVersionedTestApp_v2/Android.bp b/core/tests/hosttests/test-apps/MultiDexLegacyVersionedTestApp_v2/Android.bp
new file mode 100644
index 0000000..6cd3df7
--- /dev/null
+++ b/core/tests/hosttests/test-apps/MultiDexLegacyVersionedTestApp_v2/Android.bp
@@ -0,0 +1,41 @@
+// Copyright (C) 2014 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 {
+    default_applicable_licenses: ["frameworks_base_license"],
+}
+
+android_test_helper_app {
+    name: "MultiDexLegacyVersionedTestApp_v2",
+
+    srcs: ["src/**/*.java"],
+
+    sdk_version: "16",
+
+    static_libs: ["android-support-multidex"],
+
+    main_dex_rules: [
+        ":mainDexClassesRules",
+        "mainDexClasses.rules",
+    ],
+    dxflags: [
+        // --debug triggers the old --minimal-main-dex behavior
+        "--debug",
+    ],
+    optimize: {
+        // disable optimization to force D8 instead of R8, as R8 doesn't support
+        // --main-dex-rules.
+        enabled: false,
+    },
+}
diff --git a/core/tests/hosttests/test-apps/MultiDexLegacyVersionedTestApp_v2/Android.mk b/core/tests/hosttests/test-apps/MultiDexLegacyVersionedTestApp_v2/Android.mk
deleted file mode 100644
index 79a5906..0000000
--- a/core/tests/hosttests/test-apps/MultiDexLegacyVersionedTestApp_v2/Android.mk
+++ /dev/null
@@ -1,48 +0,0 @@
-# Copyright (C) 2014 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.
-
-LOCAL_PATH:= $(call my-dir)
-
-include $(CLEAR_VARS)
-
-LOCAL_MODULE_TAGS := tests
-
-LOCAL_SRC_FILES := $(call all-java-files-under, src)
-
-LOCAL_SDK_VERSION := 9
-
-LOCAL_PACKAGE_NAME := MultiDexLegacyVersionedTestApp_v2
-LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
-LOCAL_LICENSE_CONDITIONS := notice
-LOCAL_NOTICE_FILE  := $(LOCAL_PATH)/../../../../../NOTICE
-
-LOCAL_STATIC_JAVA_LIBRARIES := android-support-multidex
-
-LOCAL_DEX_PREOPT := false
-
-LOCAL_EMMA_INSTRUMENT := false
-
-mainDexList:= \
-	$(call intermediates-dir-for,APPS,$(LOCAL_PACKAGE_NAME),$(LOCAL_IS_HOST_MODULE),common)/maindex.list
-
-LOCAL_DX_FLAGS := --multi-dex --main-dex-list=$(mainDexList) --minimal-main-dex
-
-include $(BUILD_PACKAGE)
-
-$(mainDexList): $(full_classes_pre_proguard_jar) $(MAINDEXCLASSES) $(PROGUARD_DEPS)
-	$(hide) mkdir -p $(dir $@)
-	PROGUARD_HOME=$(PROGUARD_HOME) $(MAINDEXCLASSES) $< 1>$@
-	echo "com/android/framework/multidexlegacyversionedtestapp/MultiDexUpdateTest.class" >> $@
-
-$(built_dex_intermediate): $(mainDexList)
diff --git a/core/tests/hosttests/test-apps/MultiDexLegacyVersionedTestApp_v2/mainDexClasses.rules b/core/tests/hosttests/test-apps/MultiDexLegacyVersionedTestApp_v2/mainDexClasses.rules
new file mode 100644
index 0000000..1cdf3af
--- /dev/null
+++ b/core/tests/hosttests/test-apps/MultiDexLegacyVersionedTestApp_v2/mainDexClasses.rules
@@ -0,0 +1 @@
+-keep class com.android.framework.multidexlegacyversionedtestapp.MultiDexUpdateTest
diff --git a/core/tests/hosttests/test-apps/MultiDexLegacyVersionedTestApp_v3/Android.bp b/core/tests/hosttests/test-apps/MultiDexLegacyVersionedTestApp_v3/Android.bp
new file mode 100644
index 0000000..34dba40
--- /dev/null
+++ b/core/tests/hosttests/test-apps/MultiDexLegacyVersionedTestApp_v3/Android.bp
@@ -0,0 +1,41 @@
+// Copyright (C) 2014 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 {
+    default_applicable_licenses: ["frameworks_base_license"],
+}
+
+android_test_helper_app {
+    name: "MultiDexLegacyVersionedTestApp_v3",
+
+    srcs: ["src/**/*.java"],
+
+    sdk_version: "16",
+
+    static_libs: ["android-support-multidex"],
+
+    main_dex_rules: [
+        ":mainDexClassesRules",
+        "mainDexClasses.rules",
+    ],
+    dxflags: [
+        // --debug triggers the old --minimal-main-dex behavior
+        "--debug",
+    ],
+    optimize: {
+        // disable optimization to force D8 instead of R8, as R8 doesn't support
+        // --main-dex-rules.
+        enabled: false,
+    },
+}
diff --git a/core/tests/hosttests/test-apps/MultiDexLegacyVersionedTestApp_v3/Android.mk b/core/tests/hosttests/test-apps/MultiDexLegacyVersionedTestApp_v3/Android.mk
deleted file mode 100644
index 521bad0..0000000
--- a/core/tests/hosttests/test-apps/MultiDexLegacyVersionedTestApp_v3/Android.mk
+++ /dev/null
@@ -1,48 +0,0 @@
-# Copyright (C) 2014 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.
-
-LOCAL_PATH:= $(call my-dir)
-
-include $(CLEAR_VARS)
-
-LOCAL_MODULE_TAGS := tests
-
-LOCAL_SRC_FILES := $(call all-java-files-under, src)
-
-LOCAL_SDK_VERSION := 9
-
-LOCAL_PACKAGE_NAME := MultiDexLegacyVersionedTestApp_v3
-LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
-LOCAL_LICENSE_CONDITIONS := notice
-LOCAL_NOTICE_FILE  := $(LOCAL_PATH)/../../../../../NOTICE
-
-LOCAL_STATIC_JAVA_LIBRARIES := android-support-multidex
-
-mainDexList:= \
-	$(call intermediates-dir-for,APPS,$(LOCAL_PACKAGE_NAME),$(LOCAL_IS_HOST_MODULE),common)/maindex.list
-
-LOCAL_DEX_PREOPT := false
-
-LOCAL_EMMA_INSTRUMENT := false
-
-LOCAL_DX_FLAGS := --multi-dex --main-dex-list=$(mainDexList) --minimal-main-dex
-
-include $(BUILD_PACKAGE)
-
-$(mainDexList): $(full_classes_pre_proguard_jar) $(MAINDEXCLASSES) $(PROGUARD_DEPS)
-	$(hide) mkdir -p $(dir $@)
-	PROGUARD_HOME=$(PROGUARD_HOME) $(MAINDEXCLASSES) $< 1>$@
-	echo "com/android/framework/multidexlegacyversionedtestapp/MultiDexUpdateTest.class" >> $@
-
-$(built_dex_intermediate): $(mainDexList)
diff --git a/core/tests/hosttests/test-apps/MultiDexLegacyVersionedTestApp_v3/mainDexClasses.rules b/core/tests/hosttests/test-apps/MultiDexLegacyVersionedTestApp_v3/mainDexClasses.rules
new file mode 100644
index 0000000..1cdf3af
--- /dev/null
+++ b/core/tests/hosttests/test-apps/MultiDexLegacyVersionedTestApp_v3/mainDexClasses.rules
@@ -0,0 +1 @@
+-keep class com.android.framework.multidexlegacyversionedtestapp.MultiDexUpdateTest
diff --git a/identity/java/android/security/identity/IdentityCredential.java b/identity/java/android/security/identity/IdentityCredential.java
index 8f175bb..1e68585 100644
--- a/identity/java/android/security/identity/IdentityCredential.java
+++ b/identity/java/android/security/identity/IdentityCredential.java
@@ -160,7 +160,7 @@
      * not the case, the {@link SessionTranscriptMismatchException} exception is thrown.
      *
      * <p>If not {@code null} the {@code requestMessage} parameter must contain data for the request
-     * from the verifier. The content can be defined in the way appropriate for the credential, byt
+     * from the verifier. The content can be defined in the way appropriate for the credential, but
      * there are three requirements that must be met to work with this API:
      * <ul>
      * <li>The content must be a CBOR-encoded structure.</li>
@@ -205,9 +205,9 @@
      * must appear somewhere in {@code sessionTranscript} and ditto for the 32 bytes for the Y
      * coordinate.
      *
-     * <p>If {@code readerAuth} is not {@code null} it must be the bytes of a {@code COSE_Sign1}
-     * structure as defined in RFC 8152. For the payload nil shall be used and the
-     * detached payload is the ReaderAuthenticationBytes CBOR described below.
+     * <p>If {@code readerSignature} is not {@code null} it must be the bytes of a
+     * {@code COSE_Sign1} structure as defined in RFC 8152. For the payload nil shall be used and
+     * the detached payload is the ReaderAuthenticationBytes CBOR described below.
      * <pre>
      *     ReaderAuthentication = [
      *       "ReaderAuthentication",
diff --git a/libs/hwui/jni/PaintFilter.cpp b/libs/hwui/jni/PaintFilter.cpp
index ec115b4..86d4742 100644
--- a/libs/hwui/jni/PaintFilter.cpp
+++ b/libs/hwui/jni/PaintFilter.cpp
@@ -74,7 +74,7 @@
     result |= RegisterMethodsOrDie(env, "android/graphics/PaintFlagsDrawFilter", paintflags_methods,
                                    NELEM(paintflags_methods));
 
-    return 0;
+    return result;
 }
 
 }
diff --git a/media/java/android/media/MediaCodecInfo.java b/media/java/android/media/MediaCodecInfo.java
index 9657b25e..c7c503d 100644
--- a/media/java/android/media/MediaCodecInfo.java
+++ b/media/java/android/media/MediaCodecInfo.java
@@ -2178,12 +2178,6 @@
                 if (size == null || size.getWidth() * size.getHeight() <= 0) {
                     continue;
                 }
-                if (size.getWidth() > SIZE_RANGE.getUpper()
-                        || size.getHeight() > SIZE_RANGE.getUpper()) {
-                    size = new Size(
-                            Math.min(size.getWidth(), SIZE_RANGE.getUpper()),
-                            Math.min(size.getHeight(), SIZE_RANGE.getUpper()));
-                }
                 Range<Long> range = Utils.parseLongRange(map.get(key), null);
                 if (range == null || range.getLower() < 0 || range.getUpper() < 0) {
                     continue;
diff --git a/media/java/android/media/MediaMetadataRetriever.java b/media/java/android/media/MediaMetadataRetriever.java
index 559a61d..0f9e89a 100644
--- a/media/java/android/media/MediaMetadataRetriever.java
+++ b/media/java/android/media/MediaMetadataRetriever.java
@@ -900,7 +900,7 @@
     private @NonNull List<Bitmap> getFramesAtIndexInternal(
             int frameIndex, int numFrames, @Nullable BitmapParams params) {
         if (!"yes".equals(extractMetadata(MediaMetadataRetriever.METADATA_KEY_HAS_VIDEO))) {
-            throw new IllegalStateException("Does not contail video or image sequences");
+            throw new IllegalStateException("Does not contain video or image sequences");
         }
         int frameCount = Integer.parseInt(
                 extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_FRAME_COUNT));
@@ -1018,7 +1018,7 @@
 
     private Bitmap getImageAtIndexInternal(int imageIndex, @Nullable BitmapParams params) {
         if (!"yes".equals(extractMetadata(MediaMetadataRetriever.METADATA_KEY_HAS_IMAGE))) {
-            throw new IllegalStateException("Does not contail still images");
+            throw new IllegalStateException("Does not contain still images");
         }
 
         String imageCount = extractMetadata(MediaMetadataRetriever.METADATA_KEY_IMAGE_COUNT);
diff --git a/media/jni/audioeffect/android_media_AudioEffect.cpp b/media/jni/audioeffect/android_media_AudioEffect.cpp
index 0d53ab1..2691983 100644
--- a/media/jni/audioeffect/android_media_AudioEffect.cpp
+++ b/media/jni/audioeffect/android_media_AudioEffect.cpp
@@ -14,8 +14,8 @@
  * limitations under the License.
  */
 
-
 #include <stdio.h>
+#include <unordered_set>
 
 //#define LOG_NDEBUG 0
 #define LOG_TAG "AudioEffects-JNI"
@@ -58,21 +58,14 @@
 struct effect_callback_cookie {
     jclass      audioEffect_class;  // AudioEffect class
     jobject     audioEffect_ref;    // AudioEffect object instance
- };
-
-// ----------------------------------------------------------------------------
-class AudioEffectJniStorage {
-    public:
-        effect_callback_cookie mCallbackData;
-
-    AudioEffectJniStorage() {
-    }
-
-    ~AudioEffectJniStorage() {
-    }
-
+    bool        busy;
+    Condition   cond;
 };
 
+// ----------------------------------------------------------------------------
+struct AudioEffectJniStorage {
+    effect_callback_cookie mCallbackData{};
+};
 
 jint AudioEffectJni::translateNativeErrorToJava(int code) {
     switch(code) {
@@ -101,6 +94,7 @@
 }
 
 static Mutex sLock;
+static std::unordered_set<effect_callback_cookie*> sAudioEffectCallBackCookies;
 
 // ----------------------------------------------------------------------------
 static void effectCallback(int event, void* user, void *info) {
@@ -121,7 +115,13 @@
         ALOGW("effectCallback error user %p, env %p", user, env);
         return;
     }
-
+    {
+        Mutex::Autolock l(sLock);
+        if (sAudioEffectCallBackCookies.count(callbackInfo) == 0) {
+            return;
+        }
+        callbackInfo->busy = true;
+    }
     ALOGV("effectCallback: callbackInfo %p, audioEffect_ref %p audioEffect_class %p",
             callbackInfo,
             callbackInfo->audioEffect_ref,
@@ -188,6 +188,11 @@
         env->ExceptionDescribe();
         env->ExceptionClear();
     }
+    {
+        Mutex::Autolock l(sLock);
+        callbackInfo->busy = false;
+        callbackInfo->cond.broadcast();
+    }
 }
 
 // ----------------------------------------------------------------------------
@@ -396,6 +401,10 @@
         setAudioEffect(env, thiz, lpAudioEffect);
     }
 
+    {
+        Mutex::Autolock l(sLock);
+        sAudioEffectCallBackCookies.insert(&lpJniStorage->mCallbackData);
+    }
     env->SetLongField(thiz, fields.fidJniData, (jlong)lpJniStorage);
 
     return (jint) AUDIOEFFECT_SUCCESS;
@@ -427,6 +436,7 @@
 
 
 // ----------------------------------------------------------------------------
+#define CALLBACK_COND_WAIT_TIMEOUT_MS 1000
 static void android_media_AudioEffect_native_release(JNIEnv *env,  jobject thiz) {
     sp<AudioEffect> lpAudioEffect = setAudioEffect(env, thiz, 0);
     if (lpAudioEffect == 0) {
@@ -442,7 +452,17 @@
     env->SetLongField(thiz, fields.fidJniData, 0);
 
     if (lpJniStorage) {
-        ALOGV("deleting pJniStorage: %p\n", lpJniStorage);
+        Mutex::Autolock l(sLock);
+        effect_callback_cookie *lpCookie = &lpJniStorage->mCallbackData;
+        ALOGV("deleting lpJniStorage: %p\n", lpJniStorage);
+        sAudioEffectCallBackCookies.erase(lpCookie);
+        while (lpCookie->busy) {
+            if (lpCookie->cond.waitRelative(sLock,
+                                            milliseconds(CALLBACK_COND_WAIT_TIMEOUT_MS)) !=
+                                                    NO_ERROR) {
+                break;
+            }
+        }
         env->DeleteGlobalRef(lpJniStorage->mCallbackData.audioEffect_class);
         env->DeleteGlobalRef(lpJniStorage->mCallbackData.audioEffect_ref);
         delete lpJniStorage;
diff --git a/media/jni/audioeffect/android_media_Visualizer.cpp b/media/jni/audioeffect/android_media_Visualizer.cpp
index 4c5970a..609fafe 100644
--- a/media/jni/audioeffect/android_media_Visualizer.cpp
+++ b/media/jni/audioeffect/android_media_Visualizer.cpp
@@ -15,6 +15,7 @@
  */
 
 #include <stdio.h>
+#include <unordered_set>
 
 //#define LOG_NDEBUG 0
 #define LOG_TAG "visualizers-JNI"
@@ -63,6 +64,12 @@
     jclass      visualizer_class;  // Visualizer class
     jobject     visualizer_ref;    // Visualizer object instance
 
+    // 'busy_count' and 'cond' together with 'sLock' are used to serialize
+    // concurrent access to the callback cookie from 'setup'/'release'
+    // and the callback.
+    int         busy_count;
+    Condition   cond;
+
     // Lazily allocated arrays used to hold callback data provided to java
     // applications.  These arrays are allocated during the first callback and
     // reallocated when the size of the callback data changes.  Allocating on
@@ -70,14 +77,12 @@
     // reference to the provided data (they need to make a copy if they want to
     // hold onto outside of the callback scope), but it avoids GC thrash caused
     // by constantly allocating and releasing arrays to hold callback data.
+    // 'callback_data_lock' must never be held at the same time with 'sLock'.
     Mutex       callback_data_lock;
     jbyteArray  waveform_data;
     jbyteArray  fft_data;
 
-    visualizer_callback_cookie() {
-        waveform_data = NULL;
-        fft_data = NULL;
-    }
+    // Assumes use of default initialization by the client.
 
     ~visualizer_callback_cookie() {
         cleanupBuffers();
@@ -102,15 +107,8 @@
  };
 
 // ----------------------------------------------------------------------------
-class VisualizerJniStorage {
-    public:
-        visualizer_callback_cookie mCallbackData;
-
-    VisualizerJniStorage() {
-    }
-
-    ~VisualizerJniStorage() {
-    }
+struct VisualizerJniStorage {
+    visualizer_callback_cookie mCallbackData{};
 };
 
 
@@ -136,6 +134,7 @@
 }
 
 static Mutex sLock;
+static std::unordered_set<visualizer_callback_cookie*> sVisualizerCallBackCookies;
 
 // ----------------------------------------------------------------------------
 static void ensureArraySize(JNIEnv *env, jbyteArray *array, uint32_t size) {
@@ -173,11 +172,19 @@
         return;
     }
 
+    {
+        Mutex::Autolock l(sLock);
+        if (sVisualizerCallBackCookies.count(callbackInfo) == 0) {
+            return;
+        }
+        callbackInfo->busy_count++;
+    }
     ALOGV("captureCallback: callbackInfo %p, visualizer_ref %p visualizer_class %p",
             callbackInfo,
             callbackInfo->visualizer_ref,
             callbackInfo->visualizer_class);
 
+    {
     AutoMutex lock(&callbackInfo->callback_data_lock);
 
     if (waveformSize != 0 && waveform != NULL) {
@@ -219,11 +226,17 @@
                 jArray);
         }
     }
+    }  // callback_data_lock scope
 
     if (env->ExceptionCheck()) {
         env->ExceptionDescribe();
         env->ExceptionClear();
     }
+    {
+        Mutex::Autolock l(sLock);
+        callbackInfo->busy_count--;
+        callbackInfo->cond.broadcast();
+    }
 }
 
 // ----------------------------------------------------------------------------
@@ -332,16 +345,41 @@
                                                      void *info) {
     if ((event == AudioEffect::EVENT_ERROR) &&
         (*((status_t*)info) == DEAD_OBJECT)) {
-        VisualizerJniStorage* lpJniStorage = (VisualizerJniStorage*)user;
-        visualizer_callback_cookie* callbackInfo = &lpJniStorage->mCallbackData;
+        visualizer_callback_cookie* callbackInfo =
+                (visualizer_callback_cookie *)user;
         JNIEnv *env = AndroidRuntime::getJNIEnv();
 
+        if (!user || !env) {
+            ALOGW("effectCallback error user %p, env %p", user, env);
+            return;
+        }
+        {
+            Mutex::Autolock l(sLock);
+            if (sVisualizerCallBackCookies.count(callbackInfo) == 0) {
+                return;
+            }
+            callbackInfo->busy_count++;
+        }
+        ALOGV("effectCallback: callbackInfo %p, visualizer_ref %p visualizer_class %p",
+            callbackInfo,
+            callbackInfo->visualizer_ref,
+            callbackInfo->visualizer_class);
+
         env->CallStaticVoidMethod(
             callbackInfo->visualizer_class,
             fields.midPostNativeEvent,
             callbackInfo->visualizer_ref,
             NATIVE_EVENT_SERVER_DIED,
             0, NULL);
+        if (env->ExceptionCheck()) {
+            env->ExceptionDescribe();
+            env->ExceptionClear();
+        }
+        {
+            Mutex::Autolock l(sLock);
+            callbackInfo->busy_count--;
+            callbackInfo->cond.broadcast();
+        }
     }
 }
 
@@ -389,7 +427,7 @@
     }
     lpVisualizer->set(0,
                       android_media_visualizer_effect_callback,
-                      lpJniStorage,
+                      &lpJniStorage->mCallbackData,
                       (audio_session_t) sessionId);
 
     lStatus = translateError(lpVisualizer->initCheck());
@@ -410,6 +448,10 @@
 
     setVisualizer(env, thiz, lpVisualizer);
 
+    {
+        Mutex::Autolock l(sLock);
+        sVisualizerCallBackCookies.insert(&lpJniStorage->mCallbackData);
+    }
     env->SetLongField(thiz, fields.fidJniData, (jlong)lpJniStorage);
 
     return VISUALIZER_SUCCESS;
@@ -432,13 +474,15 @@
 }
 
 // ----------------------------------------------------------------------------
+#define CALLBACK_COND_WAIT_TIMEOUT_MS 1000
 static void android_media_visualizer_native_release(JNIEnv *env,  jobject thiz) {
-    { //limit scope so that lpVisualizer is deleted before JNI storage data.
+    {
         sp<Visualizer> lpVisualizer = setVisualizer(env, thiz, 0);
         if (lpVisualizer == 0) {
             return;
         }
         lpVisualizer->release();
+        // Visualizer can still can be held by AudioEffect::EffectClient
     }
     // delete the JNI data
     VisualizerJniStorage* lpJniStorage =
@@ -449,9 +493,22 @@
     env->SetLongField(thiz, fields.fidJniData, 0);
 
     if (lpJniStorage) {
+        {
+        Mutex::Autolock l(sLock);
+        visualizer_callback_cookie *lpCookie = &lpJniStorage->mCallbackData;
         ALOGV("deleting pJniStorage: %p\n", lpJniStorage);
+        sVisualizerCallBackCookies.erase(lpCookie);
+        while (lpCookie->busy_count > 0) {
+            if (lpCookie->cond.waitRelative(sLock,
+                                            milliseconds(CALLBACK_COND_WAIT_TIMEOUT_MS)) !=
+                                                    NO_ERROR) {
+                break;
+            }
+        }
+        ALOG_ASSERT(lpCookie->busy_count == 0, "Unbalanced busy_count inc/dec");
         env->DeleteGlobalRef(lpJniStorage->mCallbackData.visualizer_class);
         env->DeleteGlobalRef(lpJniStorage->mCallbackData.visualizer_ref);
+        }  // sLock scope
         delete lpJniStorage;
     }
 }
@@ -707,4 +764,3 @@
 {
     return AndroidRuntime::registerNativeMethods(env, kClassPathName, gMethods, NELEM(gMethods));
 }
-
diff --git a/media/tests/EffectsTest/AndroidManifest.xml b/media/tests/EffectsTest/AndroidManifest.xml
index 9b59891..ad0c10e 100644
--- a/media/tests/EffectsTest/AndroidManifest.xml
+++ b/media/tests/EffectsTest/AndroidManifest.xml
@@ -13,6 +13,10 @@
      See the License for the specific language governing permissions and
      limitations under the License.
 -->
+<!--
+Make sure to enable access to the mic in settings and run:
+adb shell am compat enable ALLOW_TEST_API_ACCESS com.android.effectstest
+-->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
     package="com.android.effectstest">
diff --git a/media/tests/EffectsTest/res/layout/bassboosttest.xml b/media/tests/EffectsTest/res/layout/bassboosttest.xml
index ac912c8..5f9132c 100644
--- a/media/tests/EffectsTest/res/layout/bassboosttest.xml
+++ b/media/tests/EffectsTest/res/layout/bassboosttest.xml
@@ -187,6 +187,11 @@
                  android:layout_height="wrap_content"
                  android:scaleType="fitXY"/>
 
+            <Button android:id="@+id/hammer_on_release_bug"
+                    android:layout_width="fill_parent" android:layout_height="wrap_content"
+                    android:text="@string/hammer_on_release_bug_name">
+            </Button>
+
         </LinearLayout>
 
     </ScrollView>
diff --git a/media/tests/EffectsTest/res/layout/visualizertest.xml b/media/tests/EffectsTest/res/layout/visualizertest.xml
index 18d7a36..85dabbc 100644
--- a/media/tests/EffectsTest/res/layout/visualizertest.xml
+++ b/media/tests/EffectsTest/res/layout/visualizertest.xml
@@ -175,6 +175,11 @@
 
     </LinearLayout>
 
+    <Button android:id="@+id/hammer_on_release_bug"
+            android:layout_width="fill_parent" android:layout_height="wrap_content"
+            android:text="@string/hammer_on_release_bug_name">
+    </Button>
+
     <ImageView
          android:src="@android:drawable/divider_horizontal_dark"
          android:layout_width="fill_parent"
diff --git a/media/tests/EffectsTest/res/values/strings.xml b/media/tests/EffectsTest/res/values/strings.xml
index 7c12da1..a44c7e9 100644
--- a/media/tests/EffectsTest/res/values/strings.xml
+++ b/media/tests/EffectsTest/res/values/strings.xml
@@ -37,4 +37,6 @@
     <string name="send_level_name">Send Level</string>
     <!-- Toggles use of a multi-threaded client for an effect [CHAR LIMIT=24] -->
     <string name="effect_multithreaded">Multithreaded Use</string>
+    <!-- Runs a stress test for a bug related to simultaneous release of multiple effect instances [CHAR LIMIT=24] -->
+    <string name="hammer_on_release_bug_name">Hammer on release()</string>
 </resources>
diff --git a/media/tests/EffectsTest/src/com/android/effectstest/BassBoostTest.java b/media/tests/EffectsTest/src/com/android/effectstest/BassBoostTest.java
index cce2acc..a207bf1 100644
--- a/media/tests/EffectsTest/src/com/android/effectstest/BassBoostTest.java
+++ b/media/tests/EffectsTest/src/com/android/effectstest/BassBoostTest.java
@@ -17,29 +17,24 @@
 package com.android.effectstest;
 
 import android.app.Activity;
-import android.content.Context;
-import android.content.Intent;
+import android.media.audiofx.AudioEffect;
+import android.media.audiofx.BassBoost;
 import android.os.Bundle;
 import android.util.Log;
 import android.view.KeyEvent;
-import android.view.Menu;
-import android.view.View.OnClickListener;
 import android.view.View;
-import android.view.ViewGroup;
+import android.view.View.OnClickListener;
 import android.widget.Button;
-import android.widget.TextView;
-import android.widget.EditText;
-import android.widget.SeekBar;
-import android.widget.ToggleButton;
 import android.widget.CompoundButton;
 import android.widget.CompoundButton.OnCheckedChangeListener;
-import java.nio.ByteOrder;
-import java.nio.ByteBuffer;
-import java.util.HashMap;
-import java.util.Map;
+import android.widget.EditText;
+import android.widget.SeekBar;
+import android.widget.TextView;
+import android.widget.ToggleButton;
 
-import android.media.audiofx.BassBoost;
-import android.media.audiofx.AudioEffect;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.util.HashMap;
 
 public class BassBoostTest extends Activity implements OnCheckedChangeListener {
 
@@ -78,6 +73,9 @@
         mReleaseButton = (ToggleButton)findViewById(R.id.bbReleaseButton);
         mOnOffButton = (ToggleButton)findViewById(R.id.bassboostOnOff);
 
+        final Button hammerReleaseTest = (Button) findViewById(R.id.hammer_on_release_bug);
+        hammerReleaseTest.setEnabled(false);
+
         getEffect(sSession);
 
         if (mBassBoost != null) {
@@ -93,6 +91,14 @@
             mStrength = new BassBoostParam(mBassBoost, 0, 1000, seekBar, textView);
             seekBar.setOnSeekBarChangeListener(mStrength);
             mStrength.setEnabled(mBassBoost.getStrengthSupported());
+
+            hammerReleaseTest.setEnabled(true);
+            hammerReleaseTest.setOnClickListener(new OnClickListener() {
+                @Override
+                public void onClick(View v) {
+                    runHammerReleaseTest(hammerReleaseTest);
+                }
+            });
         }
     }
 
@@ -273,4 +279,52 @@
         }
     }
 
+    // Stress-tests releasing of AudioEffect by doing repeated creation
+    // and subsequent releasing. Also forces emission of callbacks from
+    // the AudioFlinger by setting a control status listener. Since all
+    // effect instances are bound to the same session, the AF will
+    // notify them about the change in their status. This can reveal racy
+    // behavior w.r.t. releasing.
+    class HammerReleaseTest extends Thread {
+        private static final int NUM_EFFECTS = 10;
+        private static final int NUM_ITERATIONS = 100;
+        private final int mSession;
+        private final Runnable mOnComplete;
+
+        HammerReleaseTest(int session, Runnable onComplete) {
+            mSession = session;
+            mOnComplete = onComplete;
+        }
+
+        @Override
+        public void run() {
+            Log.w(TAG, "HammerReleaseTest started");
+            BassBoost[] effects = new BassBoost[NUM_EFFECTS];
+            for (int i = 0; i < NUM_ITERATIONS; i++) {
+                for (int j = 0; j < NUM_EFFECTS; j++) {
+                    effects[j] = new BassBoost(0, mSession);
+                    effects[j].setControlStatusListener(mEffectListener);
+                    yield();
+                }
+                for (int j = NUM_EFFECTS - 1; j >= 0; j--) {
+                    Log.w(TAG, "HammerReleaseTest releasing effect " + (Object) effects[j]);
+                    effects[j].release();
+                    effects[j] = null;
+                    yield();
+                }
+            }
+            Log.w(TAG, "HammerReleaseTest ended");
+            runOnUiThread(mOnComplete);
+        }
+    }
+
+    private void runHammerReleaseTest(Button controlButton) {
+        controlButton.setEnabled(false);
+        HammerReleaseTest thread = new HammerReleaseTest(sSession,
+                () -> {
+                    controlButton.setEnabled(true);
+                });
+        thread.start();
+    }
+
 }
diff --git a/media/tests/EffectsTest/src/com/android/effectstest/VisualizerTest.java b/media/tests/EffectsTest/src/com/android/effectstest/VisualizerTest.java
index 2e141c5..dcfe11a 100644
--- a/media/tests/EffectsTest/src/com/android/effectstest/VisualizerTest.java
+++ b/media/tests/EffectsTest/src/com/android/effectstest/VisualizerTest.java
@@ -17,6 +17,7 @@
 package com.android.effectstest;
 
 import android.app.Activity;
+import android.media.audiofx.Visualizer;
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.Looper;
@@ -24,6 +25,8 @@
 import android.util.Log;
 import android.view.KeyEvent;
 import android.view.View;
+import android.view.View.OnClickListener;
+import android.widget.Button;
 import android.widget.CompoundButton;
 import android.widget.CompoundButton.OnCheckedChangeListener;
 import android.widget.EditText;
@@ -74,11 +77,22 @@
         mCallbackOn = false;
         mCallbackButton.setChecked(mCallbackOn);
 
+        final Button hammerReleaseTest = (Button) findViewById(R.id.hammer_on_release_bug);
+        hammerReleaseTest.setEnabled(false);
+
         mMultithreadedButton.setOnCheckedChangeListener(this);
         if (getEffect(sSession) != null) {
             mReleaseButton.setOnCheckedChangeListener(this);
             mOnOffButton.setOnCheckedChangeListener(this);
             mCallbackButton.setOnCheckedChangeListener(this);
+
+            hammerReleaseTest.setEnabled(true);
+            hammerReleaseTest.setOnClickListener(new OnClickListener() {
+                @Override
+                public void onClick(View v) {
+                    runHammerReleaseTest(hammerReleaseTest);
+                }
+            });
         }
     }
 
@@ -214,4 +228,50 @@
         }
     }
 
+    // Stress-tests releasing of AudioEffect by doing repeated creation
+    // and subsequent releasing. Unlike a similar class in BassBoostTest,
+    // this one doesn't sets a control status listener because Visualizer
+    // doesn't inherit from AudioEffect and doesn't implement this method
+    // by itself.
+    class HammerReleaseTest extends Thread {
+        private static final int NUM_EFFECTS = 10;
+        private static final int NUM_ITERATIONS = 100;
+        private final int mSession;
+        private final Runnable mOnComplete;
+
+        HammerReleaseTest(int session, Runnable onComplete) {
+            mSession = session;
+            mOnComplete = onComplete;
+        }
+
+        @Override
+        public void run() {
+            Log.w(TAG, "HammerReleaseTest started");
+            Visualizer[] effects = new Visualizer[NUM_EFFECTS];
+            for (int i = 0; i < NUM_ITERATIONS; i++) {
+                for (int j = 0; j < NUM_EFFECTS; j++) {
+                    effects[j] = new Visualizer(mSession);
+                    yield();
+                }
+                for (int j = NUM_EFFECTS - 1; j >= 0; j--) {
+                    Log.w(TAG, "HammerReleaseTest releasing effect " + (Object) effects[j]);
+                    effects[j].release();
+                    effects[j] = null;
+                    yield();
+                }
+            }
+            Log.w(TAG, "HammerReleaseTest ended");
+            runOnUiThread(mOnComplete);
+        }
+    }
+
+    private void runHammerReleaseTest(Button controlButton) {
+        controlButton.setEnabled(false);
+        HammerReleaseTest thread = new HammerReleaseTest(sSession,
+                () -> {
+                    controlButton.setEnabled(true);
+                });
+        thread.start();
+    }
+
 }
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothEventManager.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothEventManager.java
index 59d8acb..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();
     }
 
@@ -293,6 +297,8 @@
                 BluetoothDevice device) {
             short rssi = intent.getShortExtra(BluetoothDevice.EXTRA_RSSI, Short.MIN_VALUE);
             String name = intent.getStringExtra(BluetoothDevice.EXTRA_NAME);
+            final boolean isCoordinatedSetMember =
+                    intent.getBooleanExtra(BluetoothDevice.EXTRA_IS_COORDINATED_SET_MEMBER, false);
             // TODO Pick up UUID. They should be available for 2.1 devices.
             // Skip for now, there's a bluez problem and we are not getting uuids even for 2.1.
             CachedBluetoothDevice cachedDevice = mDeviceManager.findDevice(device);
@@ -307,6 +313,7 @@
             }
             cachedDevice.setRssi(rssi);
             cachedDevice.setJustDiscovered(true);
+            cachedDevice.setIsCoordinatedSetMember(isCoordinatedSetMember);
         }
     }
 
@@ -335,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 +
@@ -348,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,
@@ -496,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 4c80b91..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
@@ -80,6 +84,8 @@
 
     boolean mJustDiscovered;
 
+    boolean mIsCoordinatedSetMember = false;
+
     private final Collection<Callback> mCallbacks = new CopyOnWriteArrayList<>();
 
     /**
@@ -98,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;
 
@@ -131,6 +139,7 @@
         mDevice = device;
         fillData();
         mHiSyncId = BluetoothHearingAid.HI_SYNC_ID_INVALID;
+        mGroupId = BluetoothCsipSetCoordinator.GROUP_ID_INVALID;
     }
 
     /**
@@ -297,6 +306,42 @@
         return mHiSyncId != BluetoothHearingAid.HI_SYNC_ID_INVALID;
     }
 
+    /**
+     * Mark the discovered device as member of coordinated set.
+     *
+     * @param isCoordinatedSetMember {@code true}, if the device is a member of a coordinated set.
+     */
+    public void setIsCoordinatedSetMember(boolean isCoordinatedSetMember) {
+        mIsCoordinatedSetMember = isCoordinatedSetMember;
+    }
+
+    /**
+     * Check if the device is a CSIP member device.
+     *
+     * @return {@code true}, if this device supports CSIP, otherwise returns {@code false}.
+     */
+    public boolean isCoordinatedSetMemberDevice() {
+        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.
@@ -1171,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
new file mode 100644
index 0000000..6da249c
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CsipSetCoordinatorProfile.java
@@ -0,0 +1,251 @@
+/*
+ * Copyright 2021 HIMSA II K/S - www.himsa.com.
+ * Represented by EHIMA - www.ehima.com
+ *
+ * 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 static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_ALLOWED;
+import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
+
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothClass;
+import android.bluetooth.BluetoothCsipSetCoordinator;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothProfile;
+import android.content.Context;
+import android.os.ParcelUuid;
+import android.util.Log;
+
+import androidx.annotation.RequiresApi;
+
+import com.android.settingslib.R;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * CSIP Set Coordinator handles Bluetooth CSIP Set Coordinator role profile.
+ */
+public class CsipSetCoordinatorProfile implements LocalBluetoothProfile {
+    private static final String TAG = "CsipSetCoordinatorProfile";
+    private static final boolean VDBG = true;
+
+    private Context mContext;
+
+    private BluetoothCsipSetCoordinator mService;
+    private boolean mIsProfileReady;
+
+    private final CachedBluetoothDeviceManager mDeviceManager;
+
+    static final String NAME = "CSIP Set Coordinator";
+    private final LocalBluetoothProfileManager mProfileManager;
+
+    // Order of this profile in device profiles list
+    private static final int ORDINAL = 1;
+
+    // These callbacks run on the main thread.
+    private final class CoordinatedSetServiceListener implements BluetoothProfile.ServiceListener {
+        @RequiresApi(32)
+        public void onServiceConnected(int profile, BluetoothProfile proxy) {
+            if (VDBG) {
+                Log.d(TAG, "Bluetooth service connected");
+            }
+            mService = (BluetoothCsipSetCoordinator) proxy;
+            // We just bound to the service, so refresh the UI for any connected CSIP devices.
+            List<BluetoothDevice> deviceList = mService.getConnectedDevices();
+            while (!deviceList.isEmpty()) {
+                BluetoothDevice nextDevice = deviceList.remove(0);
+                CachedBluetoothDevice device = mDeviceManager.findDevice(nextDevice);
+                // we may add a new device here, but generally this should not happen
+                if (device == null) {
+                    if (VDBG) {
+                        Log.d(TAG, "CsipSetCoordinatorProfile found new device: " + nextDevice);
+                    }
+                    device = mDeviceManager.addDevice(nextDevice);
+                }
+                device.onProfileStateChanged(
+                        CsipSetCoordinatorProfile.this, BluetoothProfile.STATE_CONNECTED);
+                device.refresh();
+            }
+
+            mDeviceManager.updateCsipDevices();
+            mProfileManager.callServiceConnectedListeners();
+            mIsProfileReady = true;
+        }
+
+        public void onServiceDisconnected(int profile) {
+            if (VDBG) {
+                Log.d(TAG, "Bluetooth service disconnected");
+            }
+            mProfileManager.callServiceDisconnectedListeners();
+            mIsProfileReady = false;
+        }
+    }
+
+    CsipSetCoordinatorProfile(Context context, CachedBluetoothDeviceManager deviceManager,
+            LocalBluetoothProfileManager profileManager) {
+        mContext = context;
+        mDeviceManager = deviceManager;
+        mProfileManager = profileManager;
+
+        BluetoothAdapter.getDefaultAdapter().getProfileProxy(context,
+                new CoordinatedSetServiceListener(), BluetoothProfile.CSIP_SET_COORDINATOR);
+    }
+
+    /**
+     * Get CSIP devices matching connection states{
+     *
+     * @code BluetoothProfile.STATE_CONNECTED,
+     * @code BluetoothProfile.STATE_CONNECTING,
+     * @code BluetoothProfile.STATE_DISCONNECTING}
+     *
+     * @return Matching device list
+     */
+    public List<BluetoothDevice> getConnectedDevices() {
+        if (mService == null) {
+            return new ArrayList<BluetoothDevice>(0);
+        }
+        return mService.getDevicesMatchingConnectionStates(
+                new int[] {BluetoothProfile.STATE_CONNECTED, BluetoothProfile.STATE_CONNECTING,
+                        BluetoothProfile.STATE_DISCONNECTING});
+    }
+
+    /**
+     * Gets the connection status of the device.
+     *
+     * @code BluetoothProfile.STATE_CONNECTED,
+     * @code BluetoothProfile.STATE_CONNECTING,
+     * @code BluetoothProfile.STATE_DISCONNECTING}
+     *
+     * @return Connection status, {@code BluetoothProfile.STATE_DISCONNECTED} if unknown.
+     */
+    public int getConnectionStatus(BluetoothDevice device) {
+        if (mService == null) {
+            return BluetoothProfile.STATE_DISCONNECTED;
+        }
+        return mService.getConnectionState(device);
+    }
+
+    @Override
+    public boolean isProfileReady() {
+        return mIsProfileReady;
+    }
+
+    @Override
+    public int getProfileId() {
+        return BluetoothProfile.CSIP_SET_COORDINATOR;
+    }
+
+    @Override
+    public boolean accessProfileEnabled() {
+        return false;
+    }
+
+    @Override
+    public boolean isAutoConnectable() {
+        return true;
+    }
+
+    @Override
+    public boolean isEnabled(BluetoothDevice device) {
+        if (mService == null || device == null) {
+            return false;
+        }
+        return mService.getConnectionPolicy(device) > CONNECTION_POLICY_FORBIDDEN;
+    }
+
+    @Override
+    public int getConnectionPolicy(BluetoothDevice device) {
+        if (mService == null || device == null) {
+            return CONNECTION_POLICY_FORBIDDEN;
+        }
+        return mService.getConnectionPolicy(device);
+    }
+
+    @Override
+    public boolean setEnabled(BluetoothDevice device, boolean enabled) {
+        boolean isEnabled = false;
+        if (mService == null || device == null) {
+            return false;
+        }
+        if (enabled) {
+            if (mService.getConnectionPolicy(device) < CONNECTION_POLICY_ALLOWED) {
+                isEnabled = mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED);
+            }
+        } else {
+            isEnabled = mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN);
+        }
+
+        return isEnabled;
+    }
+
+    @Override
+    public int getOrdinal() {
+        return ORDINAL;
+    }
+
+    @Override
+    public int getNameResource(BluetoothDevice device) {
+        return R.string.summary_empty;
+    }
+
+    @Override
+    public int getSummaryResourceForDevice(BluetoothDevice device) {
+        int state = getConnectionStatus(device);
+        return BluetoothUtils.getConnectionStateSummary(state);
+    }
+
+    @Override
+    public int getDrawableResource(BluetoothClass btClass) {
+        return 0;
+    }
+
+    /**
+     * 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() {
+        return NAME;
+    }
+
+    @RequiresApi(32)
+    protected void finalize() {
+        if (VDBG) {
+            Log.d(TAG, "finalize()");
+        }
+        if (mService != null) {
+            try {
+                BluetoothAdapter.getDefaultAdapter().closeProfileProxy(
+                        BluetoothProfile.CSIP_SET_COORDINATOR, mService);
+                mService = null;
+            } catch (Throwable t) {
+                Log.w(TAG, "Error cleaning up CSIP Set Coordinator proxy", t);
+            }
+        }
+    }
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothProfileManager.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothProfileManager.java
index c4cb6a1..24113c5 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothProfileManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothProfileManager.java
@@ -19,6 +19,7 @@
 import android.bluetooth.BluetoothA2dp;
 import android.bluetooth.BluetoothA2dpSink;
 import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothCsipSetCoordinator;
 import android.bluetooth.BluetoothDevice;
 import android.bluetooth.BluetoothHeadset;
 import android.bluetooth.BluetoothHeadsetClient;
@@ -100,6 +101,7 @@
     private PbapClientProfile mPbapClientProfile;
     private PbapServerProfile mPbapProfile;
     private HearingAidProfile mHearingAidProfile;
+    private CsipSetCoordinatorProfile mCsipSetCoordinatorProfile;
     private SapProfile mSapProfile;
     private VolumeControlProfile mVolumeControlProfile;
 
@@ -230,7 +232,16 @@
             // Note: no event handler for VCP, only for being connectable.
             mProfileNameMap.put(VolumeControlProfile.NAME, mVolumeControlProfile);
         }
-
+        if (mCsipSetCoordinatorProfile == null
+                && supportedList.contains(BluetoothProfile.CSIP_SET_COORDINATOR)) {
+            if (DEBUG) {
+                Log.d(TAG, "Adding local CSIP set coordinator profile");
+            }
+            mCsipSetCoordinatorProfile =
+                    new CsipSetCoordinatorProfile(mContext, mDeviceManager, this);
+            addProfile(mCsipSetCoordinatorProfile, mCsipSetCoordinatorProfile.NAME,
+                    BluetoothCsipSetCoordinator.ACTION_CSIS_CONNECTION_STATE_CHANGED);
+        }
         mEventManager.registerProfileIntentReceiver();
     }
 
@@ -307,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());
@@ -462,6 +497,10 @@
         return mHidDeviceProfile;
     }
 
+    public CsipSetCoordinatorProfile getCsipSetCoordinatorProfile() {
+        return mCsipSetCoordinatorProfile;
+    }
+
     /**
      * Fill in a list of LocalBluetoothProfile objects that are supported by
      * the local device and the remote device.
@@ -582,6 +621,12 @@
             removedProfiles.remove(mVolumeControlProfile);
         }
 
+        if (mCsipSetCoordinatorProfile != null
+                && ArrayUtils.contains(uuids, BluetoothUuid.COORDINATED_SET)) {
+            profiles.add(mCsipSetCoordinatorProfile);
+            removedProfiles.remove(mCsipSetCoordinatorProfile);
+        }
+
         if (DEBUG) {
             Log.d(TAG,"New Profiles" + profiles.toString());
         }
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/OWNERS b/services/OWNERS
index b7128a3..a083319 100644
--- a/services/OWNERS
+++ b/services/OWNERS
@@ -4,3 +4,4 @@
 per-file art-profile* = calin@google.com, ngeoffray@google.com, vmarko@google.com
 
 per-file java/com/android/server/* = toddke@google.com,patb@google.com
+per-file tests/servicestests/src/com/android/server/systemconfig/* = patb@google.com
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/core/java/com/android/server/tv/TvInputHardwareManager.java b/services/core/java/com/android/server/tv/TvInputHardwareManager.java
index 277163a6..630317a 100755
--- a/services/core/java/com/android/server/tv/TvInputHardwareManager.java
+++ b/services/core/java/com/android/server/tv/TvInputHardwareManager.java
@@ -1265,12 +1265,14 @@
                     if (inputId != null) {
                         if (connection.updateCableConnectionStatusLocked(cableConnectionStatus)) {
                             if (previousCableConnectionStatus != connection.getInputStateLocked()) {
-                                mListener.onStateChanged(inputId, connection.getInputStateLocked());
+                                mHandler.obtainMessage(ListenerHandler.STATE_CHANGED,
+                                    connection.getInputStateLocked(), 0, inputId).sendToTarget();
                             }
                         } else {
                             if ((previousConfigsLength == 0)
                                     != (connection.getConfigsLengthLocked() == 0)) {
-                                mListener.onStateChanged(inputId, connection.getInputStateLocked());
+                                mHandler.obtainMessage(ListenerHandler.STATE_CHANGED,
+                                    connection.getInputStateLocked(), 0, inputId).sendToTarget();
                             }
                         }
                     }
diff --git a/services/core/java/com/android/server/vcn/Vcn.java b/services/core/java/com/android/server/vcn/Vcn.java
index 382398a..e0cc8e1 100644
--- a/services/core/java/com/android/server/vcn/Vcn.java
+++ b/services/core/java/com/android/server/vcn/Vcn.java
@@ -352,7 +352,7 @@
     }
 
     private void handleSafeModeStatusChanged() {
-        logDbg("VcnGatewayConnection safe mode status changed");
+        logVdbg("VcnGatewayConnection safe mode status changed");
         boolean hasSafeModeGatewayConnection = false;
 
         // If any VcnGatewayConnection is in safe mode, mark the entire VCN as being in safe mode
@@ -368,7 +368,7 @@
                 hasSafeModeGatewayConnection ? VCN_STATUS_CODE_SAFE_MODE : VCN_STATUS_CODE_ACTIVE;
         if (oldStatus != mCurrentStatus) {
             mVcnCallback.onSafeModeStatusChanged(hasSafeModeGatewayConnection);
-            logDbg(
+            logInfo(
                     "Safe mode "
                             + (mCurrentStatus == VCN_STATUS_CODE_SAFE_MODE ? "entered" : "exited"));
         }
@@ -539,6 +539,16 @@
         Slog.d(TAG, getLogPrefix() + msg, tr);
     }
 
+    private void logInfo(String msg) {
+        Slog.i(TAG, getLogPrefix() + msg);
+        LOCAL_LOG.log(getLogPrefix() + "INFO: " + msg);
+    }
+
+    private void logInfo(String msg, Throwable tr) {
+        Slog.i(TAG, getLogPrefix() + msg, tr);
+        LOCAL_LOG.log(getLogPrefix() + "INFO: " + msg + tr);
+    }
+
     private void logErr(String msg) {
         Slog.e(TAG, getLogPrefix() + msg);
         LOCAL_LOG.log(getLogPrefix() + "ERR: " + msg);
diff --git a/services/core/java/com/android/server/vcn/VcnGatewayConnection.java b/services/core/java/com/android/server/vcn/VcnGatewayConnection.java
index 450257f..7dec4e7 100644
--- a/services/core/java/com/android/server/vcn/VcnGatewayConnection.java
+++ b/services/core/java/com/android/server/vcn/VcnGatewayConnection.java
@@ -1677,10 +1677,8 @@
             mFailedAttempts = 0;
             cancelSafeModeAlarm();
 
-            if (mIsInSafeMode) {
-                mIsInSafeMode = false;
-                mGatewayStatusCallback.onSafeModeStatusChanged();
-            }
+            mIsInSafeMode = false;
+            mGatewayStatusCallback.onSafeModeStatusChanged();
         }
 
         protected void applyTransform(
diff --git a/services/java/com/android/server/SystemConfigService.java b/services/java/com/android/server/SystemConfigService.java
index a2768c6..3a9b2dc 100644
--- a/services/java/com/android/server/SystemConfigService.java
+++ b/services/java/com/android/server/SystemConfigService.java
@@ -21,6 +21,7 @@
 import android.Manifest;
 import android.content.Context;
 import android.os.ISystemConfig;
+import android.util.ArrayMap;
 import android.util.ArraySet;
 import android.util.SparseArray;
 
@@ -84,6 +85,21 @@
             }
             return ArrayUtils.convertToIntArray(uids);
         }
+
+        @Override
+        public List<String> getEnabledComponentOverrides(String packageName) {
+            ArrayMap<String, Boolean> systemComponents = SystemConfig.getInstance()
+                    .getComponentsEnabledStates(packageName);
+            List<String> enabledComponent = new ArrayList<>();
+            if (systemComponents != null) {
+                for (Map.Entry<String, Boolean> entry : systemComponents.entrySet()) {
+                    if (Boolean.TRUE.equals(entry.getValue())) {
+                        enabledComponent.add(entry.getKey());
+                    }
+                }
+            }
+            return enabledComponent;
+        }
     };
 
     public SystemConfigService(Context context) {
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/ServiceState.java b/telephony/java/android/telephony/ServiceState.java
index 6da61b7..1677c61 100644
--- a/telephony/java/android/telephony/ServiceState.java
+++ b/telephony/java/android/telephony/ServiceState.java
@@ -444,7 +444,9 @@
         mArfcnRsrpBoost = s.mArfcnRsrpBoost;
         synchronized (mNetworkRegistrationInfos) {
             mNetworkRegistrationInfos.clear();
-            mNetworkRegistrationInfos.addAll(s.getNetworkRegistrationInfoList());
+            for (NetworkRegistrationInfo nri : s.getNetworkRegistrationInfoList()) {
+                mNetworkRegistrationInfos.add(new NetworkRegistrationInfo(nri));
+            }
         }
         mNrFrequencyRange = s.mNrFrequencyRange;
         mOperatorAlphaLongRaw = s.mOperatorAlphaLongRaw;
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/test-mock/Android.bp b/test-mock/Android.bp
index 460a26d..0bb6198 100644
--- a/test-mock/Android.bp
+++ b/test-mock/Android.bp
@@ -32,7 +32,9 @@
         ":android-test-mock-sources",
         // Note: Below are NOT APIs of this library. We only take APIs under
         // the android.test.mock package. They however provide private APIs that
-        // android.test.mock APIs references to.
+        // android.test.mock APIs references to. We need to have the classes in
+        // source code form to have access to the @hide comment which disappears
+        // when the classes are compiled into a Jar library.
         ":framework-core-sources-for-test-mock",
         ":framework_native_aidl",
     ],
@@ -46,6 +48,14 @@
     api_packages: [
         "android.test.mock",
     ],
+    // Only include android.test.mock.* classes. Jarjar rules below removes
+    // classes in other packages like android.content. In order to keep the
+    // list up-to-date, permitted_packages ensures that the library contains
+    // clases under android.test.mock after the jarjar rules are applied.
+    jarjar_rules: "jarjar-rules.txt",
+    permitted_packages: [
+        "android.test.mock",
+    ],
     compile_dex: true,
     default_to_stubs: true,
     dist_group: "android",
diff --git a/test-mock/jarjar-rules.txt b/test-mock/jarjar-rules.txt
new file mode 100644
index 0000000..4420a44
--- /dev/null
+++ b/test-mock/jarjar-rules.txt
@@ -0,0 +1,7 @@
+zap android.accounts.**
+zap android.app.**
+zap android.content.**
+zap android.database.**
+zap android.os.**
+zap android.util.**
+zap android.view.**
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"],
diff --git a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectedStateTest.java b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectedStateTest.java
index 0f84f6e..c9a8947a 100644
--- a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectedStateTest.java
+++ b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectedStateTest.java
@@ -322,6 +322,7 @@
         triggerValidation(NetworkAgent.VALIDATION_STATUS_VALID);
         verify(mSafeModeTimeoutAlarm).cancel();
         assertFalse(mGatewayConnection.isInSafeMode());
+        verifySafeModeStateAndCallbackFired(1 /* invocationCount */, false /* isInSafeMode */);
     }
 
     @Test
@@ -391,6 +392,7 @@
 
         triggerValidation(NetworkAgent.VALIDATION_STATUS_VALID);
 
+        verifySafeModeStateAndCallbackFired(2 /* invocationCount */, false /* isInSafeMode */);
         assertFalse(mGatewayConnection.isInSafeMode());
     }
 
@@ -400,7 +402,7 @@
         mTestLooper.dispatchAll();
 
         triggerValidation(NetworkAgent.VALIDATION_STATUS_VALID);
-        assertFalse(mGatewayConnection.isInSafeMode());
+        verifySafeModeStateAndCallbackFired(1 /* invocationCount */, false /* isInSafeMode */);
 
         // Trigger a failed validation, and the subsequent safemode timeout.
         triggerValidation(NetworkAgent.VALIDATION_STATUS_NOT_VALID);
@@ -416,7 +418,7 @@
         runnableCaptor.getValue().run();
         mTestLooper.dispatchAll();
 
-        assertTrue(mGatewayConnection.isInSafeMode());
+        verifySafeModeStateAndCallbackFired(2 /* invocationCount */, true /* isInSafeMode */);
     }
 
     private Consumer<VcnNetworkAgent> setupNetworkAndGetUnwantedCallback() {
diff --git a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTestBase.java b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTestBase.java
index a696b3a..64d0bca 100644
--- a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTestBase.java
+++ b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTestBase.java
@@ -23,7 +23,6 @@
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertTrue;
 import static org.mockito.Matchers.any;
 import static org.mockito.Matchers.eq;
 import static org.mockito.Mockito.CALLS_REAL_METHODS;
@@ -301,6 +300,11 @@
                 expectCanceled);
     }
 
+    protected void verifySafeModeStateAndCallbackFired(int invocationCount, boolean isInSafeMode) {
+        verify(mGatewayStatusCallback, times(invocationCount)).onSafeModeStatusChanged();
+        assertEquals(isInSafeMode, mGatewayConnection.isInSafeMode());
+    }
+
     protected void verifySafeModeTimeoutNotifiesCallbackAndUnregistersNetworkAgent(
             @NonNull State expectedState) {
         // Set a VcnNetworkAgent, and expect it to be unregistered and cleared
@@ -314,9 +318,8 @@
         delayedEvent.run();
         mTestLooper.dispatchAll();
 
-        verify(mGatewayStatusCallback).onSafeModeStatusChanged();
         assertEquals(expectedState, mGatewayConnection.getCurrentState());
-        assertTrue(mGatewayConnection.isInSafeMode());
+        verifySafeModeStateAndCallbackFired(1, true);
 
         verify(mockNetworkAgent).unregister();
         assertNull(mGatewayConnection.getNetworkAgent());
diff --git a/tools/bit/print.cpp b/tools/bit/print.cpp
index 35feda1..8bc6f16 100644
--- a/tools/bit/print.cpp
+++ b/tools/bit/print.cpp
@@ -17,6 +17,7 @@
 #include "print.h"
 
 #include <sys/ioctl.h>
+#include <stdarg.h>
 #include <stdio.h>
 #include <unistd.h>
 
diff --git a/tools/bit/util.h b/tools/bit/util.h
index 7ccdab1..8c66911 100644
--- a/tools/bit/util.h
+++ b/tools/bit/util.h
@@ -17,6 +17,8 @@
 #ifndef UTIL_H
 #define UTIL_H
 
+#include <sys/types.h>
+
 #include <map>
 #include <string>
 #include <vector>
diff --git a/tools/streaming_proto/Errors.cpp b/tools/streaming_proto/Errors.cpp
index 0cd9037..6890d99 100644
--- a/tools/streaming_proto/Errors.cpp
+++ b/tools/streaming_proto/Errors.cpp
@@ -1,5 +1,6 @@
 #include "Errors.h"
 
+#include <stdarg.h>
 #include <stdlib.h>
 
 namespace android {