diff options
157 files changed, 5604 insertions, 1317 deletions
diff --git a/Android.bp b/Android.bp index 0277af05d1f3..1fbdc151867d 100644 --- a/Android.bp +++ b/Android.bp @@ -118,7 +118,6 @@ filegroup { ":libbluetooth-binder-aidl", ":libcamera_client_aidl", ":libcamera_client_framework_aidl", - ":packagemanager_aidl", ":libupdate_engine_aidl", ":resourcemanager_aidl", ":storaged_aidl", @@ -209,6 +208,7 @@ java_library { name: "framework-internal-utils", static_libs: [ "apex_aidl_interface-java", + "packagemanager_aidl-java", "framework-protos", "updatable-driver-protos", "ota_metadata_proto_java", @@ -357,9 +357,6 @@ filegroup { 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 @@ java_library { 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 65a6547b4493..17676787375c 100644 --- a/ApiDocs.bp +++ b/ApiDocs.bp @@ -77,22 +77,16 @@ stubs_defaults { // 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 @@ stubs_defaults { merge_annotations_dirs: [ "metalava-manual", ], + write_sdk_values: true, } droidstubs { @@ -114,6 +109,24 @@ droidstubs { 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 { 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 91e207433f3d..c967e51e16f9 100644 --- a/apct-tests/perftests/multiuser/Android.bp +++ b/apct-tests/perftests/multiuser/Android.bp @@ -31,5 +31,6 @@ android_test { ], 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 9117561fe1e5..bec3cc9272c3 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 000000000000..93b06e84e130 --- /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 fbfbc7cdef79..0acd759bc73e 100644 --- a/api/Android.bp +++ b/api/Android.bp @@ -24,9 +24,8 @@ package { 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 { }, } +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 @@ python_test_host { "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 @@ genrule { ":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 @@ genrule { ":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 @@ genrule { ":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 @@ genrule { // 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 4eb929ea1b5d..d2e5b7d1a07e 100644 --- a/api/api_versions_trimmer_unittests.py +++ b/api/api_versions_trimmer_unittests.py @@ -304,4 +304,4 @@ sultCallback;Landroid/os/Handler;)Z" since="24"/> 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 000000000000..9c67d7bded76 --- /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 000000000000..26795c47af9e --- /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 c4e0e1fd8ef0..8fcaeb1f6b87 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 bc61d15f4eb0..5c072541f974 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -954,7 +954,7 @@ package android { field public static final int measureWithLargestChild = 16843476; // 0x10102d4 field public static final int mediaRouteButtonStyle = 16843693; // 0x10103ad field public static final int mediaRouteTypes = 16843694; // 0x10103ae - field public static final int memtagMode = 16844313; // 0x1010619 + field public static final int memtagMode = 16844324; // 0x1010624 field public static final int menuCategory = 16843230; // 0x10101de field public static final int mimeGroup = 16844309; // 0x1010615 field public static final int mimeType = 16842790; // 0x1010026 @@ -978,7 +978,7 @@ package android { field public static final int multiArch = 16843918; // 0x101048e field public static final int multiprocess = 16842771; // 0x1010013 field public static final int name = 16842755; // 0x1010003 - field public static final int nativeHeapZeroInitialized = 16844314; // 0x101061a + field public static final int nativeHeapZeroInitialized = 16844325; // 0x1010625 field public static final int navigationBarColor = 16843858; // 0x1010452 field public static final int navigationBarDividerColor = 16844141; // 0x101056d field public static final int navigationContentDescription = 16843969; // 0x10104c1 @@ -8657,6 +8657,15 @@ package android.bluetooth { 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 @@ package android.bluetooth { 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"; @@ -29936,6 +29946,11 @@ package android.os { ctor public OperationCanceledException(String); } + public interface OutcomeReceiver<R, E extends java.lang.Throwable> { + method public default void onError(@NonNull E); + method public void onResult(@NonNull R); + } + public final class Parcel { method public void appendFrom(android.os.Parcel, int, int); method @Nullable public android.os.IBinder[] createBinderArray(); @@ -29961,6 +29976,7 @@ package android.os { method public boolean hasFileDescriptors(); method public byte[] marshall(); method @NonNull public static android.os.Parcel obtain(); + method @NonNull public static android.os.Parcel obtain(@NonNull android.os.IBinder); method @Nullable public Object[] readArray(@Nullable ClassLoader); method @Nullable public java.util.ArrayList readArrayList(@Nullable ClassLoader); method public void readBinderArray(@NonNull android.os.IBinder[]); @@ -29983,12 +29999,15 @@ package android.os { 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); @@ -30127,7 +30146,7 @@ package android.os { 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 } @@ -41031,6 +41050,7 @@ package android.telephony { method public String getNetworkOperator(); method public String getNetworkOperatorName(); method @RequiresPermission(anyOf={"android.permission.READ_PRIVILEGED_PHONE_STATE", android.Manifest.permission.READ_PRECISE_PHONE_STATE}) public int getNetworkSelectionMode(); + method @RequiresPermission("android.permission.READ_PRIVILEGED_PHONE_STATE") public void getNetworkSlicingConfiguration(@NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<android.telephony.data.SlicingConfig,android.telephony.TelephonyManager.SlicingException>); method public String getNetworkSpecifier(); method @Deprecated @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public int getNetworkType(); method @Deprecated public int getPhoneCount(); @@ -41264,6 +41284,13 @@ package android.telephony { field public static final int ERROR_TIMEOUT = 1; // 0x1 } + public static class TelephonyManager.SlicingException extends java.lang.Exception { + ctor public TelephonyManager.SlicingException(int); + method public int getErrorCode(); + field public static final int ERROR_MODEM_ERROR = 2; // 0x2 + field public static final int ERROR_TIMEOUT = 1; // 0x1 + } + public abstract static class TelephonyManager.UssdResponseCallback { ctor public TelephonyManager.UssdResponseCallback(); method public void onReceiveUssdResponse(android.telephony.TelephonyManager, String, CharSequence); @@ -41439,6 +41466,88 @@ package android.telephony.data { method @NonNull public android.telephony.data.ApnSetting.Builder setUser(@Nullable String); } + public final class NetworkSliceInfo implements android.os.Parcelable { + method public int describeContents(); + method @IntRange(from=0xffffffff, to=0xfffffe) public int getMappedHplmnSliceDifferentiator(); + method public int getMappedHplmnSliceServiceType(); + method @IntRange(from=0xffffffff, to=0xfffffe) public int getSliceDifferentiator(); + method public int getSliceServiceType(); + method public int getStatus(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.telephony.data.NetworkSliceInfo> CREATOR; + field public static final int SLICE_DIFFERENTIATOR_NO_SLICE = -1; // 0xffffffff + field public static final int SLICE_SERVICE_TYPE_EMBB = 1; // 0x1 + field public static final int SLICE_SERVICE_TYPE_MIOT = 3; // 0x3 + field public static final int SLICE_SERVICE_TYPE_NONE = 0; // 0x0 + field public static final int SLICE_SERVICE_TYPE_URLLC = 2; // 0x2 + field public static final int SLICE_STATUS_ALLOWED = 2; // 0x2 + field public static final int SLICE_STATUS_CONFIGURED = 1; // 0x1 + field public static final int SLICE_STATUS_DEFAULT_CONFIGURED = 5; // 0x5 + field public static final int SLICE_STATUS_REJECTED_NOT_AVAILABLE_IN_PLMN = 3; // 0x3 + field public static final int SLICE_STATUS_REJECTED_NOT_AVAILABLE_IN_REGISTERED_AREA = 4; // 0x4 + field public static final int SLICE_STATUS_UNKNOWN = 0; // 0x0 + } + + public static final class NetworkSliceInfo.Builder { + ctor public NetworkSliceInfo.Builder(); + method @NonNull public android.telephony.data.NetworkSliceInfo build(); + method @NonNull public android.telephony.data.NetworkSliceInfo.Builder setMappedHplmnSliceDifferentiator(@IntRange(from=0xffffffff, to=0xfffffe) int); + method @NonNull public android.telephony.data.NetworkSliceInfo.Builder setMappedHplmnSliceServiceType(int); + method @NonNull public android.telephony.data.NetworkSliceInfo.Builder setSliceDifferentiator(@IntRange(from=0xffffffff, to=0xfffffe) int); + method @NonNull public android.telephony.data.NetworkSliceInfo.Builder setSliceServiceType(int); + method @NonNull public android.telephony.data.NetworkSliceInfo.Builder setStatus(int); + } + + public final class RouteSelectionDescriptor implements android.os.Parcelable { + method public int describeContents(); + method @NonNull public java.util.List<java.lang.String> getDataNetworkName(); + method @IntRange(from=0x0, to=0xff) public int getPrecedence(); + method public int getSessionType(); + method @NonNull public java.util.List<android.telephony.data.NetworkSliceInfo> getSliceInfo(); + method public int getSscMode(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.telephony.data.RouteSelectionDescriptor> CREATOR; + field public static final int ROUTE_SSC_MODE_1 = 1; // 0x1 + field public static final int ROUTE_SSC_MODE_2 = 2; // 0x2 + field public static final int ROUTE_SSC_MODE_3 = 3; // 0x3 + field public static final int SESSION_TYPE_IPV4 = 0; // 0x0 + field public static final int SESSION_TYPE_IPV4V6 = 2; // 0x2 + field public static final int SESSION_TYPE_IPV6 = 1; // 0x1 + } + + public final class SlicingConfig implements android.os.Parcelable { + ctor public SlicingConfig(); + method public int describeContents(); + method @NonNull public java.util.List<android.telephony.data.NetworkSliceInfo> getSliceInfo(); + method @NonNull public java.util.List<android.telephony.data.UrspRule> getUrspRules(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.telephony.data.SlicingConfig> CREATOR; + } + + public final class TrafficDescriptor implements android.os.Parcelable { + method public int describeContents(); + method @Nullable public String getDataNetworkName(); + method @Nullable public String getOsAppId(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.telephony.data.TrafficDescriptor> CREATOR; + } + + public static final class TrafficDescriptor.Builder { + ctor public TrafficDescriptor.Builder(); + method @NonNull public android.telephony.data.TrafficDescriptor build(); + method @NonNull public android.telephony.data.TrafficDescriptor.Builder setDataNetworkName(@NonNull String); + method @NonNull public android.telephony.data.TrafficDescriptor.Builder setOsAppId(@NonNull String); + } + + public final class UrspRule implements android.os.Parcelable { + method public int describeContents(); + method @IntRange(from=0x0, to=0xff) public int getPrecedence(); + method @NonNull public java.util.List<android.telephony.data.RouteSelectionDescriptor> getRouteSelectionDescriptor(); + method @NonNull public java.util.List<android.telephony.data.TrafficDescriptor> getTrafficDescriptors(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.telephony.data.UrspRule> CREATOR; + } + } package android.telephony.emergency { diff --git a/core/api/system-current.txt b/core/api/system-current.txt index cd41e58ba882..e6015835c636 100644 --- a/core/api/system-current.txt +++ b/core/api/system-current.txt @@ -1507,8 +1507,24 @@ package android.bluetooth { 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 @@ package android.bluetooth { 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 @@ package android.os { 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); } @@ -9671,6 +9691,25 @@ package android.telephony { field public static final String MBMS_STREAMING_SERVICE_ACTION = "android.telephony.action.EmbmsStreaming"; } + public final class ModemActivityInfo implements android.os.Parcelable { + method public int describeContents(); + method @NonNull public android.telephony.ModemActivityInfo getDelta(@NonNull android.telephony.ModemActivityInfo); + method public long getIdleTimeMillis(); + method public static int getNumTxPowerLevels(); + method public long getReceiveTimeMillis(); + method public long getSleepTimeMillis(); + method public long getTimestampMillis(); + method public long getTransmitDurationMillisAtPowerLevel(int); + method @NonNull public android.util.Range<java.lang.Integer> getTransmitPowerRange(int); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.telephony.ModemActivityInfo> CREATOR; + field public static final int TX_POWER_LEVEL_0 = 0; // 0x0 + field public static final int TX_POWER_LEVEL_1 = 1; // 0x1 + field public static final int TX_POWER_LEVEL_2 = 2; // 0x2 + field public static final int TX_POWER_LEVEL_3 = 3; // 0x3 + field public static final int TX_POWER_LEVEL_4 = 4; // 0x4 + } + public final class NetworkRegistrationInfo implements android.os.Parcelable { method @Nullable public android.telephony.DataSpecificRegistrationInfo getDataSpecificInfo(); method public int getRegistrationState(); @@ -10337,6 +10376,7 @@ package android.telephony { method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean rebootRadio(); method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void reportDefaultNetworkStatus(boolean); method @RequiresPermission(allOf={android.Manifest.permission.ACCESS_FINE_LOCATION, android.Manifest.permission.MODIFY_PHONE_STATE}) public void requestCellInfoUpdate(@NonNull android.os.WorkSource, @NonNull java.util.concurrent.Executor, @NonNull android.telephony.TelephonyManager.CellInfoCallback); + method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void requestModemActivityInfo(@NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<android.telephony.ModemActivityInfo,android.telephony.TelephonyManager.ModemActivityInfoException>); method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void requestNumberVerification(@NonNull android.telephony.PhoneNumberRange, long, @NonNull java.util.concurrent.Executor, @NonNull android.telephony.NumberVerificationCallback); method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void resetAllCarrierActions(); method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void resetCarrierKeysForImsiEncryption(); @@ -10509,6 +10549,14 @@ package android.telephony { field public static final int RESULT_SUCCESS = 0; // 0x0 } + public static class TelephonyManager.ModemActivityInfoException extends java.lang.Exception { + method public int getErrorCode(); + field public static final int ERROR_INVALID_INFO_RECEIVED = 2; // 0x2 + field public static final int ERROR_MODEM_RESPONSE_ERROR = 3; // 0x3 + field public static final int ERROR_PHONE_NOT_AVAILABLE = 1; // 0x1 + field public static final int ERROR_UNKNOWN = 0; // 0x0 + } + public final class ThermalMitigationRequest implements android.os.Parcelable { method public int describeContents(); method @Nullable public android.telephony.DataThrottlingRequest getDataThrottlingRequest(); @@ -10772,32 +10820,6 @@ package android.telephony.data { field @NonNull public static final android.os.Parcelable.Creator<android.telephony.data.EpsBearerQosSessionAttributes> CREATOR; } - public final class NetworkSliceInfo implements android.os.Parcelable { - method public int describeContents(); - method @IntRange(from=android.telephony.data.NetworkSliceInfo.MIN_SLICE_DIFFERENTIATOR, to=android.telephony.data.NetworkSliceInfo.MAX_SLICE_DIFFERENTIATOR) public int getMappedHplmnSliceDifferentiator(); - method public int getMappedHplmnSliceServiceType(); - method @IntRange(from=android.telephony.data.NetworkSliceInfo.MIN_SLICE_DIFFERENTIATOR, to=android.telephony.data.NetworkSliceInfo.MAX_SLICE_DIFFERENTIATOR) public int getSliceDifferentiator(); - method public int getSliceServiceType(); - method public void writeToParcel(@NonNull android.os.Parcel, int); - field @NonNull public static final android.os.Parcelable.Creator<android.telephony.data.NetworkSliceInfo> CREATOR; - field public static final int MAX_SLICE_DIFFERENTIATOR = 16777214; // 0xfffffe - field public static final int MIN_SLICE_DIFFERENTIATOR = -1; // 0xffffffff - field public static final int SLICE_DIFFERENTIATOR_NO_SLICE = -1; // 0xffffffff - field public static final int SLICE_SERVICE_TYPE_EMBB = 1; // 0x1 - field public static final int SLICE_SERVICE_TYPE_MIOT = 3; // 0x3 - field public static final int SLICE_SERVICE_TYPE_NONE = 0; // 0x0 - field public static final int SLICE_SERVICE_TYPE_URLLC = 2; // 0x2 - } - - public static final class NetworkSliceInfo.Builder { - ctor public NetworkSliceInfo.Builder(); - method @NonNull public android.telephony.data.NetworkSliceInfo build(); - method @NonNull public android.telephony.data.NetworkSliceInfo.Builder setMappedHplmnSliceDifferentiator(@IntRange(from=android.telephony.data.NetworkSliceInfo.MIN_SLICE_DIFFERENTIATOR, to=android.telephony.data.NetworkSliceInfo.MAX_SLICE_DIFFERENTIATOR) int); - method @NonNull public android.telephony.data.NetworkSliceInfo.Builder setMappedHplmnSliceServiceType(int); - method @NonNull public android.telephony.data.NetworkSliceInfo.Builder setSliceDifferentiator(@IntRange(from=android.telephony.data.NetworkSliceInfo.MIN_SLICE_DIFFERENTIATOR, to=android.telephony.data.NetworkSliceInfo.MAX_SLICE_DIFFERENTIATOR) int); - method @NonNull public android.telephony.data.NetworkSliceInfo.Builder setSliceServiceType(int); - } - public final class NrQosSessionAttributes implements android.os.Parcelable android.net.QosSessionAttributes { method public int describeContents(); method @NonNull public java.time.Duration getBitRateWindowDuration(); @@ -10854,15 +10876,6 @@ package android.telephony.data { method @NonNull public android.telephony.data.ThrottleStatus.Builder setTransportType(int); } - public final class TrafficDescriptor implements android.os.Parcelable { - ctor public TrafficDescriptor(@Nullable String, @Nullable String); - method public int describeContents(); - method @Nullable public String getDnn(); - method @Nullable public String getOsAppId(); - method public void writeToParcel(@NonNull android.os.Parcel, int); - field @NonNull public static final android.os.Parcelable.Creator<android.telephony.data.TrafficDescriptor> CREATOR; - } - } package android.telephony.euicc { diff --git a/core/api/test-current.txt b/core/api/test-current.txt index 661394732a12..d905bbeba6b5 100644 --- a/core/api/test-current.txt +++ b/core/api/test-current.txt @@ -1649,6 +1649,28 @@ package android.telephony { field public static final String MBMS_STREAMING_SERVICE_OVERRIDE_METADATA = "mbms-streaming-service-override"; } + public final class ModemActivityInfo implements android.os.Parcelable { + ctor public ModemActivityInfo(long, int, int, @NonNull int[], int); + method public int describeContents(); + method @NonNull public android.telephony.ModemActivityInfo getDelta(@NonNull android.telephony.ModemActivityInfo); + method public long getIdleTimeMillis(); + method public static int getNumTxPowerLevels(); + method public long getReceiveTimeMillis(); + method public long getSleepTimeMillis(); + method public long getTimestampMillis(); + method public long getTransmitDurationMillisAtPowerLevel(int); + method @NonNull public android.util.Range<java.lang.Integer> getTransmitPowerRange(int); + method public boolean isEmpty(); + method public boolean isValid(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.telephony.ModemActivityInfo> CREATOR; + field public static final int TX_POWER_LEVEL_0 = 0; // 0x0 + field public static final int TX_POWER_LEVEL_1 = 1; // 0x1 + field public static final int TX_POWER_LEVEL_2 = 2; // 0x2 + field public static final int TX_POWER_LEVEL_3 = 3; // 0x3 + field public static final int TX_POWER_LEVEL_4 = 4; // 0x4 + } + public class PhoneNumberUtils { method public static int getMinMatchForTest(); method public static void setMinMatchForTest(int); diff --git a/core/java/Android.bp b/core/java/Android.bp index eee696b0404c..3b2d88318b76 100644 --- a/core/java/Android.bp +++ b/core/java/Android.bp @@ -332,13 +332,29 @@ filegroup { filegroup { name: "framework-cellbroadcast-shared-srcs", srcs: [ - "android/os/HandlerExecutor.java", "android/util/LocalLog.java", + ], +} + +java_library { + name: "modules-utils-statemachine", + srcs: [ "com/android/internal/util/IState.java", - "com/android/internal/util/Preconditions.java", "com/android/internal/util/State.java", "com/android/internal/util/StateMachine.java", ], + libs: [ + "framework-annotations-lib", + "unsupportedappusage", + ], + sdk_version: "module_current", + min_sdk_version: "29", + + visibility: ["//visibility:public"], + apex_available: [ + "//apex_available:anyapex", + "//apex_available:platform", + ], } filegroup { @@ -365,10 +381,7 @@ filegroup { "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 000000000000..2d3456b0ea46 --- /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 000000000000..975318e4de67 --- /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 41170a4c2749..749e8f548fab 100644 --- a/core/java/android/app/ActivityThread.java +++ b/core/java/android/app/ActivityThread.java @@ -6299,7 +6299,13 @@ public final class ActivityThread extends ClientTransactionHandler { 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/app/IntentService.java b/core/java/android/app/IntentService.java index 71b28fba6019..6c435b9597f4 100644 --- a/core/java/android/app/IntentService.java +++ b/core/java/android/app/IntentService.java @@ -54,7 +54,7 @@ import android.os.Message; * @see android.support.v4.app.JobIntentService * * @deprecated IntentService is subject to all the - * <a href="/preview/features/background.html">background execution limits</a> + * <a href="{@docRoot}about/versions/oreo/background.html">background execution limits</a> * imposed with Android 8.0 (API level 26). Consider using {@link androidx.work.WorkManager} * or {@link androidx.core.app.JobIntentService}, which uses jobs * instead of services when running on Android 8.0 or higher. diff --git a/core/java/android/bluetooth/BluetoothAdapter.java b/core/java/android/bluetooth/BluetoothAdapter.java index d487025631c7..313dd3e2bed0 100644 --- a/core/java/android/bluetooth/BluetoothAdapter.java +++ b/core/java/android/bluetooth/BluetoothAdapter.java @@ -2819,6 +2819,10 @@ public final class BluetoothAdapter { } 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 @@ public final class BluetoothAdapter { 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 000000000000..cb542e5ba388 --- /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 38fb90d9c4a7..1054b4cd49ac 100644 --- a/core/java/android/bluetooth/BluetoothDevice.java +++ b/core/java/android/bluetooth/BluetoothDevice.java @@ -106,7 +106,7 @@ public final class BluetoothDevice implements Parcelable { * <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 final class BluetoothDevice implements Parcelable { 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 @@ public final class BluetoothDevice implements Parcelable { * @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 632572dea3c6..18aa23a07b87 100644 --- a/core/java/android/bluetooth/BluetoothHeadset.java +++ b/core/java/android/bluetooth/BluetoothHeadset.java @@ -1018,8 +1018,8 @@ public final class BluetoothHeadset implements BluetoothProfile { * - 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 @@ public final class BluetoothHeadset implements BluetoothProfile { * - 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 @@ public final class BluetoothHeadset implements BluetoothProfile { * @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 2239100a5523..fbee57773173 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/content/pm/parsing/component/ParsedIntentInfo.java b/core/java/android/content/pm/parsing/component/ParsedIntentInfo.java index 0ba92cc4fef7..504a7bd5a320 100644 --- a/core/java/android/content/pm/parsing/component/ParsedIntentInfo.java +++ b/core/java/android/content/pm/parsing/component/ParsedIntentInfo.java @@ -19,7 +19,6 @@ package android.content.pm.parsing.component; import android.annotation.Nullable; import android.content.IntentFilter; import android.os.Parcel; -import android.os.Parcelable; import android.util.Pair; import com.android.internal.util.DataClass; @@ -168,19 +167,6 @@ public final class ParsedIntentInfo extends IntentFilter { + '}'; } - public static final Parcelable.Creator<ParsedIntentInfo> CREATOR = - new Parcelable.Creator<ParsedIntentInfo>() { - @Override - public ParsedIntentInfo createFromParcel(Parcel source) { - return new ParsedIntentInfo(source); - } - - @Override - public ParsedIntentInfo[] newArray(int size) { - return new ParsedIntentInfo[size]; - } - }; - public boolean isHasDefault() { return hasDefault; } diff --git a/core/java/android/hardware/SensorManager.java b/core/java/android/hardware/SensorManager.java index 0a76a9c6bee3..9e78a6bb4760 100644 --- a/core/java/android/hardware/SensorManager.java +++ b/core/java/android/hardware/SensorManager.java @@ -1440,14 +1440,14 @@ public abstract class SensorManager { * Assuming that the bottom edge of the device faces the * user and that the screen is face-up, tilting the top edge * of the device toward the ground creates a positive pitch - * angle. The range of values is -π to π.</li> + * angle. The range of values is -π/2 to π/2.</li> * <li>values[2]: <i>Roll</i>, angle of rotation about the y axis. This * value represents the angle between a plane perpendicular * to the device's screen and a plane perpendicular to the * ground. Assuming that the bottom edge of the device faces * the user and that the screen is face-up, tilting the left * edge of the device toward the ground creates a positive - * roll angle. The range of values is -π/2 to π/2.</li> + * roll angle. The range of values is -π to π.</li> * </ul> * <p> * Applying these three rotations in the azimuth, pitch, roll order diff --git a/core/java/android/net/NetworkTemplate.java b/core/java/android/net/NetworkTemplate.java index 68917a82884b..08f75df5d82d 100644 --- a/core/java/android/net/NetworkTemplate.java +++ b/core/java/android/net/NetworkTemplate.java @@ -47,7 +47,6 @@ import android.text.TextUtils; 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 @@ public class NetworkTemplate implements Parcelable { } } - 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 @@ public class NetworkTemplate implements Parcelable { // 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 @@ public class NetworkTemplate implements Parcelable { 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 5a25cfc00cd4..ae8d0101947d 100644 --- a/core/java/android/net/nsd/NsdManager.java +++ b/core/java/android/net/nsd/NsdManager.java @@ -23,6 +23,9 @@ import static com.android.internal.util.Preconditions.checkStringNotEmpty; 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 @@ public final class NsdManager { 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 final class NsdManager { 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 @@ public final class NsdManager { 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 @@ public final class NsdManager { } 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 6da02f5c9ff5..7ce8d7267de2 100644 --- a/core/java/android/os/BaseBundle.java +++ b/core/java/android/os/BaseBundle.java @@ -260,6 +260,9 @@ public class BaseBundle { /** * 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 @@ public class BaseBundle { /** * 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 @@ public class BaseBundle { * * @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 @@ public class BaseBundle { */ 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 @@ public class BaseBundle { @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 @@ public class BaseBundle { @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 @@ public class BaseBundle { @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 4d160da22ff8..d83d94a8ec77 100644 --- a/core/java/android/os/ISystemConfig.aidl +++ b/core/java/android/os/ISystemConfig.aidl @@ -40,4 +40,9 @@ interface ISystemConfig { * @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 a870c04f9cd3..c575c80e41fc 100644 --- a/core/java/android/os/OWNERS +++ b/core/java/android/os/OWNERS @@ -69,3 +69,6 @@ per-file Bugreport* = file:/platform/frameworks/native:/cmds/dumpstate/OWNERS # 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/OutcomeReceiver.java b/core/java/android/os/OutcomeReceiver.java new file mode 100644 index 000000000000..01b276411446 --- /dev/null +++ b/core/java/android/os/OutcomeReceiver.java @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.os; + +import android.annotation.NonNull; + +/** + * Callback interface intended for use when an asynchronous operation may result in a failure. + * + * This interface may be used in cases where an asynchronous API may complete either with a value + * or with a {@link Throwable} that indicates an error. + * @param <R> The type of the result that's being sent. + * @param <E> The type of the {@link Throwable} that contains more information about the error. + */ +public interface OutcomeReceiver<R, E extends Throwable> { + /** + * Called when the asynchronous operation succeeds and delivers a result value. + * @param result The value delivered by the asynchronous operation. + */ + void onResult(@NonNull R result); + + /** + * Called when the asynchronous operation fails. The mode of failure is indicated by the + * {@link Throwable} passed as an argument to this method. + * @param error A subclass of {@link Throwable} with more details about the error that occurred. + */ + default void onError(@NonNull E error) {} +} diff --git a/core/java/android/os/Parcel.java b/core/java/android/os/Parcel.java index 00db972bf709..358aa29eaea6 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 @@ public final class Parcel { 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. @@ -282,6 +288,8 @@ public final class Parcel { @CriticalNative private static native void nativeMarkSensitive(long nativePtr); + @FastNative + private static native void nativeMarkForBinder(long nativePtr, IBinder binder); @CriticalNative private static native int nativeDataSize(long nativePtr); @CriticalNative @@ -444,6 +452,21 @@ public final class Parcel { } /** + * 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. + */ + @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. */ @@ -498,6 +521,16 @@ public final class Parcel { /** * Parcel data should be zero'd before realloc'd or deleted. + * + * Note: currently this feature requires multiple things to work in concert: + * - markSensitive must be called on every relative Parcel + * - FLAG_CLEAR_BUF must be passed into the kernel + * This requires having code which does the right thing in every method and in every backend + * of AIDL. Rather than exposing this API, it should be replaced with a single API on + * IBinder objects which can be called once, and the information should be fed into the + * Parcel using markForBinder APIs. In terms of code size and number of API calls, this is + * much more extensible. + * * @hide */ public final void markSensitive() { @@ -505,9 +538,16 @@ public final class Parcel { } /** + * @hide + */ + private void markForBinder(@NonNull IBinder binder) { + nativeMarkForBinder(mNativePtr, binder); + } + + /** * Returns the total amount of data contained in the parcel. */ - public final int dataSize() { + public int dataSize() { return nativeDataSize(mNativePtr); } @@ -1281,6 +1321,46 @@ public final class Parcel { } } + /** @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; @@ -1946,6 +2026,14 @@ public final class Parcel { 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) { @@ -2048,6 +2136,18 @@ public final class Parcel { 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; @@ -2728,7 +2828,20 @@ public final class Parcel { */ 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); } /** @@ -2927,7 +3040,7 @@ public final class Parcel { return null; } ArrayList l = new ArrayList(N); - readListInternal(l, N, loader); + readListInternal(l, N, loader, /* clazz */ null); return l; } @@ -3327,20 +3440,29 @@ public final class Parcel { */ @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; } @@ -3377,7 +3499,7 @@ public final class Parcel { setDataPosition(MathUtils.addOrThrow(dataPosition(), length)); return new LazyValue(this, start, length, type, loader); } else { - return readValue(type, loader); + return readValue(type, loader, /* clazz */ null); } } @@ -3386,12 +3508,19 @@ public final class Parcel { 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; @@ -3400,46 +3529,41 @@ public final class Parcel { @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) { - int restore = mSource.dataPosition(); - try { - mSource.setDataPosition(mPosition); - out.writeInt(mSource.readInt()); // Type - out.writeInt(mSource.readInt()); // Length - out.appendFrom(mSource, mSource.dataPosition(), mLength); - } finally { - mSource.setDataPosition(restore); - } + 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 + "}"; } @@ -3458,155 +3582,224 @@ public final class Parcel { 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: + object = readPersistableBundle(loader); + break; + + case VAL_SIZE: + object = readSize(); + break; + + case VAL_SIZEF: + object = readSizeF(); + break; - case VAL_PERSISTABLEBUNDLE: - return readPersistableBundle(loader); + case VAL_DOUBLEARRAY: + object = createDoubleArray(); + break; - case VAL_SIZE: - return readSize(); + case VAL_CHAR: + object = (char) readInt(); + break; - case VAL_SIZEF: - return readSizeF(); + case VAL_SHORTARRAY: + object = createShortArray(); + break; - case VAL_DOUBLEARRAY: - return createDoubleArray(); + case VAL_CHARARRAY: + object = createCharArray(); + break; - default: - int off = dataPosition() - 4; - throw new RuntimeException( - "Parcel " + this + ": Unmarshalling unknown type code " + type + " at offset " + off); + 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: @@ -3625,17 +3818,42 @@ public final class Parcel { * @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); } @@ -3669,6 +3887,28 @@ public final class Parcel { */ @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; @@ -3684,7 +3924,15 @@ public final class Parcel { 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 { @@ -3700,6 +3948,14 @@ public final class Parcel { 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 " @@ -3737,7 +3993,7 @@ public final class Parcel { map.put(name, creator); } - return creator; + return (Parcelable.Creator<T>) creator; } /** @@ -3983,13 +4239,21 @@ public final class Parcel { 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--; } } @@ -4068,6 +4332,10 @@ public final class Parcel { 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 a537c98fc497..a3962117279e 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 @@ public interface Parcelable { * @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/ServiceManager.java b/core/java/android/os/ServiceManager.java index 06a2c875cf4f..4e8418bd60ec 100644 --- a/core/java/android/os/ServiceManager.java +++ b/core/java/android/os/ServiceManager.java @@ -29,7 +29,14 @@ import com.android.internal.util.StatLogger; import java.util.Map; -/** @hide */ +/** + * Manage binder services as registered with the binder context manager. These services must be + * declared statically on an Android device (SELinux access_vector service_manager, w/ service + * names in service_contexts files), and they do not follow the activity lifecycle. When + * building applications, android.app.Service should be preferred. + * + * @hide + **/ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) public final class ServiceManager { private static final String TAG = "ServiceManager"; diff --git a/core/java/android/os/ServiceManagerNative.java b/core/java/android/os/ServiceManagerNative.java index 755c35f4767a..3739040eef60 100644 --- a/core/java/android/os/ServiceManagerNative.java +++ b/core/java/android/os/ServiceManagerNative.java @@ -98,6 +98,10 @@ class ServiceManagerProxy implements IServiceManager { 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 9bfa8adc8571..a6316df0780c 100644 --- a/core/java/android/os/SystemConfigManager.java +++ b/core/java/android/os/SystemConfigManager.java @@ -17,6 +17,7 @@ package android.os; 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 @@ public class SystemConfigManager { 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 bf0b655fe574..1f11197afeee 100644 --- a/core/java/android/os/VintfObject.java +++ b/core/java/android/os/VintfObject.java @@ -97,8 +97,11 @@ public class VintfObject { * ["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/os/connectivity/CellularBatteryStats.java b/core/java/android/os/connectivity/CellularBatteryStats.java index 121fd333d17f..fc17002ba056 100644 --- a/core/java/android/os/connectivity/CellularBatteryStats.java +++ b/core/java/android/os/connectivity/CellularBatteryStats.java @@ -109,7 +109,7 @@ public final class CellularBatteryStats implements Parcelable { CellSignalStrength.getNumSignalStrengthLevels())); mTxTimeMs = Arrays.copyOfRange( txTimeMs, 0, - Math.min(txTimeMs.length, ModemActivityInfo.TX_POWER_LEVELS)); + Math.min(txTimeMs.length, ModemActivityInfo.getNumTxPowerLevels())); mMonitoredRailChargeConsumedMaMs = monitoredRailChargeConsumedMaMs; } diff --git a/core/java/android/permission/OWNERS b/core/java/android/permission/OWNERS index 19a3a8bd514a..b5466b6d3cb5 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/text/Layout.java b/core/java/android/text/Layout.java index f0f0867d414b..505f400c60d2 100644 --- a/core/java/android/text/Layout.java +++ b/core/java/android/text/Layout.java @@ -2351,7 +2351,10 @@ public abstract class Layout { final int ellipsisStringLen = ellipsisString.length(); // Use the ellipsis string only if there are that at least as many characters to replace. final boolean useEllipsisString = ellipsisCount >= ellipsisStringLen; - for (int i = 0; i < ellipsisCount; i++) { + final int min = Math.max(0, start - ellipsisStart - lineStart); + final int max = Math.min(ellipsisCount, end - ellipsisStart - lineStart); + + for (int i = min; i < max; i++) { final char c; if (useEllipsisString && i < ellipsisStringLen) { c = ellipsisString.charAt(i); @@ -2360,9 +2363,7 @@ public abstract class Layout { } final int a = i + ellipsisStart + lineStart; - if (start <= a && a < end) { - dest[destoff + a - start] = c; - } + dest[destoff + a - start] = c; } } diff --git a/core/java/android/util/ArrayMap.java b/core/java/android/util/ArrayMap.java index 4cf0a36d24bb..418d92c44290 100644 --- a/core/java/android/util/ArrayMap.java +++ b/core/java/android/util/ArrayMap.java @@ -645,7 +645,7 @@ public final class ArrayMap<K, V> implements Map<K, V> { 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/webkit/URLUtil.java b/core/java/android/webkit/URLUtil.java index 844b156b84d3..c7609a6c18af 100644 --- a/core/java/android/webkit/URLUtil.java +++ b/core/java/android/webkit/URLUtil.java @@ -310,7 +310,7 @@ public final class URLUtil { String extension = null; // If we couldn't do anything with the hint, move toward the content disposition - if (filename == null && contentDisposition != null) { + if (contentDisposition != null) { filename = parseContentDisposition(contentDisposition); if (filename != null) { int index = filename.lastIndexOf('/') + 1; diff --git a/core/java/android/widget/QuickContactBadge.java b/core/java/android/widget/QuickContactBadge.java index ea39f6d71573..fcaeeffe8e99 100644 --- a/core/java/android/widget/QuickContactBadge.java +++ b/core/java/android/widget/QuickContactBadge.java @@ -396,8 +396,9 @@ public class QuickContactBadge extends ImageView implements OnClickListener { // 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/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java index dd2940f8c110..e8db609ee991 100644 --- a/core/java/com/android/internal/os/BatteryStatsImpl.java +++ b/core/java/com/android/internal/os/BatteryStatsImpl.java @@ -66,7 +66,6 @@ import android.provider.Settings; import android.telephony.CellSignalStrength; import android.telephony.DataConnectionRealTimeInfo; import android.telephony.ModemActivityInfo; -import android.telephony.ModemActivityInfo.TransmitPower; import android.telephony.ServiceState; import android.telephony.SignalStrength; import android.telephony.TelephonyManager; @@ -7205,7 +7204,7 @@ public class BatteryStatsImpl extends BatteryStats { public ControllerActivityCounterImpl getOrCreateModemControllerActivityLocked() { if (mModemControllerActivity == null) { mModemControllerActivity = new ControllerActivityCounterImpl(mBsi.mOnBatteryTimeBase, - ModemActivityInfo.TX_POWER_LEVELS); + ModemActivityInfo.getNumTxPowerLevels()); } return mModemControllerActivity; } @@ -8687,7 +8686,7 @@ public class BatteryStatsImpl extends BatteryStats { if (in.readInt() != 0) { mModemControllerActivity = new ControllerActivityCounterImpl(mBsi.mOnBatteryTimeBase, - ModemActivityInfo.TX_POWER_LEVELS, in); + ModemActivityInfo.getNumTxPowerLevels(), in); } else { mModemControllerActivity = null; } @@ -9953,7 +9952,7 @@ public class BatteryStatsImpl extends BatteryStats { mBluetoothActivity = new ControllerActivityCounterImpl(mOnBatteryTimeBase, NUM_BT_TX_LEVELS); mModemActivity = new ControllerActivityCounterImpl(mOnBatteryTimeBase, - ModemActivityInfo.TX_POWER_LEVELS); + ModemActivityInfo.getNumTxPowerLevels()); mMobileRadioActiveTimer = new StopwatchTimer(mClocks, null, -400, null, mOnBatteryTimeBase); mMobileRadioActivePerAppTimer = new StopwatchTimer(mClocks, null, -401, null, mOnBatteryTimeBase); @@ -11131,26 +11130,7 @@ public class BatteryStatsImpl extends BatteryStats { } } - private ModemActivityInfo mLastModemActivityInfo = - new ModemActivityInfo(0, 0, 0, new int[0], 0); - - private ModemActivityInfo getDeltaModemActivityInfo(ModemActivityInfo activityInfo) { - if (activityInfo == null) { - return null; - } - int[] txTimeMs = new int[ModemActivityInfo.TX_POWER_LEVELS]; - for (int i = 0; i < ModemActivityInfo.TX_POWER_LEVELS; i++) { - txTimeMs[i] = activityInfo.getTransmitPowerInfo().get(i).getTimeInMillis() - - mLastModemActivityInfo.getTransmitPowerInfo().get(i).getTimeInMillis(); - } - ModemActivityInfo deltaInfo = new ModemActivityInfo(activityInfo.getTimestamp(), - activityInfo.getSleepTimeMillis() - mLastModemActivityInfo.getSleepTimeMillis(), - activityInfo.getIdleTimeMillis() - mLastModemActivityInfo.getIdleTimeMillis(), - txTimeMs, - activityInfo.getReceiveTimeMillis() - mLastModemActivityInfo.getReceiveTimeMillis()); - mLastModemActivityInfo = activityInfo; - return deltaInfo; - } + private ModemActivityInfo mLastModemActivityInfo = null; /** * Distribute Cell radio energy info and network traffic to apps. @@ -11159,7 +11139,9 @@ public class BatteryStatsImpl extends BatteryStats { if (DEBUG_ENERGY) { Slog.d(TAG, "Updating mobile radio stats with " + activityInfo); } - ModemActivityInfo deltaInfo = getDeltaModemActivityInfo(activityInfo); + ModemActivityInfo deltaInfo = mLastModemActivityInfo == null ? activityInfo + : mLastModemActivityInfo.getDelta(activityInfo); + mLastModemActivityInfo = activityInfo; // Add modem tx power to history. addModemTxPowerToHistory(deltaInfo); @@ -11191,10 +11173,9 @@ public class BatteryStatsImpl extends BatteryStats { mModemActivity.getSleepTimeCounter().addCountLocked( deltaInfo.getSleepTimeMillis()); mModemActivity.getRxTimeCounter().addCountLocked(deltaInfo.getReceiveTimeMillis()); - for (int lvl = 0; lvl < ModemActivityInfo.TX_POWER_LEVELS; lvl++) { + for (int lvl = 0; lvl < ModemActivityInfo.getNumTxPowerLevels(); lvl++) { mModemActivity.getTxTimeCounters()[lvl] - .addCountLocked(deltaInfo.getTransmitPowerInfo() - .get(lvl).getTimeInMillis()); + .addCountLocked(deltaInfo.getTransmitDurationMillisAtPowerLevel(lvl)); } // POWER_MODEM_CONTROLLER_OPERATING_VOLTAGE is measured in mV, so convert to V. @@ -11208,11 +11189,11 @@ public class BatteryStatsImpl extends BatteryStats { mPowerProfile.getAveragePower(PowerProfile.POWER_MODEM_CONTROLLER_IDLE) + deltaInfo.getReceiveTimeMillis() * mPowerProfile.getAveragePower(PowerProfile.POWER_MODEM_CONTROLLER_RX); - List<TransmitPower> txPowerInfo = deltaInfo.getTransmitPowerInfo(); - for (int i = 0; i < Math.min(txPowerInfo.size(), + for (int i = 0; i < Math.min(ModemActivityInfo.getNumTxPowerLevels(), CellSignalStrength.getNumSignalStrengthLevels()); i++) { - energyUsed += txPowerInfo.get(i).getTimeInMillis() * mPowerProfile - .getAveragePower(PowerProfile.POWER_MODEM_CONTROLLER_TX, i); + energyUsed += deltaInfo.getTransmitDurationMillisAtPowerLevel(i) + * mPowerProfile.getAveragePower( + PowerProfile.POWER_MODEM_CONTROLLER_TX, i); } // We store the power drain as mAms. @@ -11307,10 +11288,10 @@ public class BatteryStatsImpl extends BatteryStats { } if (totalTxPackets > 0 && entry.txPackets > 0) { - for (int lvl = 0; lvl < ModemActivityInfo.TX_POWER_LEVELS; lvl++) { - long txMs = - entry.txPackets * deltaInfo.getTransmitPowerInfo() - .get(lvl).getTimeInMillis(); + for (int lvl = 0; lvl < ModemActivityInfo.getNumTxPowerLevels(); + lvl++) { + long txMs = entry.txPackets + * deltaInfo.getTransmitDurationMillisAtPowerLevel(lvl); txMs /= totalTxPackets; activityCounter.getTxTimeCounters()[lvl].addCountLocked(txMs); } @@ -11341,20 +11322,16 @@ public class BatteryStatsImpl extends BatteryStats { if (activityInfo == null) { return; } - List<TransmitPower> txPowerInfo = activityInfo.getTransmitPowerInfo(); - if (txPowerInfo == null || txPowerInfo.size() != ModemActivityInfo.TX_POWER_LEVELS) { - return; - } final long elapsedRealtime = mClocks.elapsedRealtime(); final long uptime = mClocks.uptimeMillis(); int levelMaxTimeSpent = 0; - for (int i = 1; i < txPowerInfo.size(); i++) { - if (txPowerInfo.get(i).getTimeInMillis() > txPowerInfo.get(levelMaxTimeSpent) - .getTimeInMillis()) { + for (int i = 1; i < ModemActivityInfo.getNumTxPowerLevels(); i++) { + if (activityInfo.getTransmitDurationMillisAtPowerLevel(i) + > activityInfo.getTransmitDurationMillisAtPowerLevel(levelMaxTimeSpent)) { levelMaxTimeSpent = i; } } - if (levelMaxTimeSpent == ModemActivityInfo.TX_POWER_LEVELS - 1) { + if (levelMaxTimeSpent == ModemActivityInfo.getNumTxPowerLevels() - 1) { mHistoryCur.states2 |= HistoryItem.STATE2_CELLULAR_HIGH_TX_POWER_FLAG; addHistoryRecordLocked(elapsedRealtime, uptime); } @@ -12771,7 +12748,7 @@ public class BatteryStatsImpl extends BatteryStats { timeInRxSignalStrengthLevelMs[i] = getPhoneSignalStrengthTime(i, rawRealTime, which) / 1000; } - long[] txTimeMs = new long[Math.min(ModemActivityInfo.TX_POWER_LEVELS, + long[] txTimeMs = new long[Math.min(ModemActivityInfo.getNumTxPowerLevels(), counter.getTxTimeCounters().length)]; long totalTxTimeMs = 0; for (int i = 0; i < txTimeMs.length; i++) { @@ -14817,7 +14794,7 @@ public class BatteryStatsImpl extends BatteryStats { mBluetoothActivity = new ControllerActivityCounterImpl(mOnBatteryTimeBase, NUM_BT_TX_LEVELS, in); mModemActivity = new ControllerActivityCounterImpl(mOnBatteryTimeBase, - ModemActivityInfo.TX_POWER_LEVELS, in); + ModemActivityInfo.getNumTxPowerLevels(), in); mHasWifiReporting = in.readInt() != 0; mHasBluetoothReporting = in.readInt() != 0; mHasModemReporting = in.readInt() != 0; diff --git a/core/java/com/android/internal/os/OWNERS b/core/java/com/android/internal/os/OWNERS index ea3b3a7b4056..7766b77ab3c6 100644 --- a/core/java/com/android/internal/os/OWNERS +++ b/core/java/com/android/internal/os/OWNERS @@ -10,4 +10,6 @@ per-file BatteryUsageStats* = file:/BATTERY_STATS_OWNERS 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/java/com/android/internal/util/IState.java b/core/java/com/android/internal/util/IState.java index 07837bf8f587..41b3d5e0a11f 100644 --- a/core/java/com/android/internal/util/IState.java +++ b/core/java/com/android/internal/util/IState.java @@ -27,12 +27,12 @@ import android.os.Message; public interface IState { /** - * Returned by processMessage to indicate the the message was processed. + * Returned by processMessage to indicate the message was processed. */ static final boolean HANDLED = true; /** - * Returned by processMessage to indicate the the message was NOT processed. + * Returned by processMessage to indicate the message was NOT processed. */ static final boolean NOT_HANDLED = false; diff --git a/core/java/com/android/internal/util/State.java b/core/java/com/android/internal/util/State.java index 4613dad8cc67..d5c0f60f4b37 100644 --- a/core/java/com/android/internal/util/State.java +++ b/core/java/com/android/internal/util/State.java @@ -16,6 +16,7 @@ package com.android.internal.util; +import android.annotation.SuppressLint; import android.compat.annotation.UnsupportedAppUsage; import android.os.Build; import android.os.Message; @@ -25,6 +26,7 @@ import android.os.Message; * * The class for implementing states in a StateMachine */ +@SuppressLint("AndroidFrameworkRequiresPermission") public class State implements IState { /** diff --git a/core/java/com/android/internal/util/StateMachine.java b/core/java/com/android/internal/util/StateMachine.java index 4cff785cd671..cb8d9d127415 100644 --- a/core/java/com/android/internal/util/StateMachine.java +++ b/core/java/com/android/internal/util/StateMachine.java @@ -48,7 +48,7 @@ import java.util.Vector; * in Object Oriented programming and are used to perform initialization and * cleanup of the state respectively. The <code>getName</code> method returns the * name of the state; the default implementation returns the class name. It may be - * desirable to have <code>getName</code> return the the state instance name instead, + * desirable to have <code>getName</code> return the state instance name instead, * in particular if a particular state class has multiple instances.</p> * * <p>When a state machine is created, <code>addState</code> is used to build the @@ -433,14 +433,14 @@ public class StateMachine { /** * Convenience constant that maybe returned by processMessage - * to indicate the the message was processed and is not to be + * to indicate the message was processed and is not to be * processed by parent states */ public static final boolean HANDLED = true; /** * Convenience constant that maybe returned by processMessage - * to indicate the the message was NOT processed and is to be + * to indicate the message was NOT processed and is to be * processed by parent states */ public static final boolean NOT_HANDLED = false; diff --git a/core/jni/OWNERS b/core/jni/OWNERS index 701960eb4f11..6fb2904ab63c 100644 --- a/core/jni/OWNERS +++ b/core/jni/OWNERS @@ -75,3 +75,7 @@ per-file android_view_* = file:/graphics/java/android/graphics/OWNERS # VINTF per-file android_os_VintfObject* = file:platform/system/libvintf:/OWNERS per-file android_os_VintfRuntimeInfo* = file:platform/system/libvintf:/OWNERS + +# Battery +per-file com_android_internal_os_Kernel* = file:/BATTERY_STATS_OWNERS +per-file com_android_internal_os_*MultiStateCounter* = file:/BATTERY_STATS_OWNERS diff --git a/core/jni/android_os_Parcel.cpp b/core/jni/android_os_Parcel.cpp index 241570a7f9d3..ac320386c3f3 100644 --- a/core/jni/android_os_Parcel.cpp +++ b/core/jni/android_os_Parcel.cpp @@ -98,6 +98,15 @@ static void android_os_Parcel_markSensitive(jlong nativePtr) } } +static void android_os_Parcel_markForBinder(JNIEnv* env, jclass clazz, jlong nativePtr, + jobject binder) +{ + Parcel* parcel = reinterpret_cast<Parcel*>(nativePtr); + if (parcel) { + parcel->markForBinder(ibinderForJavaObject(env, binder)); + } +} + static jint android_os_Parcel_dataSize(jlong nativePtr) { Parcel* parcel = reinterpret_cast<Parcel*>(nativePtr); @@ -766,7 +775,9 @@ static jboolean android_os_Parcel_replaceCallingWorkSourceUid(jlong nativePtr, j static const JNINativeMethod gParcelMethods[] = { // @CriticalNative - {"nativeMarkSensitive", "(J)V", (void*)android_os_Parcel_markSensitive}, + {"nativeMarkSensitive", "(J)V", (void*)android_os_Parcel_markSensitive}, + // @FastNative + {"nativeMarkForBinder", "(JLandroid/os/IBinder;)V", (void*)android_os_Parcel_markForBinder}, // @CriticalNative {"nativeDataSize", "(J)I", (void*)android_os_Parcel_dataSize}, // @CriticalNative diff --git a/core/jni/com_android_internal_os_Zygote.cpp b/core/jni/com_android_internal_os_Zygote.cpp index 515c08d8517e..288327ef61ba 100644 --- a/core/jni/com_android_internal_os_Zygote.cpp +++ b/core/jni/com_android_internal_os_Zygote.cpp @@ -1129,14 +1129,14 @@ static void isolateAppDataPerPackage(int userId, std::string_view package_name, } // 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 @@ static void isolateAppData(JNIEnv* env, const std::vector<std::string>& merged_d 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 0135e45c9b54..35a3cde2d8f9 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 14d314775482..ac1912143dd7 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/public.xml b/core/res/res/values/public.xml index 6a4702be35c9..c1c185823d33 100644 --- a/core/res/res/values/public.xml +++ b/core/res/res/values/public.xml @@ -3046,6 +3046,9 @@ <public name="canPauseRecording" /> <!-- attribute definitions go here --> <public name="requireDeviceScreenOn" /> + </public-group> + + <public-group type="attr" first-id="0x01010624"> <public name="memtagMode" /> <public name="nativeHeapZeroInitialized" /> </public-group> diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index 2be5152ce2d9..aebad6afc042 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/res/res/xml/sms_short_codes.xml b/core/res/res/xml/sms_short_codes.xml index 37d059a575f0..f1c66c5891f2 100644 --- a/core/res/res/xml/sms_short_codes.xml +++ b/core/res/res/xml/sms_short_codes.xml @@ -265,7 +265,7 @@ <!-- USA: 5-6 digits (premium codes from https://www.premiumsmsrefunds.com/ShortCodes.htm), visual voicemail code for T-Mobile: 122 --> - <shortcode country="us" pattern="\\d{5,6}" premium="20433|21(?:344|472)|22715|23(?:333|847)|24(?:15|28)0|25209|27(?:449|606|663)|28498|305(?:00|83)|32(?:340|941)|33(?:166|786|849)|34746|35(?:182|564)|37975|38(?:135|146|254)|41(?:366|463)|42335|43(?:355|500)|44(?:578|711|811)|45814|46(?:157|173|327)|46666|47553|48(?:221|277|669)|50(?:844|920)|51(?:062|368)|52944|54(?:723|892)|55928|56483|57370|59(?:182|187|252|342)|60339|61(?:266|982)|62478|64(?:219|898)|65(?:108|500)|69(?:208|388)|70877|71851|72(?:078|087|465)|73(?:288|588|882|909|997)|74(?:034|332|815)|76426|79213|81946|83177|84(?:103|685)|85797|86(?:234|236|666)|89616|90(?:715|842|938)|91(?:362|958)|94719|95297|96(?:040|666|835|969)|97(?:142|294|688)|99(?:689|796|807)" standard="44567|244444" free="122|87902|21696|24614|28003|30356|33669|40196|41064|41270|43753|44034|46645|52413|56139|57969|61785|66975|75136|76227|81398|83952|85140|86566|86799|95737|96684|99245" /> + <shortcode country="us" pattern="\\d{5,6}" premium="20433|21(?:344|472)|22715|23(?:333|847)|24(?:15|28)0|25209|27(?:449|606|663)|28498|305(?:00|83)|32(?:340|941)|33(?:166|786|849)|34746|35(?:182|564)|37975|38(?:135|146|254)|41(?:366|463)|42335|43(?:355|500)|44(?:578|711|811)|45814|46(?:157|173|327)|46666|47553|48(?:221|277|669)|50(?:844|920)|51(?:062|368)|52944|54(?:723|892)|55928|56483|57370|59(?:182|187|252|342)|60339|61(?:266|982)|62478|64(?:219|898)|65(?:108|500)|69(?:208|388)|70877|71851|72(?:078|087|465)|73(?:288|588|882|909|997)|74(?:034|332|815)|76426|79213|81946|83177|84(?:103|685)|85797|86(?:234|236|666)|89616|90(?:715|842|938)|91(?:362|958)|94719|95297|96(?:040|666|835|969)|97(?:142|294|688)|99(?:689|796|807)" standard="44567|244444" free="122|87902|21696|24614|28003|30356|33669|40196|41064|41270|43753|44034|46645|52413|56139|57969|61785|66975|75136|76227|81398|83952|85140|86566|86799|95737|96684|99245|611611" /> <!-- Vietnam: 1-5 digits (standard system default, not country specific) --> <shortcode country="vn" pattern="\\d{1,5}" free="5001|9055" /> diff --git a/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java b/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java index 000e870369db..2d63351b8303 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 @@ public class ActivityThreadTest { 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 4cc70ba6448d..9d2cab3f7026 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 @@ import org.junit.runner.RunWith; @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 @@ public class BundleTest { // 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 f26d401bea1a..000000000000 --- 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 e25764ff0ad2..000000000000 --- 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 000000000000..d439124c72cb --- /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 d9e61512115d..000000000000 --- 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 000000000000..d0645b0d8b12 --- /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 2d8556f2a3c7..000000000000 --- 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 000000000000..c0c8aba2d2f0 --- /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 d7af2d9c3cfe..000000000000 --- 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 000000000000..91e6ddbb74c7 --- /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 000000000000..fe294168586d --- /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 236c7403aa8c..000000000000 --- 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 000000000000..c558153c6a4e --- /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 6f6ccfe8dee7..000000000000 --- 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 000000000000..15bc4efbd480 --- /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 33a46ea980c1..000000000000 --- 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 000000000000..de0657fc3957 --- /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 efc06886bee4..000000000000 --- 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 56f10fe075ed..b62b25c080fb 100644 --- a/core/tests/hosttests/test-apps/MultiDexLegacyTestServicesTests/Android.bp +++ b/core/tests/hosttests/test-apps/MultiDexLegacyTestServicesTests/Android.bp @@ -24,7 +24,7 @@ package { 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 000000000000..75c753cef0fd --- /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 3920fd64b03e..000000000000 --- 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 000000000000..23c62dc352c5 --- /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 2323ad98ac3d..000000000000 --- 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 000000000000..1cdf3af798d3 --- /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 000000000000..6cd3df71ddf4 --- /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 79a59063f702..000000000000 --- 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 000000000000..1cdf3af798d3 --- /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 000000000000..34dba4013aa3 --- /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 521bad058693..000000000000 --- 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 000000000000..1cdf3af798d3 --- /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/core/tests/utiltests/src/com/android/internal/util/StateMachineTest.java b/core/tests/utiltests/src/com/android/internal/util/StateMachineTest.java index edf473eac1b1..b85cb9cec47d 100644 --- a/core/tests/utiltests/src/com/android/internal/util/StateMachineTest.java +++ b/core/tests/utiltests/src/com/android/internal/util/StateMachineTest.java @@ -542,83 +542,83 @@ public class StateMachineTest extends TestCase { public void testStateMachineEnterExitTransitionToTest() throws Exception { //if (WAIT_FOR_DEBUGGER) Debug.waitForDebugger(); - StateMachineEnterExitTransitionToTest smEnterExitTranstionToTest = - new StateMachineEnterExitTransitionToTest("smEnterExitTranstionToTest"); - smEnterExitTranstionToTest.start(); - if (smEnterExitTranstionToTest.isDbg()) { + StateMachineEnterExitTransitionToTest smEnterExitTransitionToTest = + new StateMachineEnterExitTransitionToTest("smEnterExitTransitionToTest"); + smEnterExitTransitionToTest.start(); + if (smEnterExitTransitionToTest.isDbg()) { tlog("testStateMachineEnterExitTransitionToTest E"); } - synchronized (smEnterExitTranstionToTest) { - smEnterExitTranstionToTest.sendMessage(TEST_CMD_1); + synchronized (smEnterExitTransitionToTest) { + smEnterExitTransitionToTest.sendMessage(TEST_CMD_1); try { // wait for the messages to be handled - smEnterExitTranstionToTest.wait(); + smEnterExitTransitionToTest.wait(); } catch (InterruptedException e) { tloge("testStateMachineEnterExitTransitionToTest: exception while waiting " + e.getMessage()); } } - dumpLogRecs(smEnterExitTranstionToTest); + dumpLogRecs(smEnterExitTransitionToTest); - assertEquals(9, smEnterExitTranstionToTest.getLogRecCount()); + assertEquals(9, smEnterExitTransitionToTest.getLogRecCount()); LogRec lr; - lr = smEnterExitTranstionToTest.getLogRec(0); + lr = smEnterExitTransitionToTest.getLogRec(0); assertEquals(ENTER, lr.getInfo()); - assertEquals(smEnterExitTranstionToTest.mS1, lr.getState()); + assertEquals(smEnterExitTransitionToTest.mS1, lr.getState()); - lr = smEnterExitTranstionToTest.getLogRec(1); + lr = smEnterExitTransitionToTest.getLogRec(1); assertEquals(EXIT, lr.getInfo()); - assertEquals(smEnterExitTranstionToTest.mS1, lr.getState()); + assertEquals(smEnterExitTransitionToTest.mS1, lr.getState()); - lr = smEnterExitTranstionToTest.getLogRec(2); + lr = smEnterExitTransitionToTest.getLogRec(2); assertEquals(ENTER, lr.getInfo()); - assertEquals(smEnterExitTranstionToTest.mS2, lr.getState()); + assertEquals(smEnterExitTransitionToTest.mS2, lr.getState()); - lr = smEnterExitTranstionToTest.getLogRec(3); + lr = smEnterExitTransitionToTest.getLogRec(3); assertEquals(TEST_CMD_1, lr.getWhat()); - assertEquals(smEnterExitTranstionToTest.mS2, lr.getState()); - assertEquals(smEnterExitTranstionToTest.mS2, lr.getOriginalState()); - assertEquals(smEnterExitTranstionToTest.mS3, lr.getDestState()); + assertEquals(smEnterExitTransitionToTest.mS2, lr.getState()); + assertEquals(smEnterExitTransitionToTest.mS2, lr.getOriginalState()); + assertEquals(smEnterExitTransitionToTest.mS3, lr.getDestState()); - lr = smEnterExitTranstionToTest.getLogRec(4); + lr = smEnterExitTransitionToTest.getLogRec(4); assertEquals(TEST_CMD_1, lr.getWhat()); - assertEquals(smEnterExitTranstionToTest.mS2, lr.getState()); - assertEquals(smEnterExitTranstionToTest.mS2, lr.getOriginalState()); - assertEquals(smEnterExitTranstionToTest.mS4, lr.getDestState()); + assertEquals(smEnterExitTransitionToTest.mS2, lr.getState()); + assertEquals(smEnterExitTransitionToTest.mS2, lr.getOriginalState()); + assertEquals(smEnterExitTransitionToTest.mS4, lr.getDestState()); assertEquals(EXIT, lr.getInfo()); - lr = smEnterExitTranstionToTest.getLogRec(5); + lr = smEnterExitTransitionToTest.getLogRec(5); assertEquals(TEST_CMD_1, lr.getWhat()); assertEquals(ENTER, lr.getInfo()); - assertEquals(smEnterExitTranstionToTest.mS3, lr.getState()); - assertEquals(smEnterExitTranstionToTest.mS3, lr.getOriginalState()); - assertEquals(smEnterExitTranstionToTest.mS4, lr.getDestState()); + assertEquals(smEnterExitTransitionToTest.mS3, lr.getState()); + assertEquals(smEnterExitTransitionToTest.mS3, lr.getOriginalState()); + assertEquals(smEnterExitTransitionToTest.mS4, lr.getDestState()); - lr = smEnterExitTranstionToTest.getLogRec(6); + lr = smEnterExitTransitionToTest.getLogRec(6); assertEquals(TEST_CMD_1, lr.getWhat()); assertEquals(EXIT, lr.getInfo()); - assertEquals(smEnterExitTranstionToTest.mS3, lr.getState()); - assertEquals(smEnterExitTranstionToTest.mS3, lr.getOriginalState()); - assertEquals(smEnterExitTranstionToTest.mS4, lr.getDestState()); + assertEquals(smEnterExitTransitionToTest.mS3, lr.getState()); + assertEquals(smEnterExitTransitionToTest.mS3, lr.getOriginalState()); + assertEquals(smEnterExitTransitionToTest.mS4, lr.getDestState()); - lr = smEnterExitTranstionToTest.getLogRec(7); + lr = smEnterExitTransitionToTest.getLogRec(7); assertEquals(TEST_CMD_1, lr.getWhat()); assertEquals(ENTER, lr.getInfo()); - assertEquals(smEnterExitTranstionToTest.mS4, lr.getState()); - assertEquals(smEnterExitTranstionToTest.mS4, lr.getOriginalState()); - assertEquals(smEnterExitTranstionToTest.mS4, lr.getDestState()); + assertEquals(smEnterExitTransitionToTest.mS4, lr.getState()); + assertEquals(smEnterExitTransitionToTest.mS4, lr.getOriginalState()); + assertEquals(smEnterExitTransitionToTest.mS4, lr.getDestState()); - lr = smEnterExitTranstionToTest.getLogRec(8); + lr = smEnterExitTransitionToTest.getLogRec(8); assertEquals(TEST_CMD_1, lr.getWhat()); assertEquals(EXIT, lr.getInfo()); - assertEquals(smEnterExitTranstionToTest.mS4, lr.getState()); - assertEquals(smEnterExitTranstionToTest.mS4, lr.getOriginalState()); + assertEquals(smEnterExitTransitionToTest.mS4, lr.getState()); + assertEquals(smEnterExitTransitionToTest.mS4, lr.getOriginalState()); - if (smEnterExitTranstionToTest.isDbg()) { + if (smEnterExitTransitionToTest.isDbg()) { tlog("testStateMachineEnterExitTransitionToTest X"); } } diff --git a/identity/java/android/security/identity/IdentityCredential.java b/identity/java/android/security/identity/IdentityCredential.java index 8f175bb63edb..1e685856d011 100644 --- a/identity/java/android/security/identity/IdentityCredential.java +++ b/identity/java/android/security/identity/IdentityCredential.java @@ -160,7 +160,7 @@ public abstract class IdentityCredential { * 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 @@ public abstract class IdentityCredential { * 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/Readback.cpp b/libs/hwui/Readback.cpp index e08b99d52cdb..d897e94f969f 100644 --- a/libs/hwui/Readback.cpp +++ b/libs/hwui/Readback.cpp @@ -32,6 +32,8 @@ using namespace android::uirenderer::renderthread; +static constexpr bool sEnableExtraCropInset = true; + namespace android { namespace uirenderer { @@ -71,6 +73,20 @@ CopyResult Readback::copySurfaceInto(ANativeWindow* window, const Rect& inSrcRec ALOGW("Surface doesn't have any previously queued frames, nothing to readback from"); return CopyResult::SourceEmpty; } + + if (sEnableExtraCropInset && + (cropRect.right - cropRect.left != bitmap->width() || + cropRect.bottom - cropRect.top != bitmap->height())) { + /* + * When we need use filtering, we should also make border shrink here like gui. + * But we could not check format for YUV or RGB here... Just use 1 pix. + */ + cropRect.left += 0.5f; + cropRect.top += 0.5f; + cropRect.right -= 0.5f; + cropRect.bottom -= 0.5f; + } + UniqueAHardwareBuffer sourceBuffer{rawSourceBuffer}; AHardwareBuffer_Desc description; AHardwareBuffer_describe(sourceBuffer.get(), &description); diff --git a/libs/hwui/jni/PaintFilter.cpp b/libs/hwui/jni/PaintFilter.cpp index ec115b4e141c..86d4742b949e 100644 --- a/libs/hwui/jni/PaintFilter.cpp +++ b/libs/hwui/jni/PaintFilter.cpp @@ -74,7 +74,7 @@ int register_android_graphics_DrawFilter(JNIEnv* env) { 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 9657b25e7c18..c7c503d64801 100644 --- a/media/java/android/media/MediaCodecInfo.java +++ b/media/java/android/media/MediaCodecInfo.java @@ -2178,12 +2178,6 @@ public final class MediaCodecInfo { 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 559a61daec38..0f9e89a497d2 100644 --- a/media/java/android/media/MediaMetadataRetriever.java +++ b/media/java/android/media/MediaMetadataRetriever.java @@ -900,7 +900,7 @@ public class MediaMetadataRetriever implements AutoCloseable { 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 @@ public class MediaMetadataRetriever implements AutoCloseable { 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 0d53ab152129..2691983a0e3a 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,22 +58,15 @@ static fields_t fields; struct effect_callback_cookie { jclass audioEffect_class; // AudioEffect class jobject audioEffect_ref; // AudioEffect object instance - }; + bool busy; + Condition cond; +}; // ---------------------------------------------------------------------------- -class AudioEffectJniStorage { - public: - effect_callback_cookie mCallbackData; - - AudioEffectJniStorage() { - } - - ~AudioEffectJniStorage() { - } - +struct AudioEffectJniStorage { + effect_callback_cookie mCallbackData{}; }; - jint AudioEffectJni::translateNativeErrorToJava(int code) { switch(code) { case NO_ERROR: @@ -101,6 +94,7 @@ jint AudioEffectJni::translateNativeErrorToJava(int code) { } static Mutex sLock; +static std::unordered_set<effect_callback_cookie*> sAudioEffectCallBackCookies; // ---------------------------------------------------------------------------- static void effectCallback(int event, void* user, void *info) { @@ -121,7 +115,13 @@ static void effectCallback(int event, void* user, void *info) { 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 @@ effectCallback_Exit: env->ExceptionDescribe(); env->ExceptionClear(); } + { + Mutex::Autolock l(sLock); + callbackInfo->busy = false; + callbackInfo->cond.broadcast(); + } } // ---------------------------------------------------------------------------- @@ -396,6 +401,10 @@ android_media_AudioEffect_native_setup(JNIEnv *env, jobject thiz, jobject weak_t 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 @@ setup_failure: // ---------------------------------------------------------------------------- +#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 @@ static void android_media_AudioEffect_native_release(JNIEnv *env, jobject thiz) 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 4c5970a30a05..609fafee4d6a 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 @@ struct visualizer_callback_cookie { 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 @@ struct visualizer_callback_cookie { // 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 @@ struct visualizer_callback_cookie { }; // ---------------------------------------------------------------------------- -class VisualizerJniStorage { - public: - visualizer_callback_cookie mCallbackData; - - VisualizerJniStorage() { - } - - ~VisualizerJniStorage() { - } +struct VisualizerJniStorage { + visualizer_callback_cookie mCallbackData{}; }; @@ -136,6 +134,7 @@ static jint translateError(int code) { } 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 @@ static void captureCallback(void* user, 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 @@ static void captureCallback(void* user, 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 @@ static void android_media_visualizer_effect_callback(int32_t event, 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 @@ android_media_visualizer_native_setup(JNIEnv *env, jobject thiz, jobject weak_th } lpVisualizer->set(0, android_media_visualizer_effect_callback, - lpJniStorage, + &lpJniStorage->mCallbackData, (audio_session_t) sessionId); lStatus = translateError(lpVisualizer->initCheck()); @@ -410,6 +448,10 @@ android_media_visualizer_native_setup(JNIEnv *env, jobject thiz, jobject weak_th 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 @@ setup_failure: } // ---------------------------------------------------------------------------- +#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 @@ static void android_media_visualizer_native_release(JNIEnv *env, jobject thiz) 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 @@ int register_android_media_visualizer(JNIEnv *env) { return AndroidRuntime::registerNativeMethods(env, kClassPathName, gMethods, NELEM(gMethods)); } - diff --git a/media/tests/EffectsTest/AndroidManifest.xml b/media/tests/EffectsTest/AndroidManifest.xml index 9b59891fb279..ad0c10ee3807 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 ac912c84d107..5f9132cb8cc7 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 18d7a3648fbf..85dabbc115f3 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 7c12da1274e3..a44c7e93382a 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 cce2acc5869a..a207bf1d5359 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.CompoundButton; +import android.widget.CompoundButton.OnCheckedChangeListener; import android.widget.EditText; import android.widget.SeekBar; +import android.widget.TextView; import android.widget.ToggleButton; -import android.widget.CompoundButton; -import android.widget.CompoundButton.OnCheckedChangeListener; -import java.nio.ByteOrder; + import java.nio.ByteBuffer; +import java.nio.ByteOrder; import java.util.HashMap; -import java.util.Map; - -import android.media.audiofx.BassBoost; -import android.media.audiofx.AudioEffect; public class BassBoostTest extends Activity implements OnCheckedChangeListener { @@ -78,6 +73,9 @@ public class BassBoostTest extends Activity implements OnCheckedChangeListener { 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 @@ public class BassBoostTest extends Activity implements OnCheckedChangeListener { 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 @@ public class BassBoostTest extends Activity implements OnCheckedChangeListener { } } + // 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 2e141c5ef7c8..dcfe11a79ef9 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.os.Message; 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 @@ public class VisualizerTest extends Activity implements OnCheckedChangeListener 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 @@ public class VisualizerTest extends Activity implements OnCheckedChangeListener } } + // 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 59d8acb82196..70baf1d84470 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 @@ package com.android.settingslib.bluetooth; 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 @@ public class BluetoothEventManager { 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 @@ public class BluetoothEventManager { 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 @@ public class BluetoothEventManager { } cachedDevice.setRssi(rssi); cachedDevice.setJustDiscovered(true); + cachedDevice.setIsCoordinatedSetMember(isCoordinatedSetMember); } } @@ -335,6 +342,12 @@ public class BluetoothEventManager { } 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 @@ public class BluetoothEventManager { 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 @@ public class BluetoothEventManager { 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 4c80b91f300d..78fc139894d1 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 @@ package com.android.settingslib.bluetooth; 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 com.android.settingslib.Utils; 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 @@ public class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice> 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 @@ public class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice> boolean mJustDiscovered; + boolean mIsCoordinatedSetMember = false; + private final Collection<Callback> mCallbacks = new CopyOnWriteArrayList<>(); /** @@ -98,6 +104,8 @@ public class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice> 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 @@ public class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice> mDevice = device; fillData(); mHiSyncId = BluetoothHearingAid.HI_SYNC_ID_INVALID; + mGroupId = BluetoothCsipSetCoordinator.GROUP_ID_INVALID; } /** @@ -297,6 +306,42 @@ public class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice> 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 @@ public class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice> 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 cca9cfac2d22..02566155f1c9 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 @@ package com.android.settingslib.bluetooth; 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 com.android.internal.annotations.VisibleForTesting; 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 @@ public class CachedBluetoothDeviceManager { 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 @@ public class CachedBluetoothDeviceManager { 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 @@ public class CachedBluetoothDeviceManager { 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 @@ public class CachedBluetoothDeviceManager { } /** - * 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 @@ public class CachedBluetoothDeviceManager { * 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 @@ public class CachedBluetoothDeviceManager { } /** + * 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 @@ public class CachedBluetoothDeviceManager { 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 @@ public class CachedBluetoothDeviceManager { 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 @@ public class CachedBluetoothDeviceManager { 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 class CachedBluetoothDeviceManager { } 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 @@ public class CachedBluetoothDeviceManager { } } + /** + * 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 000000000000..347e14b91656 --- /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 000000000000..6da249c2980e --- /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 c4cb6a1e7197..24113c5a0261 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 @@ package com.android.settingslib.bluetooth; 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 @@ public class LocalBluetoothProfileManager { private PbapClientProfile mPbapClientProfile; private PbapServerProfile mPbapProfile; private HearingAidProfile mHearingAidProfile; + private CsipSetCoordinatorProfile mCsipSetCoordinatorProfile; private SapProfile mSapProfile; private VolumeControlProfile mVolumeControlProfile; @@ -230,7 +232,16 @@ public class LocalBluetoothProfileManager { // 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 @@ public class LocalBluetoothProfileManager { } } } + + 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 @@ public class LocalBluetoothProfileManager { 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 @@ public class LocalBluetoothProfileManager { 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 fd5b05311247..4f8fa2fdb96e 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 @@ public class LocalBluetoothProfileManagerTest { mContext.sendBroadcast(mIntent); verify(mDeviceManager).onProfileConnectionStateChangedIfProcessed(mCachedBluetoothDevice, - BluetoothProfile.STATE_CONNECTED); + BluetoothProfile.STATE_CONNECTED, BluetoothProfile.HEARING_AID); } /** diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml index 10600e37b220..0e9a51dbb63c 100644 --- a/packages/Shell/AndroidManifest.xml +++ b/packages/Shell/AndroidManifest.xml @@ -513,7 +513,7 @@ <provider android:name=".HeapDumpProvider" android:authorities="com.android.shell.heapdump" android:grantUriPermissions="true" - android:exported="true" /> + android:exported="false" /> <activity android:name=".BugreportWarningActivity" diff --git a/services/OWNERS b/services/OWNERS index b7128a32fcee..a08331996556 100644 --- a/services/OWNERS +++ b/services/OWNERS @@ -4,3 +4,4 @@ per-file Android.bp = file:platform/build/soong:/OWNERS 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/BluetoothManagerService.java b/services/core/java/com/android/server/BluetoothManagerService.java index 00cc75352633..bdeb4d56bfa4 100644 --- a/services/core/java/com/android/server/BluetoothManagerService.java +++ b/services/core/java/com/android/server/BluetoothManagerService.java @@ -175,8 +175,6 @@ class BluetoothManagerService extends IBluetoothManager.Stub { private final ReentrantReadWriteLock mBluetoothLock = new ReentrantReadWriteLock(); private boolean mBinding; private boolean mUnbinding; - private int mWaitForEnableRetry; - private int mWaitForDisableRetry; private BluetoothModeChangeHelper mBluetoothModeChangeHelper; @@ -933,14 +931,15 @@ class BluetoothManagerService extends IBluetoothManager.Stub { if (mState == BluetoothAdapter.STATE_ON || mState == BluetoothAdapter.STATE_BLE_ON || mState == BluetoothAdapter.STATE_TURNING_ON - || mState == BluetoothAdapter.STATE_TURNING_OFF) { - Log.d(TAG, "enableBLE(): Bluetooth already enabled"); + || mState == BluetoothAdapter.STATE_TURNING_OFF + || mState == BluetoothAdapter.STATE_BLE_TURNING_ON) { + Log.d(TAG, "enableBLE(): Bluetooth is already enabled or is turning on"); return true; } synchronized (mReceiver) { // waive WRITE_SECURE_SETTINGS permission check - sendEnableMsg(false, - BluetoothProtoEnums.ENABLE_DISABLE_REASON_APPLICATION_REQUEST, packageName); + sendEnableMsg(false, BluetoothProtoEnums.ENABLE_DISABLE_REASON_APPLICATION_REQUEST, + packageName, true); } return true; } @@ -1734,6 +1733,8 @@ class BluetoothManagerService extends IBluetoothManager.Stub { private class BluetoothHandler extends Handler { boolean mGetNameAddressOnly = false; + private int mWaitForEnableRetry; + private int mWaitForDisableRetry; BluetoothHandler(Looper looper) { super(looper); @@ -1781,11 +1782,12 @@ class BluetoothManagerService extends IBluetoothManager.Stub { case MESSAGE_ENABLE: int quietEnable = msg.arg1; + int isBle = msg.arg2; if (mHandler.hasMessages(MESSAGE_HANDLE_DISABLE_DELAYED) || mHandler.hasMessages(MESSAGE_HANDLE_ENABLE_DELAYED)) { // We are handling enable or disable right now, wait for it. mHandler.sendMessageDelayed(mHandler.obtainMessage(MESSAGE_ENABLE, - quietEnable, 0), ENABLE_DISABLE_DELAY_MS); + quietEnable, isBle), ENABLE_DISABLE_DELAY_MS); break; } @@ -1800,13 +1802,28 @@ class BluetoothManagerService extends IBluetoothManager.Stub { try { mBluetoothLock.readLock().lock(); if (mBluetooth != null) { + boolean isHandled = true; int state = mBluetooth.getState(); - if (state == BluetoothAdapter.STATE_BLE_ON) { - Slog.w(TAG, "BT Enable in BLE_ON State, going to ON"); - mBluetooth.onLeServiceUp(); - persistBluetoothSetting(BLUETOOTH_ON_BLUETOOTH); - break; + switch (state) { + case BluetoothAdapter.STATE_BLE_ON: + if (isBle == 1) { + Slog.i(TAG, "Already at BLE_ON State"); + } else { + Slog.w(TAG, "BT Enable in BLE_ON State, going to ON"); + mBluetooth.onLeServiceUp(); + persistBluetoothSetting(BLUETOOTH_ON_BLUETOOTH); + } + break; + case BluetoothAdapter.STATE_BLE_TURNING_ON: + case BluetoothAdapter.STATE_TURNING_ON: + case BluetoothAdapter.STATE_ON: + Slog.i(TAG, "MESSAGE_ENABLE: already enabled"); + break; + default: + isHandled = false; + break; } + if (isHandled) break; } } catch (RemoteException e) { Slog.e(TAG, "", e); @@ -2559,7 +2576,12 @@ class BluetoothManagerService extends IBluetoothManager.Stub { } private void sendEnableMsg(boolean quietMode, int reason, String packageName) { - mHandler.sendMessage(mHandler.obtainMessage(MESSAGE_ENABLE, quietMode ? 1 : 0, 0)); + sendEnableMsg(quietMode, reason, packageName, false); + } + + private void sendEnableMsg(boolean quietMode, int reason, String packageName, boolean isBle) { + mHandler.sendMessage(mHandler.obtainMessage(MESSAGE_ENABLE, quietMode ? 1 : 0, + isBle ? 1 : 0)); addActiveLog(reason, packageName, true); mLastEnabledTime = SystemClock.elapsedRealtime(); } diff --git a/services/core/java/com/android/server/NsdService.java b/services/core/java/com/android/server/NsdService.java index a9f3a1b63b40..462ed5c70d36 100644 --- a/services/core/java/com/android/server/NsdService.java +++ b/services/core/java/com/android/server/NsdService.java @@ -82,6 +82,8 @@ public class NsdService extends INsdManager.Stub { 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 @@ public class NsdService extends INsdManager.Stub { 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 @@ public class NsdService extends INsdManager.Stub { 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 @@ public class NsdService extends INsdManager.Stub { 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 @@ public class NsdService extends INsdManager.Stub { /* 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 @@ public class NsdService extends INsdManager.Stub { 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 @@ public class NsdService extends INsdManager.Stub { 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/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java index b1ffaeb50912..6d85273de142 100644 --- a/services/core/java/com/android/server/am/ActiveServices.java +++ b/services/core/java/com/android/server/am/ActiveServices.java @@ -734,11 +734,8 @@ public final class ActiveServices { } ComponentName cmp = startServiceInnerLocked(smap, service, r, callerFg, addToStarting); - if (!r.mAllowWhileInUsePermissionInFgs) { - r.mAllowWhileInUsePermissionInFgs = - shouldAllowWhileInUsePermissionInFgsLocked(callingPackage, callingPid, - callingUid, service, r, allowBackgroundActivityStarts); - } + setFgsRestrictionLocked(callingPackage, callingPid, callingUid, r, + allowBackgroundActivityStarts); return cmp; } @@ -1411,14 +1408,6 @@ public final class ActiveServices { + String.format("0x%08X", manifestType) + " in service element of manifest file"); } - // If the foreground service is not started from TOP process, do not allow it to - // have while-in-use location/camera/microphone access. - if (!r.mAllowWhileInUsePermissionInFgs) { - Slog.w(TAG, - "Foreground service started from background can not have " - + "location/camera/microphone access: service " - + r.shortInstanceName); - } } boolean alreadyStartedOp = false; boolean stopProcStatsOp = false; @@ -1466,6 +1455,57 @@ public final class ActiveServices { ignoreForeground = true; } + if (!ignoreForeground) { + if (r.mStartForegroundCount == 0) { + /* + If the service was started with startService(), not + startForegroundService(), and if startForeground() isn't called within + mFgsStartForegroundTimeoutMs, then we check the state of the app + (who owns the service, which is the app that called startForeground()) + again. If the app is in the foreground, or in any other cases where + FGS-starts are allowed, then we still allow the FGS to be started. + Otherwise, startForeground() would fail. + + If the service was started with startForegroundService(), then the service + must call startForeground() within a timeout anyway, so we don't need this + check. + */ + if (!r.fgRequired) { + final long delayMs = SystemClock.elapsedRealtime() - r.createRealTime; + if (delayMs > mAm.mConstants.mFgsStartForegroundTimeoutMs) { + resetFgsRestrictionLocked(r); + setFgsRestrictionLocked(r.serviceInfo.packageName, r.app.pid, + r.appInfo.uid, r, false); + EventLog.writeEvent(0x534e4554, "183147114", + r.appInfo.uid, + "call setFgsRestrictionLocked again due to " + + "startForegroundTimeout"); + } + } + } else if (r.mStartForegroundCount >= 1) { + // The second or later time startForeground() is called after service is + // started. Check for app state again. + final long delayMs = SystemClock.elapsedRealtime() - + r.mLastSetFgsRestrictionTime; + if (delayMs > mAm.mConstants.mFgsStartForegroundTimeoutMs) { + resetFgsRestrictionLocked(r); + setFgsRestrictionLocked(r.serviceInfo.packageName, r.app.pid, + r.appInfo.uid, r, false); + EventLog.writeEvent(0x534e4554, "183147114", r.appInfo.uid, + "call setFgsRestrictionLocked for " + + (r.mStartForegroundCount + 1) + "th startForeground"); + } + } + // If the foreground service is not started from TOP process, do not allow it to + // have while-in-use location/camera/microphone access. + if (!r.mAllowWhileInUsePermissionInFgs) { + Slog.w(TAG, + "Foreground service started from background can not have " + + "location/camera/microphone access: service " + + r.shortInstanceName); + } + } + // Apps under strict background restrictions simply don't get to have foreground // services, so now that we've enforced the startForegroundService() contract // we only do the machinery of making the service foreground when the app @@ -1501,6 +1541,7 @@ public final class ActiveServices { active.mNumActive++; } r.isForeground = true; + r.mStartForegroundCount++; if (!stopProcStatsOp) { ServiceState stracker = r.getTracker(); if (stracker != null) { @@ -1559,6 +1600,7 @@ public final class ActiveServices { decActiveForegroundAppLocked(smap, r); } r.isForeground = false; + resetFgsRestrictionLocked(r); ServiceState stracker = r.getTracker(); if (stracker != null) { stracker.setForeground(false, mAm.mProcessStats.getMemFactorLocked(), @@ -2118,12 +2160,7 @@ public final class ActiveServices { } } - if (!s.mAllowWhileInUsePermissionInFgs) { - s.mAllowWhileInUsePermissionInFgs = - shouldAllowWhileInUsePermissionInFgsLocked(callingPackage, - callingPid, callingUid, - service, s, false); - } + setFgsRestrictionLocked(callingPackage, callingPid, callingUid, s, false); if (s.app != null) { if ((flags&Context.BIND_TREAT_LIKE_ACTIVITY) != 0) { @@ -3419,7 +3456,7 @@ public final class ActiveServices { r.isForeground = false; r.foregroundId = 0; r.foregroundNoti = null; - r.mAllowWhileInUsePermissionInFgs = false; + resetFgsRestrictionLocked(r); // Clear start entries. r.clearDeliveredStartsLocked(); @@ -4900,7 +4937,7 @@ public final class ActiveServices { * @return true if allow, false otherwise. */ private boolean shouldAllowWhileInUsePermissionInFgsLocked(String callingPackage, - int callingPid, int callingUid, Intent intent, ServiceRecord r, + int callingPid, int callingUid, ServiceRecord r, boolean allowBackgroundActivityStarts) { // Is the background FGS start restriction turned on? if (!mAm.mConstants.mFlagBackgroundFgsStartRestrictionEnabled) { @@ -4986,6 +5023,28 @@ public final class ActiveServices { boolean canAllowWhileInUsePermissionInFgsLocked(int callingPid, int callingUid, String callingPackage) { return shouldAllowWhileInUsePermissionInFgsLocked( - callingPackage, callingPid, callingUid, null, null, false); + callingPackage, callingPid, callingUid, null, false); + } + + /** + * In R, mAllowWhileInUsePermissionInFgs is to allow while-in-use permissions in foreground + * service or not. while-in-use permissions in FGS started from background might be restricted. + * @param callingPackage caller app's package name. + * @param callingUid caller app's uid. + * @param r the service to start. + * @return true if allow, false otherwise. + */ + private void setFgsRestrictionLocked(String callingPackage, + int callingPid, int callingUid, ServiceRecord r, + boolean allowBackgroundActivityStarts) { + r.mLastSetFgsRestrictionTime = SystemClock.elapsedRealtime(); + if (!r.mAllowWhileInUsePermissionInFgs) { + r.mAllowWhileInUsePermissionInFgs = shouldAllowWhileInUsePermissionInFgsLocked( + callingPackage, callingPid, callingUid, r, allowBackgroundActivityStarts); + } + } + + private void resetFgsRestrictionLocked(ServiceRecord r) { + r.mAllowWhileInUsePermissionInFgs = false; } } diff --git a/services/core/java/com/android/server/am/ActivityManagerConstants.java b/services/core/java/com/android/server/am/ActivityManagerConstants.java index 7be843f17863..00d8208ea118 100644 --- a/services/core/java/com/android/server/am/ActivityManagerConstants.java +++ b/services/core/java/com/android/server/am/ActivityManagerConstants.java @@ -88,6 +88,7 @@ final class ActivityManagerConstants extends ContentObserver { static final String KEY_PROCESS_START_ASYNC = "process_start_async"; static final String KEY_MEMORY_INFO_THROTTLE_TIME = "memory_info_throttle_time"; static final String KEY_TOP_TO_FGS_GRACE_DURATION = "top_to_fgs_grace_duration"; + static final String KEY_FGS_START_FOREGROUND_TIMEOUT = "fgs_start_foreground_timeout"; static final String KEY_PENDINGINTENT_WARNING_THRESHOLD = "pendingintent_warning_threshold"; private static final int DEFAULT_MAX_CACHED_PROCESSES = 32; @@ -121,6 +122,7 @@ final class ActivityManagerConstants extends ContentObserver { private static final boolean DEFAULT_PROCESS_START_ASYNC = true; private static final long DEFAULT_MEMORY_INFO_THROTTLE_TIME = 5*60*1000; private static final long DEFAULT_TOP_TO_FGS_GRACE_DURATION = 15 * 1000; + private static final int DEFAULT_FGS_START_FOREGROUND_TIMEOUT_MS = 10 * 1000; private static final int DEFAULT_PENDINGINTENT_WARNING_THRESHOLD = 2000; // Flag stored in the DeviceConfig API. @@ -273,6 +275,12 @@ final class ActivityManagerConstants extends ContentObserver { // this long. public long TOP_TO_FGS_GRACE_DURATION = DEFAULT_TOP_TO_FGS_GRACE_DURATION; + /** + * When service started from background, before the timeout it can be promoted to FGS by calling + * Service.startForeground(). + */ + volatile long mFgsStartForegroundTimeoutMs = DEFAULT_FGS_START_FOREGROUND_TIMEOUT_MS; + // Indicates whether the activity starts logging is enabled. // Controlled by Settings.Global.ACTIVITY_STARTS_LOGGING_ENABLED volatile boolean mFlagActivityStartsLoggingEnabled; @@ -421,6 +429,9 @@ final class ActivityManagerConstants extends ContentObserver { case KEY_MIN_ASSOC_LOG_DURATION: updateMinAssocLogDuration(); break; + case KEY_FGS_START_FOREGROUND_TIMEOUT: + updateFgsStartForegroundTimeout(); + break; default: break; } @@ -697,6 +708,13 @@ final class ActivityManagerConstants extends ContentObserver { /* defaultValue */ DEFAULT_MIN_ASSOC_LOG_DURATION); } + private void updateFgsStartForegroundTimeout() { + mFgsStartForegroundTimeoutMs = DeviceConfig.getLong( + DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, + KEY_FGS_START_FOREGROUND_TIMEOUT, + DEFAULT_FGS_START_FOREGROUND_TIMEOUT_MS); + } + void dump(PrintWriter pw) { pw.println("ACTIVITY MANAGER SETTINGS (dumpsys activity settings) " + Settings.Global.ACTIVITY_MANAGER_CONSTANTS + ":"); @@ -769,6 +787,8 @@ final class ActivityManagerConstants extends ContentObserver { pw.println(Arrays.toString(IMPERCEPTIBLE_KILL_EXEMPT_PACKAGES.toArray())); pw.print(" "); pw.print(KEY_MIN_ASSOC_LOG_DURATION); pw.print("="); pw.println(MIN_ASSOC_LOG_DURATION); + pw.print(" "); pw.print(KEY_FGS_START_FOREGROUND_TIMEOUT); pw.print("="); + pw.println(mFgsStartForegroundTimeoutMs); pw.println(); if (mOverrideMaxCachedProcesses >= 0) { diff --git a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java index af8990778773..50f3520e6291 100644 --- a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java +++ b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java @@ -94,7 +94,6 @@ import com.android.internal.util.MemInfoReader; import com.android.server.compat.PlatformCompat; import java.io.BufferedReader; -import java.io.File; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; @@ -787,8 +786,7 @@ final class ActivityManagerShellCommand extends ShellCommand { return -1; } - File file = new File(filename); - file.delete(); + // Writes an error message to stderr on failure ParcelFileDescriptor fd = openFileForSystem(filename, "w"); if (fd == null) { return -1; @@ -942,16 +940,16 @@ final class ActivityManagerShellCommand extends ShellCommand { String logNameTimeString = LOG_NAME_TIME_FORMATTER.format(localDateTime); heapFile = "/data/local/tmp/heapdump-" + logNameTimeString + ".prof"; } - pw.println("File: " + heapFile); - pw.flush(); - File file = new File(heapFile); - file.delete(); + // Writes an error message to stderr on failure ParcelFileDescriptor fd = openFileForSystem(heapFile, "w"); if (fd == null) { return -1; } + pw.println("File: " + heapFile); + pw.flush(); + final CountDownLatch latch = new CountDownLatch(1); final RemoteCallback finishCallback = new RemoteCallback(new OnResultListener() { diff --git a/services/core/java/com/android/server/am/BatteryExternalStatsWorker.java b/services/core/java/com/android/server/am/BatteryExternalStatsWorker.java index 39f79ca2f13b..ef47b1ebc7ec 100644 --- a/services/core/java/com/android/server/am/BatteryExternalStatsWorker.java +++ b/services/core/java/com/android/server/am/BatteryExternalStatsWorker.java @@ -22,6 +22,7 @@ import android.content.Context; import android.net.wifi.WifiManager; import android.os.BatteryStats; import android.os.Bundle; +import android.os.OutcomeReceiver; import android.os.Parcelable; import android.os.Process; import android.os.ServiceManager; @@ -40,6 +41,7 @@ import com.android.internal.os.BatteryStatsImpl; import com.android.internal.util.FrameworkStatsLog; import com.android.internal.util.function.pooled.PooledLambda; +import java.util.concurrent.ExecutionException; import libcore.util.EmptyArray; import java.util.concurrent.CompletableFuture; @@ -405,7 +407,7 @@ class BatteryExternalStatsWorker implements BatteryStatsImpl.ExternalStatsSync { // We will request data from external processes asynchronously, and wait on a timeout. SynchronousResultReceiver wifiReceiver = null; SynchronousResultReceiver bluetoothReceiver = null; - SynchronousResultReceiver modemReceiver = null; + CompletableFuture<ModemActivityInfo> modemFuture = CompletableFuture.completedFuture(null); boolean railUpdated = false; if ((updateFlags & BatteryStatsImpl.ExternalStatsSync.UPDATE_WIFI) != 0) { @@ -460,8 +462,22 @@ class BatteryExternalStatsWorker implements BatteryStatsImpl.ExternalStatsSync { } if (mTelephony != null) { - modemReceiver = new SynchronousResultReceiver("telephony"); - mTelephony.requestModemActivityInfo(modemReceiver); + CompletableFuture<ModemActivityInfo> temp = new CompletableFuture<>(); + mTelephony.requestModemActivityInfo(Runnable::run, + new OutcomeReceiver<ModemActivityInfo, + TelephonyManager.ModemActivityInfoException>() { + @Override + public void onResult(ModemActivityInfo result) { + temp.complete(result); + } + + @Override + public void onError(TelephonyManager.ModemActivityInfoException e) { + Slog.w(TAG, "error reading modem stats:" + e); + temp.complete(null); + } + }); + modemFuture = temp; } if (!railUpdated) { synchronized (mStats) { @@ -472,7 +488,17 @@ class BatteryExternalStatsWorker implements BatteryStatsImpl.ExternalStatsSync { final WifiActivityEnergyInfo wifiInfo = awaitControllerInfo(wifiReceiver); final BluetoothActivityEnergyInfo bluetoothInfo = awaitControllerInfo(bluetoothReceiver); - final ModemActivityInfo modemInfo = awaitControllerInfo(modemReceiver); + ModemActivityInfo modemInfo = null; + try { + modemInfo = modemFuture.get(EXTERNAL_STATS_SYNC_TIMEOUT_MILLIS, + TimeUnit.MILLISECONDS); + } catch (TimeoutException | InterruptedException e) { + Slog.w(TAG, "timeout or interrupt reading modem stats: " + e); + } catch (ExecutionException e) { + Slog.w(TAG, "exception reading modem stats: " + e.getCause()); + } + final long elapsedRealtime = SystemClock.elapsedRealtime(); + final long uptime = SystemClock.uptimeMillis(); synchronized (mStats) { mStats.addHistoryEventLocked( @@ -519,11 +545,7 @@ class BatteryExternalStatsWorker implements BatteryStatsImpl.ExternalStatsSync { } if (modemInfo != null) { - if (modemInfo.isValid()) { - mStats.updateMobileRadioState(modemInfo); - } else { - Slog.w(TAG, "modem info is invalid: " + modemInfo); - } + mStats.updateMobileRadioState(modemInfo); } } diff --git a/services/core/java/com/android/server/am/BatteryStatsService.java b/services/core/java/com/android/server/am/BatteryStatsService.java index 580ceca3b197..34ba3e078860 100644 --- a/services/core/java/com/android/server/am/BatteryStatsService.java +++ b/services/core/java/com/android/server/am/BatteryStatsService.java @@ -1208,7 +1208,7 @@ public final class BatteryStatsService extends IBatteryStats.Stub public void noteModemControllerActivity(ModemActivityInfo info) { enforceCallingPermission(); - if (info == null || !info.isValid()) { + if (info == null) { Slog.e(TAG, "invalid modem data given: " + info); return; } diff --git a/services/core/java/com/android/server/am/ServiceRecord.java b/services/core/java/com/android/server/am/ServiceRecord.java index 1b65dbac2294..0e628289a09f 100644 --- a/services/core/java/com/android/server/am/ServiceRecord.java +++ b/services/core/java/com/android/server/am/ServiceRecord.java @@ -142,6 +142,10 @@ final class ServiceRecord extends Binder implements ComponentName.WithComponentN // allow while-in-use permissions in foreground service or not. // while-in-use permissions in FGS started from background might be restricted. boolean mAllowWhileInUsePermissionInFgs; + // The number of times Service.startForeground() is called; + int mStartForegroundCount; + // Last time mAllowWhileInUsePermissionInFgs is set. + long mLastSetFgsRestrictionTime; // the most recent package that start/bind this service. String mRecentCallingPackage; @@ -406,6 +410,8 @@ final class ServiceRecord extends Binder implements ComponentName.WithComponentN } pw.print(prefix); pw.print("allowWhileInUsePermissionInFgs="); pw.println(mAllowWhileInUsePermissionInFgs); + pw.print(prefix); pw.print("startForegroundCount="); + pw.println(mStartForegroundCount); pw.print(prefix); pw.print("recentCallingPackage="); pw.println(mRecentCallingPackage); if (delayed) { diff --git a/services/core/java/com/android/server/hdmi/HdmiCecMessageValidator.java b/services/core/java/com/android/server/hdmi/HdmiCecMessageValidator.java index c24973d4f2d4..8926e20ae5ea 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 @@ public class HdmiCecMessageValidator { 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 @@ public class HdmiCecMessageValidator { 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 cca8be8a1bea..b049d012f0ae 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 @@ public class HdmiControlService extends SystemService { // 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 @@ public class HdmiControlService extends SystemService { @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 @@ public class HdmiControlService extends SystemService { @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 @@ public class HdmiControlService extends SystemService { 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/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java index 5babfeb08f76..f83d059ce212 100755 --- a/services/core/java/com/android/server/notification/NotificationManagerService.java +++ b/services/core/java/com/android/server/notification/NotificationManagerService.java @@ -123,6 +123,7 @@ import android.app.INotificationManager; import android.app.ITransientNotification; import android.app.ITransientNotificationCallback; import android.app.IUriGrantsManager; +import android.app.KeyguardManager; import android.app.Notification; import android.app.NotificationChannel; import android.app.NotificationChannelGroup; @@ -479,6 +480,8 @@ public class NotificationManagerService extends SystemService { final ArrayMap<NotificationRecord, ArrayList<CancelNotificationRunnable>> mDelayedCancelations = new ArrayMap<>(); + private KeyguardManager mKeyguardManager; + // The last key in this list owns the hardware. ArrayList<String> mLights = new ArrayList<>(); @@ -1726,6 +1729,11 @@ public class NotificationManagerService extends SystemService { } @VisibleForTesting + void setKeyguardManager(KeyguardManager keyguardManager) { + mKeyguardManager = keyguardManager; + } + + @VisibleForTesting ShortcutHelper getShortcutHelper() { return mShortcutHelper; } @@ -2330,6 +2338,7 @@ public class NotificationManagerService extends SystemService { mAudioManager = (AudioManager) getContext().getSystemService(Context.AUDIO_SERVICE); mAudioManagerInternal = getLocalService(AudioManagerInternal.class); mWindowManagerInternal = LocalServices.getService(WindowManagerInternal.class); + mKeyguardManager = getContext().getSystemService(KeyguardManager.class); mZenModeHelper.onSystemReady(); mRoleObserver = new RoleObserver(getContext().getSystemService(RoleManager.class), mPackageManager, getContext().getMainExecutor()); @@ -6902,7 +6911,6 @@ public class NotificationManagerService extends SystemService { boolean beep = false; boolean blink = false; - final Notification notification = record.getSbn().getNotification(); final String key = record.getKey(); // Should this notification make noise, vibe, or use the LED? @@ -6924,7 +6932,7 @@ public class NotificationManagerService extends SystemService { if (!record.isUpdate && record.getImportance() > IMPORTANCE_MIN && !suppressedByDnd) { - sendAccessibilityEvent(notification, record.getSbn().getPackageName()); + sendAccessibilityEvent(record); sentAccessibilityEvent = true; } @@ -6946,7 +6954,7 @@ public class NotificationManagerService extends SystemService { boolean hasAudibleAlert = hasValidSound || hasValidVibrate; if (hasAudibleAlert && !shouldMuteNotificationLocked(record)) { if (!sentAccessibilityEvent) { - sendAccessibilityEvent(notification, record.getSbn().getPackageName()); + sendAccessibilityEvent(record); sentAccessibilityEvent = true; } if (DBG) Slog.v(TAG, "Interrupting!"); @@ -7663,17 +7671,30 @@ public class NotificationManagerService extends SystemService { return (x < low) ? low : ((x > high) ? high : x); } - void sendAccessibilityEvent(Notification notification, CharSequence packageName) { + void sendAccessibilityEvent(NotificationRecord record) { if (!mAccessibilityManager.isEnabled()) { return; } - AccessibilityEvent event = + final Notification notification = record.getNotification(); + final CharSequence packageName = record.getSbn().getPackageName(); + final AccessibilityEvent event = AccessibilityEvent.obtain(AccessibilityEvent.TYPE_NOTIFICATION_STATE_CHANGED); event.setPackageName(packageName); event.setClassName(Notification.class.getName()); - event.setParcelableData(notification); - CharSequence tickerText = notification.tickerText; + final int visibilityOverride = record.getPackageVisibilityOverride(); + final int notifVisibility = visibilityOverride == NotificationManager.VISIBILITY_NO_OVERRIDE + ? notification.visibility : visibilityOverride; + final int userId = record.getUser().getIdentifier(); + final boolean needPublic = userId >= 0 && mKeyguardManager.isDeviceLocked(userId); + if (needPublic && notifVisibility != Notification.VISIBILITY_PUBLIC) { + // Emit the public version if we're on the lockscreen and this notification isn't + // publicly visible. + event.setParcelableData(notification.publicVersion); + } else { + event.setParcelableData(notification); + } + final CharSequence tickerText = notification.tickerText; if (!TextUtils.isEmpty(tickerText)) { event.getText().add(tickerText); } diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index 1173df629796..4767823c8963 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -14289,9 +14289,15 @@ public class PackageManagerService extends IPackageManager.Stub return new ParceledListSlice<IntentFilter>(result) { @Override protected void writeElement(IntentFilter parcelable, Parcel dest, int callFlags) { - // IntentFilter has final Parcelable methods, so redirect to the subclass - ((ParsedIntentInfo) parcelable).writeIntentInfoToParcel(dest, - callFlags); + parcelable.writeToParcel(dest, callFlags); + } + + @Override + protected void writeParcelableCreator(IntentFilter parcelable, Parcel dest) { + // All Parcel#writeParcelableCreator does is serialize the class name to + // access via reflection to grab its CREATOR. This does that manually, pointing + // to the parent IntentFilter so that all of the subclass fields are ignored. + dest.writeString(IntentFilter.class.getName()); } }; } diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java index 836e6150414c..ae940d0e3c1c 100644 --- a/services/core/java/com/android/server/policy/PhoneWindowManager.java +++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java @@ -608,6 +608,8 @@ public class PhoneWindowManager implements WindowManagerPolicy { private int mPowerButtonSuppressionDelayMillis = POWER_BUTTON_SUPPRESSION_DELAY_DEFAULT_MILLIS; + private boolean mLockNowPending = false; + private static final int MSG_DISPATCH_MEDIA_KEY_WITH_WAKE_LOCK = 3; private static final int MSG_DISPATCH_MEDIA_KEY_REPEAT_WITH_WAKE_LOCK = 4; private static final int MSG_KEYGUARD_DRAWN_COMPLETE = 5; @@ -4941,6 +4943,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { mKeyguardDelegate.doKeyguardTimeout(options); } mLockScreenTimerActive = false; + mLockNowPending = false; options = null; } } @@ -4950,7 +4953,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { } } - ScreenLockTimeout mScreenLockTimeout = new ScreenLockTimeout(); + final ScreenLockTimeout mScreenLockTimeout = new ScreenLockTimeout(); @Override public void lockNow(Bundle options) { @@ -4962,6 +4965,9 @@ public class PhoneWindowManager implements WindowManagerPolicy { mScreenLockTimeout.setLockOptions(options); } mHandler.post(mScreenLockTimeout); + synchronized (mScreenLockTimeout) { + mLockNowPending = true; + } } // TODO (b/113840485): Move this logic to DisplayPolicy when lockscreen supports multi-display. @@ -4977,6 +4983,10 @@ public class PhoneWindowManager implements WindowManagerPolicy { private void updateLockScreenTimeout() { synchronized (mScreenLockTimeout) { + if (mLockNowPending) { + Log.w(TAG, "lockNow pending, ignore updating lockscreen timeout"); + return; + } final boolean enable = !mAllowLockscreenWhenOnDisplays.isEmpty() && mDefaultDisplayPolicy.isAwake() && mKeyguardDelegate != null && mKeyguardDelegate.isSecure(mCurrentUserId); diff --git a/services/core/java/com/android/server/role/RoleManagerService.java b/services/core/java/com/android/server/role/RoleManagerService.java index 392792dbae69..b75bce833e04 100644 --- a/services/core/java/com/android/server/role/RoleManagerService.java +++ b/services/core/java/com/android/server/role/RoleManagerService.java @@ -662,6 +662,12 @@ public class RoleManagerService extends SystemService implements RoleUserState.C @Override public String getDefaultSmsPackage(int userId) { + userId = handleIncomingUser(userId, false, "getDefaultSmsPackage"); + if (!mUserManagerInternal.exists(userId)) { + Slog.e(LOG_TAG, "user " + userId + " does not exist"); + return null; + } + long identity = Binder.clearCallingIdentity(); try { return CollectionUtils.firstOrNull( diff --git a/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java b/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java index 03900150cfae..71b3e61a9844 100644 --- a/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java +++ b/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java @@ -102,6 +102,7 @@ import android.os.Environment; import android.os.IStoraged; import android.os.IThermalEventListener; import android.os.IThermalService; +import android.os.OutcomeReceiver; import android.os.ParcelFileDescriptor; import android.os.Parcelable; import android.os.RemoteException; @@ -172,6 +173,7 @@ import com.android.server.stats.pull.netstats.SubInfo; import com.android.server.storage.DiskStatsFileLogger; import com.android.server.storage.DiskStatsLoggingService; +import java.util.concurrent.ExecutionException; import libcore.io.IoUtils; import org.json.JSONArray; @@ -1731,22 +1733,47 @@ public class StatsPullAtomService extends SystemService { int pullModemActivityInfoLocked(int atomTag, List<StatsEvent> pulledData) { long token = Binder.clearCallingIdentity(); try { - SynchronousResultReceiver modemReceiver = new SynchronousResultReceiver("telephony"); - mTelephony.requestModemActivityInfo(modemReceiver); - final ModemActivityInfo modemInfo = awaitControllerInfo(modemReceiver); + CompletableFuture<ModemActivityInfo> modemFuture = new CompletableFuture<>(); + mTelephony.requestModemActivityInfo(Runnable::run, + new OutcomeReceiver<ModemActivityInfo, + TelephonyManager.ModemActivityInfoException>() { + @Override + public void onResult(ModemActivityInfo result) { + modemFuture.complete(result); + } + + @Override + public void onError(TelephonyManager.ModemActivityInfoException e) { + Slog.w(TAG, "error reading modem stats:" + e); + modemFuture.complete(null); + } + }); + + ModemActivityInfo modemInfo; + try { + modemInfo = modemFuture.get(EXTERNAL_STATS_SYNC_TIMEOUT_MILLIS, + TimeUnit.MILLISECONDS); + } catch (TimeoutException | InterruptedException e) { + Slog.w(TAG, "timeout or interrupt reading modem stats: " + e); + return StatsManager.PULL_SKIP; + } catch (ExecutionException e) { + Slog.w(TAG, "exception reading modem stats: " + e.getCause()); + return StatsManager.PULL_SKIP; + } + if (modemInfo == null) { return StatsManager.PULL_SKIP; } StatsEvent e = StatsEvent.newBuilder() .setAtomId(atomTag) - .writeLong(modemInfo.getTimestamp()) + .writeLong(modemInfo.getTimestampMillis()) .writeLong(modemInfo.getSleepTimeMillis()) .writeLong(modemInfo.getIdleTimeMillis()) - .writeLong(modemInfo.getTransmitPowerInfo().get(0).getTimeInMillis()) - .writeLong(modemInfo.getTransmitPowerInfo().get(1).getTimeInMillis()) - .writeLong(modemInfo.getTransmitPowerInfo().get(2).getTimeInMillis()) - .writeLong(modemInfo.getTransmitPowerInfo().get(3).getTimeInMillis()) - .writeLong(modemInfo.getTransmitPowerInfo().get(4).getTimeInMillis()) + .writeLong(modemInfo.getTransmitDurationMillisAtPowerLevel(0)) + .writeLong(modemInfo.getTransmitDurationMillisAtPowerLevel(1)) + .writeLong(modemInfo.getTransmitDurationMillisAtPowerLevel(2)) + .writeLong(modemInfo.getTransmitDurationMillisAtPowerLevel(3)) + .writeLong(modemInfo.getTransmitDurationMillisAtPowerLevel(4)) .writeLong(modemInfo.getReceiveTimeMillis()) .build(); pulledData.add(e); diff --git a/services/core/java/com/android/server/tv/TvInputHardwareManager.java b/services/core/java/com/android/server/tv/TvInputHardwareManager.java index 277163a6e3ec..630317a150f6 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 @@ class TvInputHardwareManager implements TvInputHal.Callback { 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 382398a210bb..e0cc8e182079 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 @@ public class Vcn extends Handler { } 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 @@ public class Vcn extends Handler { 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 @@ public class Vcn extends Handler { 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 450257fcdecb..7dec4e785f5c 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 @@ public class VcnGatewayConnection extends StateMachine { 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 a2768c637d79..3a9b2dca3921 100644 --- a/services/java/com/android/server/SystemConfigService.java +++ b/services/java/com/android/server/SystemConfigService.java @@ -21,6 +21,7 @@ import static java.util.stream.Collectors.toMap; 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 @@ public class SystemConfigService extends SystemService { } 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/net/java/android/net/ConnectivityModuleConnector.java b/services/net/java/android/net/ConnectivityModuleConnector.java index 62f2c35f339b..c6b15c17bd3c 100644 --- a/services/net/java/android/net/ConnectivityModuleConnector.java +++ b/services/net/java/android/net/ConnectivityModuleConnector.java @@ -278,7 +278,10 @@ public class ConnectivityModuleConnector { // This code path is only run by the system server: only the system server binds // to the NetworkStack as a service. Other processes get the NetworkStack from // the ServiceManager. - maybeCrashWithTerribleFailure("Lost network stack", mPackageName); + maybeCrashWithTerribleFailure( + "Lost network stack. This is not the root cause of any issue, it is a side " + + "effect of a crash that happened earlier. Earlier logs should point to the " + + "actual issue.", mPackageName); } } diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTest.java index 3e5cbea6a2a4..c45d084ac7c9 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 @@ public class HdmiCecLocalDeviceTest { 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 ae7f422817e5..ae99dab6ed4e 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 @@ public class HdmiCecMessageValidatorTest { } private IntegerSubject assertMessageValidity(String message) { - return assertThat(mHdmiCecMessageValidator.isValid(buildMessage(message))); + return assertThat(mHdmiCecMessageValidator.isValid(buildMessage(message), false)); } /** diff --git a/services/tests/uiservicestests/src/com/android/server/notification/BuzzBeepBlinkTest.java b/services/tests/uiservicestests/src/com/android/server/notification/BuzzBeepBlinkTest.java index ad15a99a0d78..11ea4a46b73b 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/BuzzBeepBlinkTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/BuzzBeepBlinkTest.java @@ -46,6 +46,7 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.app.ActivityManager; +import android.app.KeyguardManager; import android.app.Notification; import android.app.Notification.Builder; import android.app.NotificationChannel; @@ -103,6 +104,8 @@ public class BuzzBeepBlinkTest extends UiServiceTestCase { NotificationUsageStats mUsageStats; @Mock IAccessibilityManager mAccessibilityService; + @Mock + KeyguardManager mKeyguardManager; NotificationRecordLoggerFake mNotificationRecordLogger = new NotificationRecordLoggerFake(); private InstanceIdSequence mNotificationInstanceIdSequence = new InstanceIdSequenceFake( 1 << 30); @@ -147,6 +150,7 @@ public class BuzzBeepBlinkTest extends UiServiceTestCase { when(mAudioManager.getStreamVolume(anyInt())).thenReturn(10); when(mAudioManager.getRingerModeInternal()).thenReturn(AudioManager.RINGER_MODE_NORMAL); when(mUsageStats.isAlertRateLimited(any())).thenReturn(false); + when(mKeyguardManager.isDeviceLocked(anyInt())).thenReturn(false); long serviceReturnValue = IntPair.of( AccessibilityManager.STATE_FLAG_ACCESSIBILITY_ENABLED, @@ -168,6 +172,7 @@ public class BuzzBeepBlinkTest extends UiServiceTestCase { mService.setFallbackVibrationPattern(FALLBACK_VIBRATION_PATTERN); mService.setUsageStats(mUsageStats); mService.setAccessibilityManager(accessibilityManager); + mService.setKeyguardManager(mKeyguardManager); mService.mScreenOn = false; mService.mInCallStateOffHook = false; mService.mNotificationPulseEnabled = true; @@ -484,6 +489,94 @@ public class BuzzBeepBlinkTest extends UiServiceTestCase { } @Test + public void testLockedPrivateA11yRedaction() throws Exception { + NotificationRecord r = getBeepyNotification(); + r.setPackageVisibilityOverride(NotificationManager.VISIBILITY_NO_OVERRIDE); + r.getNotification().visibility = Notification.VISIBILITY_PRIVATE; + when(mKeyguardManager.isDeviceLocked(anyInt())).thenReturn(true); + AccessibilityManager accessibilityManager = Mockito.mock(AccessibilityManager.class); + when(accessibilityManager.isEnabled()).thenReturn(true); + mService.setAccessibilityManager(accessibilityManager); + + mService.buzzBeepBlinkLocked(r); + + ArgumentCaptor<AccessibilityEvent> eventCaptor = + ArgumentCaptor.forClass(AccessibilityEvent.class); + + verify(accessibilityManager, times(1)) + .sendAccessibilityEvent(eventCaptor.capture()); + + AccessibilityEvent event = eventCaptor.getValue(); + assertEquals(r.getNotification().publicVersion, event.getParcelableData()); + } + + @Test + public void testLockedOverridePrivateA11yRedaction() throws Exception { + NotificationRecord r = getBeepyNotification(); + r.setPackageVisibilityOverride(Notification.VISIBILITY_PRIVATE); + r.getNotification().visibility = Notification.VISIBILITY_PUBLIC; + when(mKeyguardManager.isDeviceLocked(anyInt())).thenReturn(true); + AccessibilityManager accessibilityManager = Mockito.mock(AccessibilityManager.class); + when(accessibilityManager.isEnabled()).thenReturn(true); + mService.setAccessibilityManager(accessibilityManager); + + mService.buzzBeepBlinkLocked(r); + + ArgumentCaptor<AccessibilityEvent> eventCaptor = + ArgumentCaptor.forClass(AccessibilityEvent.class); + + verify(accessibilityManager, times(1)) + .sendAccessibilityEvent(eventCaptor.capture()); + + AccessibilityEvent event = eventCaptor.getValue(); + assertEquals(r.getNotification().publicVersion, event.getParcelableData()); + } + + @Test + public void testLockedPublicA11yNoRedaction() throws Exception { + NotificationRecord r = getBeepyNotification(); + r.setPackageVisibilityOverride(NotificationManager.VISIBILITY_NO_OVERRIDE); + r.getNotification().visibility = Notification.VISIBILITY_PUBLIC; + when(mKeyguardManager.isDeviceLocked(anyInt())).thenReturn(true); + AccessibilityManager accessibilityManager = Mockito.mock(AccessibilityManager.class); + when(accessibilityManager.isEnabled()).thenReturn(true); + mService.setAccessibilityManager(accessibilityManager); + + mService.buzzBeepBlinkLocked(r); + + ArgumentCaptor<AccessibilityEvent> eventCaptor = + ArgumentCaptor.forClass(AccessibilityEvent.class); + + verify(accessibilityManager, times(1)) + .sendAccessibilityEvent(eventCaptor.capture()); + + AccessibilityEvent event = eventCaptor.getValue(); + assertEquals(r.getNotification(), event.getParcelableData()); + } + + @Test + public void testUnlockedPrivateA11yNoRedaction() throws Exception { + NotificationRecord r = getBeepyNotification(); + r.setPackageVisibilityOverride(NotificationManager.VISIBILITY_NO_OVERRIDE); + r.getNotification().visibility = Notification.VISIBILITY_PRIVATE; + when(mKeyguardManager.isDeviceLocked(anyInt())).thenReturn(false); + AccessibilityManager accessibilityManager = Mockito.mock(AccessibilityManager.class); + when(accessibilityManager.isEnabled()).thenReturn(true); + mService.setAccessibilityManager(accessibilityManager); + + mService.buzzBeepBlinkLocked(r); + + ArgumentCaptor<AccessibilityEvent> eventCaptor = + ArgumentCaptor.forClass(AccessibilityEvent.class); + + verify(accessibilityManager, times(1)) + .sendAccessibilityEvent(eventCaptor.capture()); + + AccessibilityEvent event = eventCaptor.getValue(); + assertEquals(r.getNotification(), event.getParcelableData()); + } + + @Test public void testBeepInsistently() throws Exception { NotificationRecord r = getInsistentBeepyNotification(); diff --git a/telecomm/java/android/telecom/InCallService.java b/telecomm/java/android/telecom/InCallService.java index 5e3d26a5179d..705b4918726e 100644 --- a/telecomm/java/android/telecom/InCallService.java +++ b/telecomm/java/android/telecom/InCallService.java @@ -81,7 +81,8 @@ import java.util.List; * <pre> * {@code * <service android:name="your.package.YourInCallServiceImplementation" - * android:permission="android.permission.BIND_INCALL_SERVICE"> + * android:permission="android.permission.BIND_INCALL_SERVICE" + * android:exported="true"> * <meta-data android:name="android.telecom.IN_CALL_SERVICE_UI" android:value="true" /> * <meta-data android:name="android.telecom.IN_CALL_SERVICE_RINGING" * android:value="true" /> @@ -91,6 +92,10 @@ import java.util.List; * </service> * } * </pre> + * + * <em>Note: You should NOT mark your {@link InCallService} with the attribute + * {@code android:exported="false"}; doing so can result in a failure to bind to your implementation + * during calls.</em> * <p> * In addition to implementing the {@link InCallService} API, you must also declare an activity in * your manifest which handles the {@link Intent#ACTION_DIAL} intent. The example below illustrates diff --git a/telephony/java/android/telephony/ModemActivityInfo.java b/telephony/java/android/telephony/ModemActivityInfo.java index debb119c94bc..0bf8ce620eb7 100644 --- a/telephony/java/android/telephony/ModemActivityInfo.java +++ b/telephony/java/android/telephony/ModemActivityInfo.java @@ -16,8 +16,12 @@ package android.telephony; +import android.annotation.DurationMillisLong; +import android.annotation.ElapsedRealtimeLong; import android.annotation.IntDef; import android.annotation.NonNull; +import android.annotation.SystemApi; +import android.annotation.TestApi; import android.os.Parcel; import android.os.Parcelable; import android.os.SystemClock; @@ -25,46 +29,50 @@ import android.util.Range; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; -import java.util.ArrayList; -import java.util.List; +import java.util.Arrays; +import java.util.Objects; /** - * Reports modem activity information. + * Contains information about the modem's activity. May be useful for power stats reporting. * @hide */ +@SystemApi +@TestApi public final class ModemActivityInfo implements Parcelable { + private static final int TX_POWER_LEVELS = 5; + /** - * Tx(transmit) power level. see power index below - * <ul> - * <li> index 0 = tx_power < 0dBm. </li> - * <li> index 1 = 0dBm < tx_power < 5dBm. </li> - * <li> index 2 = 5dBm < tx_power < 15dBm. </li> - * <li> index 3 = 15dBm < tx_power < 20dBm. </li> - * <li> index 4 = tx_power > 20dBm. </li> - * </ul> - */ - public static final int TX_POWER_LEVELS = 5; - /** - * Tx(transmit) power level 0: tx_power < 0dBm + * Corresponds to transmit power of less than 0dBm. */ public static final int TX_POWER_LEVEL_0 = 0; + /** - * Tx(transmit) power level 1: 0dBm < tx_power < 5dBm + * Corresponds to transmit power between 0dBm and 5dBm. */ public static final int TX_POWER_LEVEL_1 = 1; + /** - * Tx(transmit) power level 2: 5dBm < tx_power < 15dBm + * Corresponds to transmit power between 5dBm and 15dBm. */ public static final int TX_POWER_LEVEL_2 = 2; + /** - * Tx(transmit) power level 3: 15dBm < tx_power < 20dBm. + * Corresponds to transmit power between 15dBm and 20dBm. */ public static final int TX_POWER_LEVEL_3 = 3; + /** - * Tx(transmit) power level 4: tx_power > 20dBm + * Corresponds to transmit power above 20dBm. */ public static final int TX_POWER_LEVEL_4 = 4; + /** + * The number of transmit power levels. Fixed by HAL definition. + */ + public static int getNumTxPowerLevels() { + return TX_POWER_LEVELS; + } + /** @hide */ @IntDef(prefix = {"TX_POWER_LEVEL_"}, value = { TX_POWER_LEVEL_0, @@ -82,34 +90,39 @@ public final class ModemActivityInfo implements Parcelable { new Range<>(5, 15), new Range<>(15, 20), new Range<>(20, Integer.MAX_VALUE) - }; private long mTimestamp; private int mSleepTimeMs; private int mIdleTimeMs; - private List<TransmitPower> mTransmitPowerInfo = new ArrayList<>(TX_POWER_LEVELS); + private int[] mTxTimeMs; private int mRxTimeMs; + /** + * @hide + */ + @TestApi public ModemActivityInfo(long timestamp, int sleepTimeMs, int idleTimeMs, @NonNull int[] txTimeMs, int rxTimeMs) { + Objects.requireNonNull(txTimeMs); + if (txTimeMs.length != TX_POWER_LEVELS) { + throw new IllegalArgumentException("txTimeMs must have length == TX_POWER_LEVELS"); + } mTimestamp = timestamp; mSleepTimeMs = sleepTimeMs; mIdleTimeMs = idleTimeMs; - populateTransmitPowerRange(txTimeMs); + mTxTimeMs = txTimeMs; mRxTimeMs = rxTimeMs; } - /** helper API to populate tx power range for each bucket **/ - private void populateTransmitPowerRange(@NonNull int[] transmitPowerMs) { - int i = 0; - for ( ; i < Math.min(transmitPowerMs.length, TX_POWER_LEVELS); i++) { - mTransmitPowerInfo.add(i, new TransmitPower(TX_POWER_RANGES[i], transmitPowerMs[i])); - } - // Make sure that mTransmitPowerInfo is fully initialized. - for ( ; i < TX_POWER_LEVELS; i++) { - mTransmitPowerInfo.add(i, new TransmitPower(TX_POWER_RANGES[i], 0)); - } + /** + * Provided for convenience in manipulation since the API exposes long values but internal + * representations are ints. + * @hide + */ + public ModemActivityInfo(long timestamp, long sleepTimeMs, long idleTimeMs, + @NonNull int[] txTimeMs, long rxTimeMs) { + this(timestamp, (int) sleepTimeMs, (int) idleTimeMs, txTimeMs, (int) rxTimeMs); } @Override @@ -118,7 +131,7 @@ public final class ModemActivityInfo implements Parcelable { + " mTimestamp=" + mTimestamp + " mSleepTimeMs=" + mSleepTimeMs + " mIdleTimeMs=" + mIdleTimeMs - + " mTransmitPowerInfo[]=" + mTransmitPowerInfo.toString() + + " mTxTimeMs[]=" + Arrays.toString(mTxTimeMs) + " mRxTimeMs=" + mRxTimeMs + "}"; } @@ -129,14 +142,12 @@ public final class ModemActivityInfo implements Parcelable { public static final @android.annotation.NonNull Parcelable.Creator<ModemActivityInfo> CREATOR = new Parcelable.Creator<ModemActivityInfo>() { - public ModemActivityInfo createFromParcel(Parcel in) { + public ModemActivityInfo createFromParcel(@NonNull Parcel in) { long timestamp = in.readLong(); int sleepTimeMs = in.readInt(); int idleTimeMs = in.readInt(); int[] txTimeMs = new int[TX_POWER_LEVELS]; - for (int i = 0; i < TX_POWER_LEVELS; i++) { - txTimeMs[i] = in.readInt(); - } + in.readIntArray(txTimeMs); int rxTimeMs = in.readInt(); return new ModemActivityInfo(timestamp, sleepTimeMs, idleTimeMs, txTimeMs, rxTimeMs); @@ -147,21 +158,25 @@ public final class ModemActivityInfo implements Parcelable { } }; - public void writeToParcel(Parcel dest, int flags) { + /** + * @param dest The Parcel in which the object should be written. + * @param flags Additional flags about how the object should be written. + */ + public void writeToParcel(@NonNull Parcel dest, int flags) { dest.writeLong(mTimestamp); dest.writeInt(mSleepTimeMs); dest.writeInt(mIdleTimeMs); - for (int i = 0; i < TX_POWER_LEVELS; i++) { - dest.writeInt(mTransmitPowerInfo.get(i).getTimeInMillis()); - } + dest.writeIntArray(mTxTimeMs); dest.writeInt(mRxTimeMs); } /** - * @return milliseconds since boot, including mTimeInMillis spent in sleep. - * @see SystemClock#elapsedRealtime() + * Gets the timestamp at which this modem activity info was recorded. + * + * @return The timestamp, as returned by {@link SystemClock#elapsedRealtime()}, when this + * {@link ModemActivityInfo} was recorded. */ - public long getTimestamp() { + public @ElapsedRealtimeLong long getTimestampMillis() { return mTimestamp; } @@ -171,35 +186,48 @@ public final class ModemActivityInfo implements Parcelable { } /** - * @return an arrayList of {@link TransmitPower} with each element representing the total time where - * transmitter is awake time (in ms) for a given power range (in dbm). + * Gets the amount of time the modem spent transmitting at a certain power level. * - * @see #TX_POWER_LEVELS + * @param powerLevel The power level to query. + * @return The amount of time, in milliseconds, that the modem spent transmitting at the + * given power level. */ - @NonNull - public List<TransmitPower> getTransmitPowerInfo() { - return mTransmitPowerInfo; + public @DurationMillisLong long getTransmitDurationMillisAtPowerLevel( + @TxPowerLevel int powerLevel) { + return mTxTimeMs[powerLevel]; + } + + /** + * Gets the range of transmit powers corresponding to a certain power level. + * + * @param powerLevel The power level to query + * @return A {@link Range} object representing the range of intensities (in dBm) to which this + * power level corresponds. + */ + public @NonNull Range<Integer> getTransmitPowerRange(@TxPowerLevel int powerLevel) { + return TX_POWER_RANGES[powerLevel]; } /** @hide */ public void setTransmitTimeMillis(int[] txTimeMs) { - populateTransmitPowerRange(txTimeMs); + mTxTimeMs = Arrays.copyOf(txTimeMs, TX_POWER_LEVELS); } - /** @hide */ + /** + * @return The raw array of transmit power durations + * @hide + */ @NonNull public int[] getTransmitTimeMillis() { - int[] transmitTimeMillis = new int[TX_POWER_LEVELS]; - for (int i = 0; i < transmitTimeMillis.length; i++) { - transmitTimeMillis[i] = mTransmitPowerInfo.get(i).getTimeInMillis(); - } - return transmitTimeMillis; + return mTxTimeMs; } /** - * @return total mTimeInMillis (in ms) when modem is in a low power or sleep state. + * Gets the amount of time (in milliseconds) when the modem is in a low power or sleep state. + * + * @return Time in milliseconds. */ - public int getSleepTimeMillis() { + public @DurationMillisLong long getSleepTimeMillis() { return mSleepTimeMs; } @@ -209,10 +237,44 @@ public final class ModemActivityInfo implements Parcelable { } /** - * @return total mTimeInMillis (in ms) when modem is awake but neither the transmitter nor receiver are - * active. + * Provided for convenience, since the API surface needs to return longs but internal + * representations are ints. + * @hide */ - public int getIdleTimeMillis() { + public void setSleepTimeMillis(long sleepTimeMillis) { + mSleepTimeMs = (int) sleepTimeMillis; + } + + /** + * Computes the difference between this instance of {@link ModemActivityInfo} and another + * instance. + * + * This method should be used to compute the amount of activity that has happened between two + * samples of modem activity taken at separate times. The sample passed in as an argument to + * this method should be the one that's taken later in time (and therefore has more activity). + * @param other The other instance of {@link ModemActivityInfo} to diff against. + * @return An instance of {@link ModemActivityInfo} representing the difference in modem + * activity. + */ + public @NonNull ModemActivityInfo getDelta(@NonNull ModemActivityInfo other) { + int[] txTimeMs = new int[ModemActivityInfo.TX_POWER_LEVELS]; + for (int i = 0; i < ModemActivityInfo.TX_POWER_LEVELS; i++) { + txTimeMs[i] = other.mTxTimeMs[i] - mTxTimeMs[i]; + } + return new ModemActivityInfo(other.getTimestampMillis(), + other.getSleepTimeMillis() - getSleepTimeMillis(), + other.getIdleTimeMillis() - getIdleTimeMillis(), + txTimeMs, + other.getReceiveTimeMillis() - getReceiveTimeMillis()); + } + + /** + * Gets the amount of time (in milliseconds) when the modem is awake but neither transmitting + * nor receiving. + * + * @return Time in milliseconds. + */ + public @DurationMillisLong long getIdleTimeMillis() { return mIdleTimeMs; } @@ -222,9 +284,20 @@ public final class ModemActivityInfo implements Parcelable { } /** - * @return rx(receive) mTimeInMillis in ms. + * Provided for convenience, since the API surface needs to return longs but internal + * representations are ints. + * @hide + */ + public void setIdleTimeMillis(long idleTimeMillis) { + mIdleTimeMs = (int) idleTimeMillis; + } + + /** + * Gets the amount of time (in milliseconds) when the modem is awake and receiving data. + * + * @return Time in milliseconds. */ - public int getReceiveTimeMillis() { + public @DurationMillisLong long getReceiveTimeMillis() { return mRxTimeMs; } @@ -234,71 +307,56 @@ public final class ModemActivityInfo implements Parcelable { } /** + * Provided for convenience, since the API surface needs to return longs but internal + * representations are ints. + * @hide + */ + public void setReceiveTimeMillis(long receiveTimeMillis) { + mRxTimeMs = (int) receiveTimeMillis; + } + + /** * Indicates if the modem has reported valid {@link ModemActivityInfo}. * * @return {@code true} if this {@link ModemActivityInfo} record is valid, * {@code false} otherwise. + * @hide */ + @TestApi public boolean isValid() { - for (TransmitPower powerInfo : getTransmitPowerInfo()) { - if(powerInfo.getTimeInMillis() < 0) { - return false; - } - } + boolean isTxPowerValid = Arrays.stream(mTxTimeMs).allMatch((i) -> i >= 0); - return ((getIdleTimeMillis() >= 0) && (getSleepTimeMillis() >= 0) + return isTxPowerValid && ((getIdleTimeMillis() >= 0) && (getSleepTimeMillis() >= 0) && (getReceiveTimeMillis() >= 0) && !isEmpty()); } - private boolean isEmpty() { - for (TransmitPower txVal : getTransmitPowerInfo()) { - if(txVal.getTimeInMillis() != 0) { - return false; - } - } + /** @hide */ + @TestApi + public boolean isEmpty() { + boolean isTxPowerEmpty = mTxTimeMs == null || mTxTimeMs.length == 0 + || Arrays.stream(mTxTimeMs).allMatch((i) -> i == 0); - return ((getIdleTimeMillis() == 0) && (getSleepTimeMillis() == 0) + return isTxPowerEmpty && ((getIdleTimeMillis() == 0) && (getSleepTimeMillis() == 0) && (getReceiveTimeMillis() == 0)); } - /** - * Transmit power Information, including the power range in dbm and the total time (in ms) where - * the transmitter is active/awake for this power range. - * e.g, range: 0dbm(lower) ~ 5dbm(upper) - * time: 5ms - */ - public class TransmitPower { - private int mTimeInMillis; - private Range<Integer> mPowerRangeInDbm; - /** @hide */ - public TransmitPower(@NonNull Range<Integer> range, int time) { - this.mTimeInMillis = time; - this.mPowerRangeInDbm = range; - } - - /** - * @return the total time in ms where the transmitter is active/wake for this power range - * {@link #getPowerRangeInDbm()}. - */ - public int getTimeInMillis() { - return mTimeInMillis; - } - /** - * @return the power range in dbm. e.g, range: 0dbm(lower) ~ 5dbm(upper) - */ - @NonNull - public Range<Integer> getPowerRangeInDbm() { - return mPowerRangeInDbm; - } + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + ModemActivityInfo that = (ModemActivityInfo) o; + return mTimestamp == that.mTimestamp + && mSleepTimeMs == that.mSleepTimeMs + && mIdleTimeMs == that.mIdleTimeMs + && mRxTimeMs == that.mRxTimeMs + && Arrays.equals(mTxTimeMs, that.mTxTimeMs); + } - @Override - public String toString() { - return "TransmitPower{" - + " mTimeInMillis=" + mTimeInMillis - + " mPowerRangeInDbm={" + mPowerRangeInDbm.getLower() - + "," + mPowerRangeInDbm.getUpper() - + "}}"; - } + @Override + public int hashCode() { + int result = Objects.hash(mTimestamp, mSleepTimeMs, mIdleTimeMs, mRxTimeMs); + result = 31 * result + Arrays.hashCode(mTxTimeMs); + return result; } } diff --git a/telephony/java/android/telephony/ServiceState.java b/telephony/java/android/telephony/ServiceState.java index 6da61b712916..1677c61eb9cf 100644 --- a/telephony/java/android/telephony/ServiceState.java +++ b/telephony/java/android/telephony/ServiceState.java @@ -444,7 +444,9 @@ public class ServiceState implements Parcelable { 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 fe7e5976b132..41e24ddb3fa6 100644 --- a/telephony/java/android/telephony/SignalStrengthUpdateRequest.java +++ b/telephony/java/android/telephony/SignalStrengthUpdateRequest.java @@ -26,8 +26,10 @@ import java.util.ArrayList; 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 @@ public final class SignalStrengthUpdateRequest implements Parcelable { } 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 @@ public final class SignalStrengthUpdateRequest implements Parcelable { * @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 @@ public final class SignalStrengthUpdateRequest implements Parcelable { } /** - * 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/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java index 4189784bf5e0..f55dc8b6f8f1 100644 --- a/telephony/java/android/telephony/TelephonyManager.java +++ b/telephony/java/android/telephony/TelephonyManager.java @@ -57,7 +57,9 @@ import android.os.Build; import android.os.Bundle; import android.os.Handler; import android.os.IBinder; +import android.os.OutcomeReceiver; import android.os.ParcelFileDescriptor; +import android.os.Parcelable; import android.os.PersistableBundle; import android.os.RemoteException; import android.os.ResultReceiver; @@ -84,6 +86,7 @@ import android.telephony.CallForwardingInfo.CallForwardingReason; import android.telephony.VisualVoicemailService.VisualVoicemailTask; import android.telephony.data.ApnSetting; import android.telephony.data.ApnSetting.MvnoType; +import android.telephony.data.SlicingConfig; import android.telephony.emergency.EmergencyNumber; import android.telephony.emergency.EmergencyNumber.EmergencyServiceCategories; import android.telephony.gba.UaSecurityProtocolIdentifier; @@ -176,6 +179,9 @@ public class TelephonyManager { */ public static final String MODEM_ACTIVITY_RESULT_KEY = "controller_activity"; + /** @hide */ + public static final String EXCEPTION_RESULT_KEY = "exception"; + /** * The process name of the Phone app as well as many other apps that use this process name, such * as settings and vendor components. @@ -10855,26 +10861,149 @@ public class TelephonyManager { return null; } + /** + * Exception that may be supplied to the callback provided in {@link #requestModemActivityInfo}. + * @hide + */ + @SystemApi + public static class ModemActivityInfoException extends Exception { + /** Indicates that an unknown error occurred */ + public static final int ERROR_UNKNOWN = 0; + + /** + * Indicates that the modem or phone processes are not available (such as when the device + * is in airplane mode). + */ + public static final int ERROR_PHONE_NOT_AVAILABLE = 1; + + /** + * Indicates that the modem supplied an invalid instance of {@link ModemActivityInfo} + */ + public static final int ERROR_INVALID_INFO_RECEIVED = 2; + + /** + * Indicates that the modem encountered an internal failure when processing the request + * for activity info. + */ + public static final int ERROR_MODEM_RESPONSE_ERROR = 3; + + /** @hide */ + @Retention(RetentionPolicy.SOURCE) + @IntDef(prefix = {"ERROR_"}, + value = { + ERROR_UNKNOWN, + ERROR_PHONE_NOT_AVAILABLE, + ERROR_INVALID_INFO_RECEIVED, + ERROR_MODEM_RESPONSE_ERROR, + }) + public @interface ModemActivityInfoError {} + + private final int mErrorCode; + + /** @hide */ + public ModemActivityInfoException(@ModemActivityInfoError int errorCode) { + mErrorCode = errorCode; + } + + public @ModemActivityInfoError int getErrorCode() { + return mErrorCode; + } + + @Override + public String toString() { + switch (mErrorCode) { + case ERROR_UNKNOWN: return "ERROR_UNKNOWN"; + case ERROR_PHONE_NOT_AVAILABLE: return "ERROR_PHONE_NOT_AVAILABLE"; + case ERROR_INVALID_INFO_RECEIVED: return "ERROR_INVALID_INFO_RECEIVED"; + case ERROR_MODEM_RESPONSE_ERROR: return "ERROR_MODEM_RESPONSE_ERROR"; + default: return "UNDEFINED"; + } + } + } /** - * Requests the modem activity info. The recipient will place the result - * in `result`. - * @param result The object on which the recipient will send the resulting - * {@link android.telephony.ModemActivityInfo} object with key of - * {@link #MODEM_ACTIVITY_RESULT_KEY}. + * Requests the current modem activity info. + * + * The provided instance of {@link ModemActivityInfo} represents the cumulative activity since + * the last restart of the phone process. + * + * @param callback A callback object to which the result will be delivered. If there was an + * error processing the request, {@link OutcomeReceiver#onError} will be called + * with more details about the error. * @hide */ - public void requestModemActivityInfo(@NonNull ResultReceiver result) { + @SystemApi + @RequiresPermission(Manifest.permission.MODIFY_PHONE_STATE) + public void requestModemActivityInfo(@NonNull @CallbackExecutor Executor executor, + @NonNull OutcomeReceiver<ModemActivityInfo, ModemActivityInfoException> callback) { + Objects.requireNonNull(executor); + Objects.requireNonNull(callback); + + // Pass no handler into the receiver, since we're going to be trampolining the call to the + // listener onto the provided executor. + ResultReceiver wrapperResultReceiver = new ResultReceiver(null) { + @Override + protected void onReceiveResult(int resultCode, Bundle data) { + if (data == null) { + Log.w(TAG, "requestModemActivityInfo: received null bundle"); + sendErrorToListener(ModemActivityInfoException.ERROR_UNKNOWN); + return; + } + data.setDefusable(true); + if (data.containsKey(EXCEPTION_RESULT_KEY)) { + int receivedErrorCode = data.getInt(EXCEPTION_RESULT_KEY); + sendErrorToListener(receivedErrorCode); + return; + } + + if (!data.containsKey(MODEM_ACTIVITY_RESULT_KEY)) { + Log.w(TAG, "requestModemActivityInfo: Bundle did not contain expected key"); + sendErrorToListener(ModemActivityInfoException.ERROR_UNKNOWN); + return; + } + Parcelable receivedResult = data.getParcelable(MODEM_ACTIVITY_RESULT_KEY); + if (!(receivedResult instanceof ModemActivityInfo)) { + Log.w(TAG, "requestModemActivityInfo: Bundle contained something that wasn't " + + "a ModemActivityInfo."); + sendErrorToListener(ModemActivityInfoException.ERROR_UNKNOWN); + return; + } + ModemActivityInfo modemActivityInfo = (ModemActivityInfo) receivedResult; + if (!modemActivityInfo.isValid()) { + Log.w(TAG, "requestModemActivityInfo: Received an invalid ModemActivityInfo"); + sendErrorToListener(ModemActivityInfoException.ERROR_INVALID_INFO_RECEIVED); + return; + } + Log.d(TAG, "requestModemActivityInfo: Sending result to app: " + modemActivityInfo); + sendResultToListener(modemActivityInfo); + } + + private void sendResultToListener(ModemActivityInfo info) { + Binder.withCleanCallingIdentity(() -> + executor.execute(() -> + callback.onResult(info))); + } + + private void sendErrorToListener(int code) { + ModemActivityInfoException e = new ModemActivityInfoException(code); + Binder.withCleanCallingIdentity(() -> + executor.execute(() -> + callback.onError(e))); + } + }; + try { ITelephony service = getITelephony(); if (service != null) { - service.requestModemActivityInfo(result); + service.requestModemActivityInfo(wrapperResultReceiver); return; } } catch (RemoteException e) { Log.e(TAG, "Error calling ITelephony#getModemActivityInfo", e); } - result.send(0, null); + executor.execute(() -> callback.onError( + new ModemActivityInfoException( + ModemActivityInfoException.ERROR_PHONE_NOT_AVAILABLE))); } /** @@ -15093,4 +15222,96 @@ public class TelephonyManager { return PhoneCapability.DEFAULT_SSSS_CAPABILITY; } } + + /** + * Exception that may be supplied to the callback in {@link #getNetworkSlicingConfiguration} if + * something goes awry. + */ + public static class SlicingException extends Exception { + /** + * Getting the current slicing configuration successfully. Used internally only. + * @hide + */ + public static final int SUCCESS = 0; + + /** + * The system timed out waiting for a response from the Radio. + */ + public static final int ERROR_TIMEOUT = 1; + + /** + * The modem returned a failure. + */ + public static final int ERROR_MODEM_ERROR = 2; + + /** @hide */ + @IntDef(prefix = {"ERROR_"}, value = { + ERROR_TIMEOUT, + ERROR_MODEM_ERROR, + }) + @Retention(RetentionPolicy.SOURCE) + public @interface SlicingError {} + + private final int mErrorCode; + + public SlicingException(@SlicingError int errorCode) { + mErrorCode = errorCode; + } + + /** + * Fetches the error code associated with this exception. + * @return An error code. + */ + public @SlicingError int getErrorCode() { + return mErrorCode; + } + } + + /** @hide */ + public static final String KEY_SLICING_CONFIG_HANDLE = "slicing_config_handle"; + + /** + * Request to get the current slicing configuration including URSP rules and + * NSSAIs (configured, allowed and rejected). + * + * This method can be invoked if one of the following requirements is met: + * <ul> + * <li>If the calling app has been granted the READ_PRIVILEGED_PHONE_STATE permission; this + * is a privileged permission that can only be granted to apps preloaded on the device. + * <li>If the calling app has carrier privileges (see {@link #hasCarrierPrivileges}). + * </ul> + * + * @param executor the executor on which callback will be invoked. + * @param callback a callback to receive the current slicing configuration. + */ + @SuppressAutoDoc // No support for carrier privileges (b/72967236). + @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) + public void getNetworkSlicingConfiguration( + @NonNull @CallbackExecutor Executor executor, + @NonNull OutcomeReceiver<SlicingConfig, SlicingException> callback) { + Objects.requireNonNull(executor); + Objects.requireNonNull(callback); + + try { + ITelephony telephony = getITelephony(); + if (telephony == null) { + throw new IllegalStateException("telephony service is null."); + } + telephony.getSlicingConfig(new ResultReceiver(null) { + @Override + protected void onReceiveResult(int resultCode, Bundle result) { + if (resultCode != SlicingException.SUCCESS) { + executor.execute(() -> callback.onError( + new SlicingException(resultCode))); + return; + } + SlicingConfig slicingConfig = + result.getParcelable(KEY_SLICING_CONFIG_HANDLE); + executor.execute(() -> callback.onResult(slicingConfig)); + } + }); + } catch (RemoteException ex) { + ex.rethrowAsRuntimeException(); + } + } } diff --git a/telephony/java/android/telephony/data/NetworkSliceInfo.java b/telephony/java/android/telephony/data/NetworkSliceInfo.java index 1d9009533685..232a93012f4b 100644 --- a/telephony/java/android/telephony/data/NetworkSliceInfo.java +++ b/telephony/java/android/telephony/data/NetworkSliceInfo.java @@ -19,8 +19,6 @@ package android.telephony.data; import android.annotation.IntDef; import android.annotation.IntRange; import android.annotation.NonNull; -import android.annotation.SuppressLint; -import android.annotation.SystemApi; import android.os.Parcel; import android.os.Parcelable; @@ -35,10 +33,7 @@ import java.util.Objects; * SliceServiceType defines the type of service provided by the slice, and SliceDifferentiator is * used to differentiate between multiple slices of the same type. If the devices is not on HPLMN, * the mappedHplmn versions of these 2 fields indicate the corresponding values in HPLMN. - * - * @hide */ -@SystemApi public final class NetworkSliceInfo implements Parcelable { /** * When set on a Slice Differentiator, this value indicates that there is no corresponding @@ -68,14 +63,14 @@ public final class NetworkSliceInfo implements Parcelable { /** * The min acceptable value for a Slice Differentiator + * @hide */ - @SuppressLint("MinMaxConstant") public static final int MIN_SLICE_DIFFERENTIATOR = -1; /** * The max acceptable value for a Slice Differentiator + * @hide */ - @SuppressLint("MinMaxConstant") public static final int MAX_SLICE_DIFFERENTIATOR = 0xFFFFFE; /** @hide */ @@ -88,6 +83,62 @@ public final class NetworkSliceInfo implements Parcelable { @Retention(RetentionPolicy.SOURCE) public @interface SliceServiceType {} + /** + * The slice status is unknown. This can happen during IWLAN->cellular handover when the + * NetworkSliceInfo is received over IWLAN. + */ + public static final int SLICE_STATUS_UNKNOWN = 0; + + /** + * The slice is configured but not allowed or rejected yet. + */ + public static final int SLICE_STATUS_CONFIGURED = 1; + + /** + * The slice is allowed to be used. + */ + public static final int SLICE_STATUS_ALLOWED = 2; + + /** + * The slice is rejected because not available in PLMN. + */ + public static final int SLICE_STATUS_REJECTED_NOT_AVAILABLE_IN_PLMN = 3; + + /** + * The slice is rejected because not available in registered area. + */ + public static final int SLICE_STATUS_REJECTED_NOT_AVAILABLE_IN_REGISTERED_AREA = 4; + + /** + * The slice is configured by home operator(HPLMN) in default and is used if configured/allowed + * slices are not available for the serving PLMN. + */ + public static final int SLICE_STATUS_DEFAULT_CONFIGURED = 5; + + /** + * The min acceptable value for a slice status. + * @hide + */ + public static final int MIN_SLICE_STATUS = SLICE_STATUS_UNKNOWN; + + /** + * The max acceptable value for a slice status. + * @hide + */ + public static final int MAX_SLICE_STATUS = SLICE_STATUS_DEFAULT_CONFIGURED; + + /** @hide */ + @IntDef(prefix = { "SLICE_STATUS_" }, value = { + SLICE_STATUS_UNKNOWN, + SLICE_STATUS_CONFIGURED, + SLICE_STATUS_ALLOWED, + SLICE_STATUS_REJECTED_NOT_AVAILABLE_IN_PLMN, + SLICE_STATUS_REJECTED_NOT_AVAILABLE_IN_REGISTERED_AREA, + SLICE_STATUS_DEFAULT_CONFIGURED, + }) + @Retention(RetentionPolicy.SOURCE) + public @interface SliceStatus {} + @SliceServiceType private final int mSliceServiceType; @@ -97,14 +148,18 @@ public final class NetworkSliceInfo implements Parcelable { private final int mMappedHplmnSliceServiceType; @IntRange(from = MIN_SLICE_DIFFERENTIATOR, to = MAX_SLICE_DIFFERENTIATOR) private final int mMappedHplmnSliceDifferentiator; + @SliceStatus + @IntRange(from = MIN_SLICE_STATUS, to = MAX_SLICE_STATUS) + private final int mStatus; private NetworkSliceInfo(@SliceServiceType int sliceServiceType, int sliceDifferentiator, int mappedHplmnSliceServiceType, - int mappedHplmnSliceDifferentiator) { + int mappedHplmnSliceDifferentiator, int status) { mSliceServiceType = sliceServiceType; mSliceDifferentiator = sliceDifferentiator; mMappedHplmnSliceDifferentiator = mappedHplmnSliceDifferentiator; mMappedHplmnSliceServiceType = mappedHplmnSliceServiceType; + mStatus = status; } /** @@ -157,11 +212,21 @@ public final class NetworkSliceInfo implements Parcelable { return mMappedHplmnSliceDifferentiator; } + /** + * Field to indicate the current status of the slice. + * @return the current status for this slice info. + */ + @SliceStatus + public int getStatus() { + return mStatus; + } + private NetworkSliceInfo(@NonNull Parcel in) { mSliceServiceType = in.readInt(); mSliceDifferentiator = in.readInt(); mMappedHplmnSliceServiceType = in.readInt(); mMappedHplmnSliceDifferentiator = in.readInt(); + mStatus = in.readInt(); } @Override @@ -175,6 +240,7 @@ public final class NetworkSliceInfo implements Parcelable { dest.writeInt(mSliceDifferentiator); dest.writeInt(mMappedHplmnSliceServiceType); dest.writeInt(mMappedHplmnSliceDifferentiator); + dest.writeInt(mStatus); } public static final @android.annotation.NonNull Parcelable.Creator<NetworkSliceInfo> CREATOR = @@ -200,6 +266,7 @@ public final class NetworkSliceInfo implements Parcelable { + ", mMappedHplmnSliceServiceType=" + sliceServiceTypeToString(mMappedHplmnSliceServiceType) + ", mMappedHplmnSliceDifferentiator=" + mMappedHplmnSliceDifferentiator + + ", mStatus=" + sliceStatusToString(mStatus) + '}'; } @@ -218,6 +285,25 @@ public final class NetworkSliceInfo implements Parcelable { } } + private static String sliceStatusToString(@SliceStatus int sliceStatus) { + switch(sliceStatus) { + case SLICE_STATUS_UNKNOWN: + return "UNKNOWN"; + case SLICE_STATUS_CONFIGURED: + return "CONFIGURED"; + case SLICE_STATUS_ALLOWED: + return "ALLOWED"; + case SLICE_STATUS_REJECTED_NOT_AVAILABLE_IN_PLMN: + return "REJECTED_NOT_AVAILABLE_IN_PLMN"; + case SLICE_STATUS_REJECTED_NOT_AVAILABLE_IN_REGISTERED_AREA: + return "REJECTED_NOT_AVAILABLE_IN_REGISTERED_AREA"; + case SLICE_STATUS_DEFAULT_CONFIGURED: + return "DEFAULT_CONFIGURED"; + default: + return Integer.toString(sliceStatus); + } + } + @Override public boolean equals(Object o) { if (this == o) return true; @@ -226,13 +312,14 @@ public final class NetworkSliceInfo implements Parcelable { return mSliceServiceType == sliceInfo.mSliceServiceType && mSliceDifferentiator == sliceInfo.mSliceDifferentiator && mMappedHplmnSliceServiceType == sliceInfo.mMappedHplmnSliceServiceType - && mMappedHplmnSliceDifferentiator == sliceInfo.mMappedHplmnSliceDifferentiator; + && mMappedHplmnSliceDifferentiator == sliceInfo.mMappedHplmnSliceDifferentiator + && mStatus == sliceInfo.mStatus; } @Override public int hashCode() { return Objects.hash(mSliceServiceType, mSliceDifferentiator, mMappedHplmnSliceServiceType, - mMappedHplmnSliceDifferentiator); + mMappedHplmnSliceDifferentiator, mStatus); } /** @@ -257,6 +344,9 @@ public final class NetworkSliceInfo implements Parcelable { private int mMappedHplmnSliceServiceType = SLICE_SERVICE_TYPE_NONE; @IntRange(from = MIN_SLICE_DIFFERENTIATOR, to = MAX_SLICE_DIFFERENTIATOR) private int mMappedHplmnSliceDifferentiator = SLICE_DIFFERENTIATOR_NO_SLICE; + @SliceStatus + @IntRange(from = MIN_SLICE_STATUS, to = MAX_SLICE_STATUS) + private int mStatus = SLICE_STATUS_UNKNOWN; /** * Default constructor for Builder. @@ -281,8 +371,7 @@ public final class NetworkSliceInfo implements Parcelable { * A value of {@link #SLICE_DIFFERENTIATOR_NO_SLICE} indicates that there is no * corresponding Slice. * - * @throws IllegalArgumentException if the parameter is not between - * {@link #MIN_SLICE_DIFFERENTIATOR} and {@link #MAX_SLICE_DIFFERENTIATOR}. + * @throws IllegalArgumentException if the parameter is not in the expected range. * * @return The same instance of the builder. */ @@ -316,8 +405,7 @@ public final class NetworkSliceInfo implements Parcelable { * A value of {@link #SLICE_DIFFERENTIATOR_NO_SLICE} indicates that there is no * corresponding Slice of the HPLMN. * - * @throws IllegalArgumentException if the parameter is not between - * {@link #MIN_SLICE_DIFFERENTIATOR} and {@link #MAX_SLICE_DIFFERENTIATOR}. + * @throws IllegalArgumentException if the parameter is not in the expected range. * * @return The same instance of the builder. */ @@ -334,6 +422,22 @@ public final class NetworkSliceInfo implements Parcelable { } /** + * Set the slice status. + * + * @throws IllegalArgumentException if the status is invalid. + * + * @return The same instance of the builder. + */ + @NonNull + public Builder setStatus(@SliceStatus int status) { + if (status < MIN_SLICE_STATUS || status > MAX_SLICE_STATUS) { + throw new IllegalArgumentException("The slice status is not valid"); + } + this.mStatus = status; + return this; + } + + /** * Build the {@link NetworkSliceInfo}. * * @return the {@link NetworkSliceInfo} object. @@ -341,7 +445,8 @@ public final class NetworkSliceInfo implements Parcelable { @NonNull public NetworkSliceInfo build() { return new NetworkSliceInfo(this.mSliceServiceType, this.mSliceDifferentiator, - this.mMappedHplmnSliceServiceType, this.mMappedHplmnSliceDifferentiator); + this.mMappedHplmnSliceServiceType, this.mMappedHplmnSliceDifferentiator, + this.mStatus); } } } diff --git a/telephony/java/android/telephony/data/RouteSelectionDescriptor.aidl b/telephony/java/android/telephony/data/RouteSelectionDescriptor.aidl new file mode 100644 index 000000000000..563a00e5d177 --- /dev/null +++ b/telephony/java/android/telephony/data/RouteSelectionDescriptor.aidl @@ -0,0 +1,19 @@ +/* + * Copyright 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 android.telephony.data; + +parcelable RouteSelectionDescriptor; diff --git a/telephony/java/android/telephony/data/RouteSelectionDescriptor.java b/telephony/java/android/telephony/data/RouteSelectionDescriptor.java new file mode 100644 index 000000000000..c2457f21f5ef --- /dev/null +++ b/telephony/java/android/telephony/data/RouteSelectionDescriptor.java @@ -0,0 +1,263 @@ +/** + * Copyright 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 android.telephony.data; + +import android.annotation.IntDef; +import android.annotation.IntRange; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.os.Parcel; +import android.os.Parcelable; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +/** + * Represents a single route selection descriptor as defined in + * 3GPP TS 24.526. + */ +public final class RouteSelectionDescriptor implements Parcelable { + /** + * The min acceptable value for the precedence of a route selection descriptor. + * @hide + */ + public static final int MIN_ROUTE_PRECEDENCE = 0; + + /** + * The max acceptable value for the precedence of a route selection descriptor. + * @hide + */ + public static final int MAX_ROUTE_PRECEDENCE = 255; + + /** + * The route selection descriptor is for the session with IPV4 type. + */ + public static final int SESSION_TYPE_IPV4 = 0; + + /** + * The route selection descriptor is for the session with IPV6 type. + */ + public static final int SESSION_TYPE_IPV6 = 1; + + /** + * The route selection descriptor is for the session with both IP and IPV6 types. + */ + public static final int SESSION_TYPE_IPV4V6 = 2; + + /** @hide */ + @IntDef(prefix = { "SESSION_TYPE_" }, value = { + SESSION_TYPE_IPV4, + SESSION_TYPE_IPV6, + SESSION_TYPE_IPV4V6, + }) + @Retention(RetentionPolicy.SOURCE) + public @interface RouteSessionType {} + + /** + * The route selection descriptor is using SSC mode 1. The session will provide continual + * support when UE's location is updated. + */ + public static final int ROUTE_SSC_MODE_1 = 1; + + /** + * The route selection descriptor is using SSC mode 2. The new session for the same network + * will be established after releasing the old session when UE's location is updated. + */ + public static final int ROUTE_SSC_MODE_2 = 2; + + /** + * The route selection descriptor is using SSC mode 3. The new session for the same network is + * allowed to be established before releasing the old session when UE's location is updated. + */ + public static final int ROUTE_SSC_MODE_3 = 3; + + /** + * The min acceptable value for the SSC mode of a route selection descriptor. + * @hide + */ + public static final int MIN_ROUTE_SSC_MODE = ROUTE_SSC_MODE_1; + + /** + * The max acceptable value for the SSC mode of a route selection descriptor. + * @hide + */ + public static final int MAX_ROUTE_SSC_MODE = ROUTE_SSC_MODE_3; + + /** @hide */ + @IntDef(prefix = { "ROUTE_SSC_MODE_" }, value = { + ROUTE_SSC_MODE_1, + ROUTE_SSC_MODE_2, + ROUTE_SSC_MODE_3, + }) + @Retention(RetentionPolicy.SOURCE) + public @interface RouteSscMode {} + + @IntRange(from = MIN_ROUTE_PRECEDENCE, to = MAX_ROUTE_PRECEDENCE) + private final int mPrecedence; + @RouteSessionType + private final int mSessionType; + @RouteSscMode + @IntRange(from = MIN_ROUTE_SSC_MODE, to = MAX_ROUTE_SSC_MODE) + private final int mSscMode; + private final List<NetworkSliceInfo> mSliceInfo; + private final List<String> mDnn; + + /** @hide */ + RouteSelectionDescriptor(android.hardware.radio.V1_6.RouteSelectionDescriptor rsd) { + this(rsd.precedence, rsd.sessionType.value(), rsd.sscMode.value(), rsd.sliceInfo, + rsd.dnn); + } + + /** @hide */ + public RouteSelectionDescriptor(int precedence, int sessionType, int sscMode, + List<android.hardware.radio.V1_6.SliceInfo> sliceInfo, List<String> dnn) { + mPrecedence = precedence; + mSessionType = sessionType; + mSscMode = sscMode; + mSliceInfo = new ArrayList<NetworkSliceInfo>(); + for (android.hardware.radio.V1_6.SliceInfo si : sliceInfo) { + mSliceInfo.add(sliceInfoBuilder(si)); + } + mDnn = new ArrayList<String>(); + mDnn.addAll(dnn); + } + + private NetworkSliceInfo sliceInfoBuilder(android.hardware.radio.V1_6.SliceInfo si) { + NetworkSliceInfo.Builder builder = new NetworkSliceInfo.Builder() + .setSliceServiceType(si.sst) + .setMappedHplmnSliceServiceType(si.mappedHplmnSst); + if (si.sliceDifferentiator != NetworkSliceInfo.SLICE_DIFFERENTIATOR_NO_SLICE) { + builder + .setSliceDifferentiator(si.sliceDifferentiator) + .setMappedHplmnSliceDifferentiator(si.mappedHplmnSD); + } + return builder.build(); + } + + private RouteSelectionDescriptor(Parcel p) { + mPrecedence = p.readInt(); + mSessionType = p.readInt(); + mSscMode = p.readInt(); + mSliceInfo = p.createTypedArrayList(NetworkSliceInfo.CREATOR); + mDnn = new ArrayList<String>(); + p.readStringList(mDnn); + } + + /** + * Precedence value in the range of 0 to 255. Higher value has lower precedence. + * @return the precedence value for this route selection descriptor. + */ + @IntRange(from = MIN_ROUTE_PRECEDENCE, to = MAX_ROUTE_PRECEDENCE) + public int getPrecedence() { + return mPrecedence; + } + + /** + * This is used for checking which session type defined in 3GPP TS 23.501 is allowed for the + * route in a route selection descriptor. + * @return the session type for this route selection descriptor. + */ + @RouteSessionType + public int getSessionType() { + return mSessionType; + } + + /** + * SSC mode stands for Session and Service Continuity mode (which specifies the IP continuity + * mode) as defined in 3GPP TS 23.501. + * @return the SSC mode for this route selection descriptor. + */ + @RouteSscMode + public int getSscMode() { + return mSscMode; + } + + /** + * This is the list of all the slices available in the route selection descriptor as indicated + * by the network. These are the slices that can be used by the device if this route selection + * descriptor is used based the traffic (see 3GPP TS 23.501 for details). + * @return the list of all the slices available in the route selection descriptor. + */ + public @NonNull List<NetworkSliceInfo> getSliceInfo() { + return mSliceInfo; + } + + /** + * DNN stands for Data Network Name and represents an APN as defined in 3GPP TS 23.003. There + * can be 0 or more DNNs specified in a route selection descriptor. + * @return the list of DNN for this route selection descriptor. + */ + public @NonNull List<String> getDataNetworkName() { + return mDnn; + } + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + dest.writeInt(mPrecedence); + dest.writeInt(mSessionType); + dest.writeInt(mSscMode); + dest.writeTypedList(mSliceInfo, flags); + dest.writeStringList(mDnn); + } + + public static final @NonNull Parcelable.Creator<RouteSelectionDescriptor> CREATOR = + new Parcelable.Creator<RouteSelectionDescriptor>() { + @Override + public RouteSelectionDescriptor createFromParcel(Parcel source) { + return new RouteSelectionDescriptor(source); + } + + @Override + public RouteSelectionDescriptor[] newArray(int size) { + return new RouteSelectionDescriptor[size]; + } + }; + + @Override + public int describeContents() { + return 0; + } + + @Override + public boolean equals(@Nullable Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + RouteSelectionDescriptor that = (RouteSelectionDescriptor) o; + return mPrecedence == that.mPrecedence + && mSessionType == that.mSessionType + && mSscMode == that.mSscMode + && mSliceInfo.size() == that.mSliceInfo.size() + && mSliceInfo.containsAll(that.mSliceInfo) + && mDnn.size() == that.mDnn.size() + && mDnn.containsAll(that.mDnn); + } + + @Override + public int hashCode() { + return Objects.hash(mPrecedence, mSessionType, mSscMode, mSliceInfo, mDnn); + } + + @Override + public String toString() { + return "{.precedence = " + mPrecedence + ", .sessionType = " + mSessionType + + ", .sscMode = " + mSscMode + ", .sliceInfo = " + mSliceInfo + + ", .dnn = " + mDnn + "}"; + } +} diff --git a/telephony/java/android/telephony/data/SlicingConfig.aidl b/telephony/java/android/telephony/data/SlicingConfig.aidl new file mode 100644 index 000000000000..ad93d8cafeba --- /dev/null +++ b/telephony/java/android/telephony/data/SlicingConfig.aidl @@ -0,0 +1,19 @@ +/* + * Copyright 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 android.telephony.data; + +parcelable SlicingConfig; diff --git a/telephony/java/android/telephony/data/SlicingConfig.java b/telephony/java/android/telephony/data/SlicingConfig.java new file mode 100644 index 000000000000..990e4d218930 --- /dev/null +++ b/telephony/java/android/telephony/data/SlicingConfig.java @@ -0,0 +1,137 @@ +/** + * Copyright 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 android.telephony.data; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.os.Parcel; +import android.os.Parcelable; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +/** + * Represents a slicing configuration + */ +public final class SlicingConfig implements Parcelable { + private final List<UrspRule> mUrspRules; + private final List<NetworkSliceInfo> mSliceInfo; + + public SlicingConfig() { + mUrspRules = new ArrayList<UrspRule>(); + mSliceInfo = new ArrayList<NetworkSliceInfo>(); + } + + /** @hide */ + public SlicingConfig(android.hardware.radio.V1_6.SlicingConfig sc) { + this(sc.urspRules, sc.sliceInfo); + } + + /** @hide */ + public SlicingConfig(List<android.hardware.radio.V1_6.UrspRule> urspRules, + List<android.hardware.radio.V1_6.SliceInfo> sliceInfo) { + mUrspRules = new ArrayList<UrspRule>(); + for (android.hardware.radio.V1_6.UrspRule ur : urspRules) { + mUrspRules.add(new UrspRule(ur.precedence, ur.trafficDescriptors, + ur.routeSelectionDescriptor)); + } + mSliceInfo = new ArrayList<NetworkSliceInfo>(); + for (android.hardware.radio.V1_6.SliceInfo si : sliceInfo) { + mSliceInfo.add(sliceInfoBuilder(si)); + } + } + + private NetworkSliceInfo sliceInfoBuilder(android.hardware.radio.V1_6.SliceInfo si) { + NetworkSliceInfo.Builder builder = new NetworkSliceInfo.Builder() + .setSliceServiceType(si.sst) + .setMappedHplmnSliceServiceType(si.mappedHplmnSst); + if (si.sliceDifferentiator != NetworkSliceInfo.SLICE_DIFFERENTIATOR_NO_SLICE) { + builder + .setSliceDifferentiator(si.sliceDifferentiator) + .setMappedHplmnSliceDifferentiator(si.mappedHplmnSD); + } + return builder.build(); + } + + /** @hide */ + public SlicingConfig(Parcel p) { + mUrspRules = p.createTypedArrayList(UrspRule.CREATOR); + mSliceInfo = p.createTypedArrayList(NetworkSliceInfo.CREATOR); + } + + /** + * This list contains the current URSP rules. Empty list represents that no rules are + * configured. + * @return the current URSP rules for this slicing configuration. + */ + public @NonNull List<UrspRule> getUrspRules() { + return mUrspRules; + } + + /** + * @return the list of all slices for this slicing configuration. + */ + public @NonNull List<NetworkSliceInfo> getSliceInfo() { + return mSliceInfo; + } + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + dest.writeTypedList(mUrspRules, flags); + dest.writeTypedList(mSliceInfo, flags); + } + + public static final @NonNull Parcelable.Creator<SlicingConfig> CREATOR = + new Parcelable.Creator<SlicingConfig>() { + @Override + public SlicingConfig createFromParcel(Parcel source) { + return new SlicingConfig(source); + } + + @Override + public SlicingConfig[] newArray(int size) { + return new SlicingConfig[size]; + } + }; + + @Override + public int describeContents() { + return 0; + } + + @Override + public boolean equals(@Nullable Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + SlicingConfig that = (SlicingConfig) o; + return mUrspRules.size() == that.mUrspRules.size() + && mUrspRules.containsAll(that.mUrspRules) + && mSliceInfo.size() == that.mSliceInfo.size() + && mSliceInfo.containsAll(that.mSliceInfo); + } + + @Override + public int hashCode() { + return Objects.hash(mUrspRules, mSliceInfo); + } + + @Override + public String toString() { + return "{.urspRules = " + mUrspRules + ", .sliceInfo = " + mSliceInfo + "}"; + } +} diff --git a/telephony/java/android/telephony/data/TrafficDescriptor.java b/telephony/java/android/telephony/data/TrafficDescriptor.java index 480379d641b6..d813bc58fe6f 100644 --- a/telephony/java/android/telephony/data/TrafficDescriptor.java +++ b/telephony/java/android/telephony/data/TrafficDescriptor.java @@ -18,20 +18,17 @@ package android.telephony.data; import android.annotation.NonNull; import android.annotation.Nullable; -import android.annotation.SystemApi; import android.os.Parcel; import android.os.Parcelable; import java.util.Objects; /** - * A traffic descriptor, as defined in 3GPP TS 24.526 Section 5.2. It is used for URSP traffic - * matching as described in 3GPP TS 24.526 Section 4.2.2. It includes an optional DNN, which, - * if present, must be used for traffic matching; it does not specify the end point to be used for - * the data call. - * @hide + * A traffic descriptor, as defined in 3GPP TS 24.526 Section 5.2. It is used for UE Route Selection + * Policy(URSP) traffic matching as described in 3GPP TS 24.526 Section 4.2.2. It includes an + * optional Data Network Name(DNN), which, if present, must be used for traffic matching; it does + * not specify the end point to be used for the data call. */ -@SystemApi public final class TrafficDescriptor implements Parcelable { private final String mDnn; private final String mOsAppId; @@ -45,8 +42,10 @@ public final class TrafficDescriptor implements Parcelable { * Create a traffic descriptor, as defined in 3GPP TS 24.526 Section 5.2 * @param dnn optional DNN, which must be used for traffic matching, if present * @param osAppId OsId + osAppId of the traffic descriptor + * + * @hide */ - public TrafficDescriptor(@Nullable String dnn, @Nullable String osAppId) { + public TrafficDescriptor(String dnn, String osAppId) { mDnn = dnn; mOsAppId = osAppId; } @@ -55,12 +54,13 @@ public final class TrafficDescriptor implements Parcelable { * DNN stands for Data Network Name and represents an APN as defined in 3GPP TS 23.003. * @return the DNN of this traffic descriptor. */ - public @Nullable String getDnn() { + public @Nullable String getDataNetworkName() { return mDnn; } /** - * OsAppId represents the OsId + OsAppId as defined in 3GPP TS 24.526 Section 5.2. + * OsAppId is the app id as defined in 3GPP TS 24.526 Section 5.2, and it identifies a traffic + * category. * @return the OS App ID of this traffic descriptor. */ public @Nullable String getOsAppId() { @@ -108,4 +108,65 @@ public final class TrafficDescriptor implements Parcelable { public int hashCode() { return Objects.hash(mDnn, mOsAppId); } + + /** + * Provides a convenient way to set the fields of a {@link TrafficDescriptor} when creating a + * new instance. + * + * <p>The example below shows how you might create a new {@code TrafficDescriptor}: + * + * <pre><code> + * + * TrafficDescriptor response = new TrafficDescriptor.Builder() + * .setDnn("") + * .build(); + * </code></pre> + */ + public static final class Builder { + private String mDnn = null; + private String mOsAppId = null; + + /** + * Default constructor for Builder. + */ + public Builder() { + } + + /** + * Set the Data Network Name(DNN). + * + * @return The same instance of the builder. + */ + @NonNull + public Builder setDataNetworkName(@NonNull String dnn) { + this.mDnn = dnn; + return this; + } + + /** + * Set the OS App ID. + * + * @return The same instance of the builder. + */ + @NonNull + public Builder setOsAppId(@NonNull String osAppId) { + this.mOsAppId = osAppId; + return this; + } + + /** + * Build the {@link TrafficDescriptor}. + * + * @throws IllegalArgumentException if DNN and OS App ID are null. + * + * @return the {@link TrafficDescriptor} object. + */ + @NonNull + public TrafficDescriptor build() { + if (this.mDnn == null && this.mOsAppId == null) { + throw new IllegalArgumentException("DNN and OS App ID are null"); + } + return new TrafficDescriptor(this.mDnn, this.mOsAppId); + } + } } diff --git a/telephony/java/android/telephony/data/UrspRule.aidl b/telephony/java/android/telephony/data/UrspRule.aidl new file mode 100644 index 000000000000..2bed583c750e --- /dev/null +++ b/telephony/java/android/telephony/data/UrspRule.aidl @@ -0,0 +1,19 @@ +/* + * Copyright 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 android.telephony.data; + +parcelable UrspRule; diff --git a/telephony/java/android/telephony/data/UrspRule.java b/telephony/java/android/telephony/data/UrspRule.java new file mode 100644 index 000000000000..e2c47fd86b4b --- /dev/null +++ b/telephony/java/android/telephony/data/UrspRule.java @@ -0,0 +1,178 @@ +/** + * Copyright 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 android.telephony.data; + +import android.annotation.IntRange; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.hardware.radio.V1_6.OptionalDnn; +import android.hardware.radio.V1_6.OptionalOsAppId; +import android.os.Parcel; +import android.os.Parcelable; + + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +/** + * Represents a single URSP rule as defined in 3GPP TS 24.526. URSP stands for UE Route Selection + * Policy. In 5G, network can provide URSP information to devices which provides information on + * what connection parameters should be used for what traffic. + */ +public final class UrspRule implements Parcelable { + /** + * The min acceptable value for the precedence of a URSP rule. + * @hide + */ + public static final int MIN_URSP_PRECEDENCE = 0; + + /** + * The max acceptable value for the precedence of a URSP rule. + * @hide + */ + public static final int MAX_URSP_PRECEDENCE = 255; + + @IntRange(from = MIN_URSP_PRECEDENCE, to = MAX_URSP_PRECEDENCE) + private final int mPrecedence; + private final List<TrafficDescriptor> mTrafficDescriptors; + private final List<RouteSelectionDescriptor> mRouteSelectionDescriptor; + + UrspRule(android.hardware.radio.V1_6.UrspRule ur) { + this(ur.precedence, ur.trafficDescriptors, ur.routeSelectionDescriptor); + } + + /** @hide */ + public UrspRule(int precedence, + List<android.hardware.radio.V1_6.TrafficDescriptor> trafficDescriptors, + List<android.hardware.radio.V1_6.RouteSelectionDescriptor> routeSelectionDescriptor) { + mPrecedence = precedence; + mTrafficDescriptors = new ArrayList<TrafficDescriptor>(); + for (android.hardware.radio.V1_6.TrafficDescriptor td : trafficDescriptors) { + mTrafficDescriptors.add(convertToTrafficDescriptor(td)); + } + mRouteSelectionDescriptor = new ArrayList<RouteSelectionDescriptor>(); + for (android.hardware.radio.V1_6.RouteSelectionDescriptor rsd : routeSelectionDescriptor) { + mRouteSelectionDescriptor.add(new RouteSelectionDescriptor(rsd)); + } + } + + /** Convert an ArrayList of Bytes to an exactly-sized primitive array */ + private byte[] arrayListToPrimitiveArray(ArrayList<Byte> bytes) { + byte[] ret = new byte[bytes.size()]; + for (int i = 0; i < ret.length; i++) { + ret[i] = bytes.get(i); + } + return ret; + } + + private TrafficDescriptor convertToTrafficDescriptor( + android.hardware.radio.V1_6.TrafficDescriptor td) { + String dnn = td.dnn.getDiscriminator() == OptionalDnn.hidl_discriminator.noinit + ? null : td.dnn.value(); + String osAppId = td.osAppId.getDiscriminator() == OptionalOsAppId.hidl_discriminator.noinit + ? null : new String(arrayListToPrimitiveArray(td.osAppId.value().osAppId)); + TrafficDescriptor.Builder builder = new TrafficDescriptor.Builder(); + if (dnn != null) { + builder.setDataNetworkName(dnn); + } + if (osAppId != null) { + builder.setOsAppId(osAppId); + } + return builder.build(); + } + + private UrspRule(Parcel p) { + mPrecedence = p.readInt(); + mTrafficDescriptors = p.createTypedArrayList(TrafficDescriptor.CREATOR); + mRouteSelectionDescriptor = p.createTypedArrayList(RouteSelectionDescriptor.CREATOR); + } + + /** + * Precedence value in the range of 0 to 255. Higher value has lower precedence. + * @return the precedence value for this URSP rule. + */ + @IntRange(from = MIN_URSP_PRECEDENCE, to = MAX_URSP_PRECEDENCE) + public int getPrecedence() { + return mPrecedence; + } + + /** + * These traffic descriptors are used as a matcher for network requests. + * @return the traffic descriptors which are associated to this URSP rule. + */ + public @NonNull List<TrafficDescriptor> getTrafficDescriptors() { + return mTrafficDescriptors; + } + + /** + * List of routes (connection parameters) that must be used by the device for requests matching + * a traffic descriptor. + * @return the route selection descriptors which are associated to this URSP rule. + */ + public @NonNull List<RouteSelectionDescriptor> getRouteSelectionDescriptor() { + return mRouteSelectionDescriptor; + } + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + dest.writeInt(mPrecedence); + dest.writeTypedList(mTrafficDescriptors, flags); + dest.writeTypedList(mRouteSelectionDescriptor, flags); + } + + public static final @NonNull Parcelable.Creator<UrspRule> CREATOR = + new Parcelable.Creator<UrspRule>() { + @Override + public UrspRule createFromParcel(Parcel source) { + return new UrspRule(source); + } + + @Override + public UrspRule[] newArray(int size) { + return new UrspRule[size]; + } + }; + + @Override + public int describeContents() { + return 0; + } + + @Override + public boolean equals(@Nullable Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + UrspRule that = (UrspRule) o; + return mPrecedence == that.mPrecedence + && mTrafficDescriptors.size() == that.mTrafficDescriptors.size() + && mTrafficDescriptors.containsAll(that.mTrafficDescriptors) + && mRouteSelectionDescriptor.size() == that.mRouteSelectionDescriptor.size() + && mRouteSelectionDescriptor.containsAll(that.mRouteSelectionDescriptor); + } + + @Override + public int hashCode() { + return Objects.hash(mPrecedence, mTrafficDescriptors, mRouteSelectionDescriptor); + } + + @Override + public String toString() { + return "{.precedence = " + mPrecedence + ", .trafficDescriptors = " + mTrafficDescriptors + + ", .routeSelectionDescriptor = " + mRouteSelectionDescriptor + "}"; + } +} diff --git a/telephony/java/android/telephony/ims/ImsCallSession.java b/telephony/java/android/telephony/ims/ImsCallSession.java index 0aff99709a52..dfe5e6c93f53 100755 --- a/telephony/java/android/telephony/ims/ImsCallSession.java +++ b/telephony/java/android/telephony/ims/ImsCallSession.java @@ -766,7 +766,10 @@ public class ImsCallSession { * The method is only valid to call when the session state is in * {@link ImsCallSession.State#IDLE}. * - * @param callee dialed string to make the call to + * @param callee dial string to make the call to. The platform passes the dialed number + * entered by the user as-is. The {@link ImsService} should ensure that the + * number is formatted in SIP messages appropriately (e.g. using + * {@link android.telephony.PhoneNumberUtils#formatNumberToE164(String, String)}). * @param profile call profile to make the call with the specified service type, * call type and media information * @see Listener#callSessionStarted, Listener#callSessionStartFailed @@ -788,7 +791,10 @@ public class ImsCallSession { * The method is only valid to call when the session state is in * {@link ImsCallSession.State#IDLE}. * - * @param participants participant list to initiate an IMS conference call + * @param participants participant list to initiate an IMS conference call. The platform passes + * the dialed numbers entered by the user as-is. The {@link ImsService} should + * ensure that the number is formatted in SIP messages appropriately (e.g. using + * {@link android.telephony.PhoneNumberUtils#formatNumberToE164(String, String)}). * @param profile call profile to make the call with the specified service type, * call type and media information * @see Listener#callSessionStarted, Listener#callSessionStartFailed diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl index ae5f997360cd..d812b4641748 100644 --- a/telephony/java/com/android/internal/telephony/ITelephony.aidl +++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl @@ -2447,4 +2447,10 @@ interface ITelephony { * Gets the current phone capability. */ PhoneCapability getPhoneCapability(); + + /** + * Request to get the current slicing configuration including URSP rules and + * NSSAIs (configured, allowed and rejected). + */ + void getSlicingConfig(in ResultReceiver callback); } diff --git a/telephony/java/com/android/internal/telephony/RILConstants.java b/telephony/java/com/android/internal/telephony/RILConstants.java index 822fc44146d6..fe8e6715fe35 100644 --- a/telephony/java/com/android/internal/telephony/RILConstants.java +++ b/telephony/java/com/android/internal/telephony/RILConstants.java @@ -527,6 +527,7 @@ public interface RILConstants { int RIL_REQUEST_SET_DATA_THROTTLING = 221; int RIL_REQUEST_SET_ALLOWED_NETWORK_TYPES_BITMAP = 222; int RIL_REQUEST_GET_ALLOWED_NETWORK_TYPES_BITMAP = 223; + int RIL_REQUEST_GET_SLICING_CONFIG = 224; /* Responses begin */ int RIL_RESPONSE_ACKNOWLEDGEMENT = 800; diff --git a/test-mock/Android.bp b/test-mock/Android.bp index 460a26d63cd6..0bb61987a514 100644 --- a/test-mock/Android.bp +++ b/test-mock/Android.bp @@ -32,7 +32,9 @@ java_sdk_library { ":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 @@ java_sdk_library { 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 000000000000..4420a4413f5b --- /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 840a588bfe88..086ef95877e6 100644 --- a/tests/StagedInstallTest/Android.bp +++ b/tests/StagedInstallTest/Android.bp @@ -50,7 +50,7 @@ java_test_host { "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 0f84f6ebe522..c9a8947ab5ef 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 @@ public class VcnGatewayConnectionConnectedStateTest extends VcnGatewayConnection triggerValidation(NetworkAgent.VALIDATION_STATUS_VALID); verify(mSafeModeTimeoutAlarm).cancel(); assertFalse(mGatewayConnection.isInSafeMode()); + verifySafeModeStateAndCallbackFired(1 /* invocationCount */, false /* isInSafeMode */); } @Test @@ -391,6 +392,7 @@ public class VcnGatewayConnectionConnectedStateTest extends VcnGatewayConnection triggerValidation(NetworkAgent.VALIDATION_STATUS_VALID); + verifySafeModeStateAndCallbackFired(2 /* invocationCount */, false /* isInSafeMode */); assertFalse(mGatewayConnection.isInSafeMode()); } @@ -400,7 +402,7 @@ public class VcnGatewayConnectionConnectedStateTest extends VcnGatewayConnection 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 @@ public class VcnGatewayConnectionConnectedStateTest extends VcnGatewayConnection 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 a696b3ae28f7..64d0bca15ce9 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 com.android.server.vcn.VcnTestUtils.setupIpSecManager; 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 @@ public class VcnGatewayConnectionTestBase { 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 @@ public class VcnGatewayConnectionTestBase { 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 35feda11ec29..8bc6f167bd7f 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 7ccdab103d9a..8c66911b3c48 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 0cd9037dcb55..6890d99b104c 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 { |